Can I style text in localized Flutter application? - flutter

I am using intl package (by using flutter_localizations) and I was wondering how one would go about styling portions of localized text.
Example:
{
"name": "Your name is {name}",
"#name": {
"placeholders": {
"name": {}
}
}
}
Usage:
class MyWidget extends StatelessWidget {
String _name = 'User';
// ... some Flutter widget
Text(AppLocalizations.of(context)!.name(_name),
Expectation:
Your name is User
Is there a recommended approach to solve this? I have found styled_text package but if I could avoid using 3rd party lib, I would prefer it.
I want to avoid splitting text into multiple items as that would not work because different languages might require different ordering and I do not want to deal with that inside widgets by detecting language programmatically.

Text widget has own style property
Text('Your Text' , style: TextStyle())
Also you can read this medium article

Related

Flutter Localizations with variables

I'm trying to localize my Flutter app by following the documentation.
What I'm trying to achieve is that while building widgets dynamically, I wanted to translate the data which comes from my model. This is what I've tried so far
List.generate(services.length, (index) {
final Service service = services[index];
return Material(
borderRadius: BorderRadius.circular(10.0),
child: Text(
AppLocalizations.of(context).{{service.title}} // Here I wanted to translate the service title
),
);
}
How can I achieve this in Flutter? or any other possible method to translate dynamic contents.
Dynamic translation is not supported by Flutter localization packages. You can try to integrate google translation API in you app. But I strongly convinced that it should be a server-side feature and Flutter client should obtain already translated messages in your models. To achieve this, for example, you can use http-headers with your device locale to tell client's language to server.
Also you need to use arguments for Intl messages according to this guide. Here is an example of message with String argument in AppLocalizations:
String someLocalizedString(String argument) => Intl.message(
'Localized part - $argument',
name: 'someLocalizedString',
locale: localeName,
args: [argument],
);
This string inside .arb file:
"someLocalizedString": "Localized part - {argument}",
"#someLocalizedString": {
"type": "text",
"placeholders": {
"argument": {}
}
}

flutter test enterText on widget extending EditableText "Bad state: No element" : building editable text for mention

I have a class that extends EditableText, which provides styled editing - colour spans and so on.
The parent widget has a bool flag to use default TextInput or the custom one that extends EditableText.
Testing works fine for the default one:
final String dummyDesc = 'dummy desc';
final Finder mention = find.byType(Mention);
await tester.enterText(mention, dummyDesc);
however, if I do the same on a page where Mention uses the custom input, the test fails as it cannot find a TextInput - as there isn't one.
Error:
Bad state: No element
How Do you use enterText on a widget that extends EditableText? Or something similar?
This appears to be due to the showKeyboard method that enterText calls internally: https://github.com/flutter/flutter/blob/3b067049adc14ffb300e78d9b0d0adf3d581de7c/packages/flutter_test/lib/src/widget_tester.dart#L854
It is checking that the finder matches or is a descendent of EditableText, which does it's matching based on the runtimeType.
It's a bit hacky, but you could provide a runtimeType getter, which returns EditableText and will allow it to work.
class Mention extends EditableText {
// ...
Type get runtimeType => EditableText;
// ...
}
Caveat: This will mean that you will not be able to find.byType(Mention) any more. You can work around this by writing a custom finder, or using the predicate finder find.byElementPredicate((Element element) => element.widget is Mention).

Most elegant/efficient way to refactor widgets in Flutter and Dart

Searching online on "how to refactor Flutter widgets" I found that there exist two possible ways that are both functioning as per my testing, still very different from a structural standpoint. The second method, indeed includes and additional building instruction, which should bring a further burden on the app performances right?
This is the code I want to refactor:
Container(
child: Column(
children: <Widget> [
[long code to create a button with many properties],
[long code to create a button with many properties],
[long code to create a button with many properties],
[long code to create a button with many properties],
],),);
These are the main ways I found:
1):
Widget MyButton(Color color, String text) {
return [long code to create a button with many properties];
}
2):
class MyButton extends StatelessWidget {
MyButton(this.color, this.text);
final Color color;
final String text;
#override
Widget build(BuildContext context) {
return [long code to create a button with many properties];
}
}
Which is the best method?
Please take a look and consider this other question:
What is the difference between functions and classes to create reusable widgets?
Short answer: It' better the second method (both efficient and elegant).
In the first method (extract to a function), you are just creating a function that return the encapsulated widget.
In the second method (extract to a class), you are extracting the widget to a new class that extends from StatelessWidget. This difference gives to the Flutter framework a way to make optimizations.

Extracting Class Members like Widget Builders to a Different File?

In developing some of the screens for my flutter app, I regularly need to dynamically render widgets based on the state of the screen. For circumstances where it makes sense to create a separate widget and include it, I do that.
However, there are many use cases where what I need to render is not fit for a widget, and leverages existing state from the page. Therefore I use builder methods to render the appropriate widgets to the page. As anyone who uses Flutter knows, that can lead to lengthy code where you need to scroll up/down a lot to get to what you need to work on.
For better maintainability, I would love to move those builder methods into separate files, and then just include them. This would make it much easier to work on specific code widgets rendered and make the screen widget much cleaner.
But I haven't found a proper way to extract that dynamic widget code, which makes use of state, calls to update state, etc. I'm looking for a type of "include" file that would insert code into the main screen and render as if it's part of the core code.
Is this possible? How to achieve?
With the introduction of extension members, I came across this really neat way of achieving exactly what your described!
Say you have a State class defined like this:
class MyWidgetState extends State<MyWidget> {
int cakes;
#override
void initState() {
super.initState();
cakes = 0;
}
#override
Widget build(BuildContext context) {
return Builder(
builder: (context) => Text('$cakes'),
);
}
}
As you can see, there is a local variable cakes and a builder function. The very neat way to extract this builder now is the following:
extension CakesBuilderExtension on MyWidgetState {
Widget cakesBuilder(BuildContext context) {
return Text('$cakes');
}
}
Now, the cakes member can be accessed from the extension even if the extension is placed in another file.
Now, you would update your State class like this (the builder changed):
class MyWidgetState extends State<MyWidget> {
int cakes;
#override
void initState() {
super.initState();
cakes = 0;
}
#override
Widget build(BuildContext context) {
return Builder(
builder: cakesBuilder,
);
}
}
The cakesBuilder can be referenced from MyWidgetState, even though it is only declared in the CakesBuilderExtension!
Note
The extension feature requires Dart 2.6. This is not yet available in the stable channel, but should be around the end of 2019 I guess. Thus, you need to use the dev or master channels: flutter channel dev or flutter channel master and update the environment constraint in your pubspec.yaml file:
environment:
sdk: '>=2.6.0-dev.8.2 <3.0.0'

Use multiple LocalizationsDelegates in Flutter

I'm facing an issue where I'm trying to use multiple LocalizationsDelegates in a MaterialApp.
I'm using the Dart intl tools to provide translations to my labels. When I have multiple LocalizationsDelegates only the one that is specified the first gets the translated values. The labels of the next delegate, get the default value provided in the Intl.message() function.
Short, self contained, correct example
I've set up a minimal project as an example of this issue on GitHub.
Code snippets
In the MaterialApp, I define a bunch of localizationsDelegates, including two app specific ones: DogLocalizationsDelegate and CatLocalizationsDelegate.
MaterialApp(
// other properties
locale: Locale("en"),
localizationsDelegates: [
CatLocalizationsDelegate(),
DogLocalizationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: [
const Locale('en'),
const Locale('nl'),
],
);
The delegates have the same boilerplate code, but provide different labels.
Here's how the DogLocalizations and its DogLocalizationsDelegate look like.
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'messages_all.dart';
class DogLocalizations {
static Future<DogLocalizations> load(Locale locale) {
final String name = locale.languageCode;
final String localeName = Intl.canonicalizedLocale(name);
return initializeMessages(localeName).then((_) {
Intl.defaultLocale = localeName;
return DogLocalizations();
});
}
static DogLocalizations of(BuildContext context) {
return Localizations.of<DogLocalizations>(context, DogLocalizations);
}
String get bark {
return Intl.message(
'<insert dog sound>',
name: 'bark',
);
}
}
class DogLocalizationsDelegate extends LocalizationsDelegate<DogLocalizations> {
const DogLocalizationsDelegate();
#override
bool isSupported(Locale locale) => ['en', 'nl'].contains(locale.languageCode);
#override
Future<DogLocalizations> load(Locale locale) => DogLocalizations.load(locale);
#override
bool shouldReload(DogLocalizationsDelegate old) => false;
}
The CatLocalizations are the same, but with a meow String getter. Full example in the GitHub project.
Commands used to generate translations files
I'm using multiple extraction and generation commands instead of having multiple files in one command. This is because I'm actually having this problem with a library (with its own labels) and a consumer of that library (that also has its own labels).
Extract labels of both cats and dogs
flutter pub run intl_translation:extract_to_arb --output-dir=lib/cat_labels/gen lib/cat_labels/CatLabels.dart
flutter pub run intl_translation:extract_to_arb --output-dir=lib/dog_labels/gen lib/dog_labels/DogLabels.dart
Translate the generated intl_messages.arb to have two language files
intl_en.arb
intl_nl.arb
And then add the correct translated values to these files.
Generate the dart files from ARB
flutter pub run intl_translation:generate_from_arb --output-dir=lib/cat_labels lib/cat_labels/CatLabels.dart lib/cat_labels/gen/intl_*.arb
flutter pub run intl_translation:generate_from_arb --output-dir=lib/dog_labels lib/dog_labels/DogLabels.dart lib/dog_labels/gen/intl_*.arb
Issue
In this demo project, having the following order of delegates:
// main.dart (line 20)
DogLocalizationsDelegate(),
CatLocalizationsDelegate(),
will give the translation for the bark label, but not for the meow label.
When switching it:
// main.dart (line 20)
CatLocalizationsDelegate(),
DogLocalizationsDelegate(),
will give the translation for the meow label, but not for the bark label.
Why multiple localization delegates
In case you're wondering why: I'm using labels in a library and in the consumer apps of that library.
Important to know is that it's not (really) possible to specify both localization files in the same generator command because of this.
From what I learned, no, you can’t use multiple localizations delegates like this. That is because intl’s initializeMessages can only be called once per locale.
So in your example, once your CatLocalizationsDelegate runs initializeMessages, the one for DogLocalizationsDelegate has no effect. That’s why you are only seeing the translation Meow! but not Dog’s, or whichever one gets to run it first.
For additional reading, check out https://phrase.com/blog/posts/how-to-internationalize-a-flutter-app/ and please share feedback at https://github.com/flutter/flutter/issues/41437.
I find a solution with a dedicated flutter package available here: [https://pub.dev/packages/multiple_localization][1]
You can use the multiple_localizations package. it worked for me