Use multiple LocalizationsDelegates in Flutter - 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

Related

How to migrate Provider to Riverpod?

I got this error:
'ChangeNotifierProvider' isn't a function. Try correcting the name to
match an existing function, or define a method or function named
'ChangeNotifierProvider'
I'm looking to migrate provider to Riverpod, but it is difficult for me to do that
This is my code:
void main() async {
await Hive.initFlutter();
await Hive.openBox("Habit_Database");
runApp(MultiProvider(providers: [
ChangeNotifierProvider(
create: (context) => UserProvider(),
),
], child: const MyApp()));
}
How can I solve this issue?
Thanks for any help you can provide
I'm working on this right now for a client. I have chosen to maintain the existing provider infrastructure while slowly migrating one provider at a time to riverpod.
To make this work, I first constructed a renaming shim for all of the exported provider functions. It imports provider, and establishes typedefs to alias those names to universally end in "X", including the extension on BlockContext which becomes .readX and .selectX. I tested this by not having the "X" initially, then renaming each symbol in VSC one at a time, which surprisingly worked well. The renaming shim looks something like:
import 'package:provider/provider.dart' as provider;
typedef ProviderX<T> = provider.Provider<T>;
typedef MultiProviderX = provider.MultiProvider;
typedef ChangeNotifierProviderX<T extends ChangeNotifier> = provider.ChangeNotifierProvider<T>;
which continues for about 100 lines. The tricky ones are the extensions:
extension ReadContext on BuildContext {
T readX<T>() => provider.ReadContext(this).read<T>();
}
extension SelectContext on BuildContext {
R selectX<T, R>(R Function(T value) selector) => provider.SelectContext(this).select(selector);
}
Admittedly, once I started the pattern, Github copilot eagerly offered me line after line, and was wrong for only a few things initially.
Next, I added the RiverPod ProviderScope to my runApp, and selected a particular provider to migrate. I created the equivalent in RiverPod, nicely namespaced because "FooProvider" became "fooProvider", and then located all references to that class in .readX or ConsumerX access. I inserted the equivalent with Consumer blocks or ConsumerWidget widgets, giving me a ref to use with ref.read or ref.watch as appropriate.
It's not trivial. But once you get over "the great rename" hurdle, the rest is just a rather mechanical translation, and can be done incrementally.
The error message is indicating that ChangeNotifierProvider isn't a function. You are trying to migrate from provider to Riverpod.
In Riverpod, the equivalent class for ChangeNotifierProvider is ChangeNotifierProvider.autoDispose.
So you should replace this line:
ChangeNotifierProvider(
create: (context) =>
UserProvider(),
),
with this line:
ChangeNotifierProvider.autoDispose( create: (context) => UserProvider(),),
This should solve the issue.

Flutter constants File with Provider package

I'm having trouble figuring out the best way to set up some constants (mainly strings). Currently I'm using a constants.dart file that just has the const variables defined in there and import it whenever it's needed. No class or anything, just a blank dart file. This works, however, I recently implemented localization using the Flutter Intl plugin in Android Studio. I got everything to work and can do something like this S.of(context).settings and it gets the translation from the correct file. My problem comes with some constant list of strings I have in my constants.dart file. I use them in many places for option selects. They look like this:
const playType = [
'RP/Story Focused',
'Battle/Combat Focused',
'Puzzles and Challenges',
'Exploration/Travel',
];
const length = [
'One Shot',
'2-5 Sessions',
'5-10 Sessions',
'On-going Campaign',
];
I cant change the the strings to the Intl reference because there is not context to be passed. Not sure how to set up a class that is loaded but not sure how to set that up and use the Provider package to serve it up.
EDIT:
heres the Constants file. Calling this with the provider is fine. The issue comes when I need to use the localization on the strings in the lists
import 'package:scryer/generated/l10n.dart';
class Constants {
Constants._();
static final instance = Constants._();
static List<String> playType = [
S.of(context).rpstoryFocused,//need a reference to a context
'Battle/Combat Focused',
'Puzzles and Challenges',
'Exploration/Travel',
];
static const length = [
'One Shot',
'2-5 Sessions',
'5-10 Sessions',
'On-going Campaign',
];
}
Heres how I call the constants on the actual page. Its a button that on press goes to a new screen that is either a multiselect checkbox list or single select radiobutton list. I pass the constants as an argument for the list
MaterialPageRoute(builder: (context) {
return MultiSelectScreen(
args: MultiSelectArguments(
label: S.of(context).selectPreferredPlayStyle,
options: playType, //this is the constants list reference
selections:
profile.playType,
),
);
})
The easy solution is to not use a constant and just create the lists in these spots, only used like twice i think, but better practice is to pull it out since its being used multiple times
you can try.
class Constants {
final BuildContext context;
Constants(#required this.context);
// static final instance = Constants._(); // to make class a singleton
static const playType = [
'RP/Story Focused',
'Battle/Combat Focused',
'Puzzles and Challenges',
'Exploration/Travel',
];
static const length = [
'One Shot',
'2-5 Sessions',
'5-10 Sessions',
'On-going Campaign',
];
}
Then, at the root of your project you should provide this class like;
void main() {
/** WidgetsFlutterBinding.ensureInitialized() is required in Flutter v1.9.4+ before using any plugins if the code is executed before runApp. */
WidgetsFlutterBinding.ensureInitialized();
runApp( Provider(
create: (context) => Constants(context),
child: MyApp(),
),
);
}
You will be able to access these constants in any BuildContext anywhere down the widget tree as follows;
final Contants constants = Provider<Constants>.of(context);
you get the length constant like; constants.length

Text widget with wrong emoji on initialization in Flutter

I want to display an emoji within a text widget, using Flutter.
When I copy an example emoji from the internet, some of them shows up with 2 characters in my IDE.
E.g:
static String dualCharEmoji = "⚔️";
static String singleCharEmoji = "🗡";
When I use this variable in the text widget, both of them work fine:
Text("⚔️",)
Text("🗡",)
However, only when first running the app, the dual character emoji shows up as its first character only.
i.e. Only when first opening the app, the sword icon shows up as ⚔ instead of as ⚔️
After it gets reloaded it gets fixed, and hot reloading/hot restarting does not makes it bug again.
My question is:
Is this a bug? Am I missing some detail here? Why does it only happen when first opening the app?
How can I show a 2 sized emoji from the start?
I'm using the following Flutter version:
>flutter --version
Flutter 1.9.1+hotfix.4 • channel stable • https://github.com/flutter/flutter.git
Framework • revision cc949a8e8b (9 weeks ago) • 2019-09-27 15:04:59 -0700
Engine • revision b863200c37
Tools • Dart 2.5.0
See the minimum reproducible example below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
static String dualCharEmoji = "⚔️";
static String singleCharEmoji = "🗡";
String text = dualCharEmoji;
int count = 0;
void swapText() {
setState(() {
if (count % 2 == 0)
text = singleCharEmoji;
else
text = dualCharEmoji;
count++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
text,
style: TextStyle(fontSize: 50),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: swapText,
),
);
}
}
After creating this question, doing many unsuccessful tries, and no working answer, I've created an issue on Flutter's official Github repository.
After I showed my problem, an user explained that that is a bug related to how Flutter renders and caches the fonts, in case of the default font cannot handle properly some character:
The first time that dualCharEmoji is rendered Flutter sees that the default font does not handle the 2694 character. [...]it can render the basic 2694 crossed swords character but not the 2694 FE0F emoji.
When singleCharEmoji is rendered Flutter now includes the NotoSansSymbols font in the list of fonts given to the HarfBuzz text shaper.[...] and matchFamilyStyleCharacter returns NotoColorEmoji. Flutter also adds this font to the cache.
The next time the engine tries to render dualCharEmoji it provides both cached fonts (NotoSansSymbols and NotoColorEmoji) to HarfBuzz. Now that HarfBuzz is aware of the sequences defined in NotoColorEmoji it can recognize the 2694 FE0F emoji and return it to Flutter.
I've referenced here only some parts of the discussion. You can read the full discussion and explanation on the issue page I've linked above.
Users suggested some workarounds, namely two:
First, you can force the engine to pre-cache a font that handles emojis correctly. You can do this by adding the following code in the build, or the initState method (anywhere that runs before building the Text Widget):
import 'dart:ui';
ParagraphBuilder pb = ParagraphBuilder(ParagraphStyle(locale: window.locale));
pb.addText('\ud83d\ude01'); // smiley face emoji
pb.build().layout(ParagraphConstraints(width: 100));
This worked on my example project, but as stated on the Issue page:
However, this relies on implementation details of the current Flutter text engine that may change in the future.
So be aware that this workaround can stop working at any given time.
The second workaround is given as follow:
For now the work around is to list the desired font in the text style and it will be resolved correctly.
Which is simply to give a TextStyle property to the Text Widget, with its fontFamily (or the fontFamilyFallback) property set. The chosen font must already support all characters required. This also worked for me, however I had to include a custom font from my computer (or from a public online package).

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'

Supporting multiple languages for constant strings in Flutter

I would like to start putting all my constant strings (like labels etc.) into a place that can be translated at a later stage.
How is this handled in Flutter?
Create a Localizations.dart file
Add the following code to that file:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart' show SynchronousFuture;
class DemoLocalizations {
DemoLocalizations(this.locale);
final Locale locale;
static DemoLocalizations of(BuildContext context) {
return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
}
static Map<String, Map<String, String>> _localizedValues = {
'en': {
'title': 'App title',
'googleLogin': 'Login with Google'
},
'es': {
'title': 'Título de App',
'googleLogin': 'Conectar con Google'
},
};
String get title {
return _localizedValues[locale.languageCode]['title'];
}
String get googleLogin {
return _localizedValues[locale.languageCode]['googleLogin'];
}
}
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
const DemoLocalizationsDelegate();
#override
bool isSupported(Locale locale) => ['en', 'es'].contains(locale.languageCode);
#override
Future<DemoLocalizations> load(Locale locale) {
// Returning a SynchronousFuture here because an async "load" operation
// isn't needed to produce an instance of DemoLocalizations.
return new SynchronousFuture<DemoLocalizations>(new DemoLocalizations(locale));
}
#override
bool shouldReload(DemoLocalizationsDelegate old) => false;
}
Import Localizations.dart into the file where you use the strings.
Add the delegate DemoLocalizationsDelegate in the MaterialApp
MaterialApp(
localizationsDelegates: [
MyLocalizationsDelegate(),
],
...
)
Substitute new Text("App Title"), with new Text(DemoLocalizations.of(context).title),
For each new string you want to localize, you need to add the translated text to each language's map and then add the String get... line.
It's a bit cumbersome but it does what you need it to.
This is a quick overview of one way of doing it.
You can read more about it in the Flutter docs: https://flutter.io/tutorials/internationalization/
I asked on gitter and I got the following:
Translation/Internationalization isn't a feature we consider "done"
yet. https://pub.dartlang.org/packages/intl works in Flutter. We have
a bug tracking this more generally: flutter/flutter#393
More complete internationalization (i18n) and accessibility support
are two of the big arcs of work ahead of Flutter in the coming months.
Another example of i18n work we have planned, is completing
Right-to-left (RTL) layouts for our provided Widgets (e.g. teaching
the Material library's Scaffold to place the Drawer on the left when
the locale is an RTL language). RTL Text support works today, but
there are no widgets which are out-of-the-box RTL-layout aware at this
moment.