-
Notifications
You must be signed in to change notification settings - Fork 56
injected_i18n_api
Hello world, hola mondo, boujour le monde and مرحبا بالعالم .
With RM.injectI18N
, app internationalization has never been easier.
- Create translation classes
- InjectI18N
- Listen to i18n (TopStatelessWidget)
- Consume the translation
- Change locale
- Device system locale
- supported locales
- Example of app localization using arb files
You start by creating the translation class.
Example: For English US:
//My naming convention is: EnUS = (En: language code, US: country code)
class EnUS{
final helloWorld = 'Hello world';
//You can use method for plurals
String countTimes(int count){
if (count<=1){
return '$count time';
}
return '$count times';
}
//Can have formJson method if the translation is load asynchronously
EnUs fromJson(String json){
...
}
}
For Arabic
//Implements EnUS for type consistency.
//We can use an abstract class as type.
class ArDZ implements EnUS {
final helloWorld = 'مرحبا بالعالم';
//You can use method for plurals
String countTimes(int count){
if (count<=1){
return '$count مرة';
}
return '$count مرات';
}
}
static InjectedI18N<I18N> i18n = RM.injectI18N<I18N>(
Map<Locale, FutureOr<I18N> Function()> selectionMap, {
String? persistKey,
//
//Similar to other injected
SideEffects? sideEffects,
SnapState<I18N> Function(SnapState<I18N> current, SnapState<I18N> next ) stateInterceptor,
DependsOn<I18N>? dependsOn,
int undoStackLength = 0,
bool isLazy = true,
String? debugPrintWhenNotifiedPreMessage,
})
It is a map between Locale
s and function that returns the corresponding translation Objects.
The function return is FutureOr of the translation object type. This means that we can return Futures.
Example:
final i18n = RM.injectI18N<EnUS>(
{
Locale('en', 'US'): ()=> EnUS();
Locale('es', 'Es'): () async {
String json = await rootBundle.loadString('lang/es_es.json');
return EsES.fromJson(json);
};
}
)
Notice here that translation can be obtained synchronously or asynchronously. Both ways are accepted. Even if you mix them, states_rebuilder will handle them appropriately.
It is the key to be used to locally store the state of the app's locale. If defined the app will store the chosen locale and retrieve it on app start.
You have to first implement the IPersistStore
or use the library states_rebuilder_storage
.
To make the state of the InjectedI18N
available to the widget tree, we use the TopStatelessWidget
.
import 'package:flutter_localizations/flutter_localizations.dart';
class MyApp extends TopStatelessWidget {
// This widget is the root of your application.
@override
void splashScreen() => MaterialApp(
home: Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
),
),
@override
Widget build(BuildContext context) {
return MaterialApp(
//Defining locale, localeResolutionCallback and localizationDelegates is more than enough for the app to get
//the right locale.
locale: i18n.locale,
localeResolutionCallback: i18n.localeResolutionCallback,
localizationsDelegates: i18n.localizationsDelegates,
//For more elaborate locale resolution algorithm use supportedLocales and
//localeListResolutionCallback.
// supportedLocales: i18n.supportedLocales,
// localeListResolutionCallback: (List<Locale>? locales, Iterable<Locale> supportedLocales){
// //your algorithm
// } ,
home: const HomePage(),//Notice const here
);
}
}
The splashScreen
parameter of TopStatelessWidget is optional and should be defined if the translation is fetched from an async source. If you forget to define onWaiting and some of your translations are async and an exception will be thrown.
In the widget tree, we use the i18n.of(context)
to obtain the translations.
Text(i18n.of(context).helloWorld);
Text(i18n.of(context).countTimes(count));
The of
method depends on an inherited widget, so even if your widget is declared const, it will rebuild when the app locale changes.
You can directly use i18n.state.helloWorld
and it will work provided you do not use const widgets that prevent the parent from rebuilding them.
To change locale use set the locale of the i18n state
i18n.locale = Locale('en');
i18n.locale = SystemLocale();
states_rebuilder search of an exact match for the locale, if don't find any, it searches for a locale with the same languageCode. If that fails, then the first element in selectionMap
is used.
SystemLocale is a class from states_rebuilder library. It extends the Locale class. It is used to represent the system locale.
Example:
PopupMenuButton<Locale>(
onSelected: (locale) {
//set the locale, the app will use the corresponding translation
i18n.locale = locale;
},
itemBuilder: (context) {
return [
//First item is for the system locale
PopupMenuItem(
value: SystemLocale(),
child: Text('Use system language'),
),
//Map throw all the supported locales
...i18n.supportedLocales.map(
(e) => PopupMenuItem(
value: locale,
child: Text('$locale'),
),
)
];
},
),
If the app is set to use the system locale, then it will look for the device system locale. and search if it finds an exact correspondence in the selectionMap
. If it does not find one, it looks for a locale with the same language code, and lastly, if it fails it takes the first locale in the selectionMap
.
When the device app locale changes, states_rebuilder observe it and change to the corresponding locale.
Use the getter supportedLocales
to get the support locales.
-
add
generate: true
topubspec.yaml
file# The following section is specific to Flutter. flutter: generate: true # Add this line
-
Create the
l10n.yaml
file In the root directory of your flutter application and add the following cotenant:# [arb-dire] is the directory where to put the language arb files arb-dir: lib/l10n # arb file to take as template. It contains translation as well as metadata template-arb-file: app_en.arb
-
create
lib/l10n
directory. Add put all your arb languages here. For example create:-
app_en.arb
for english (the template one) -
app_ar.arb
for arabic -
app_es.arb
for spanish
-
-
inside the
lib/l10n
folder create the filei18n.dart
where to useInjected18n
import 'package:states_rebuilder/states_rebuilder.dart'; // Generated file are in ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n import 'package:flutter_gen/gen_l10n/app_localizations_en.dart'; import 'package:flutter_gen/gen_l10n/app_localizations_ar.dart'; import 'package:flutter_gen/gen_l10n/app_localizations_es.dart'; final i18nRM = RM.injectI18N({ const Locale('en'): () => AppLocalizationsEn(), const Locale('ar'): () => AppLocalizationsAr(), const Locale('es'): () => AppLocalizationsEs(), });
Flutter generates the translation files inside
${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n
. For each language the generated class isAppLocalizations[LAN_CODE]
. To import it useimport 'package:flutter_gen/gen_l10n/app_localizations_[LAN_CODE].dart'
;For example:
- The language is
en
=> the generated class isAppLocalizationsEn
. To import:import 'package:flutter_gen/gen_l10n/app_localizations_en.dart';
- The language is
ar
=> the generated class isAppLocalizationsAr
. To import:import 'package:flutter_gen/gen_l10n/app_localizations_ar.dart';
- The language is
es
=> the generated class isAppLocalizationsEs
. To import:import 'package:flutter_gen/gen_l10n/app_localizations_es.dart';
- The language is
en_US
=> the generated class isAppLocalizationsEnUS
. To import:import 'package:flutter_gen/gen_l10n/app_localizations_en_us.dart';
- The language is
-
Use
TopStatelessWidget
in top of theMaterialApp
widget:class MyApp extends TopStatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return MaterialApp( locale: i18nRM.locale, localeResolutionCallback: i18nRM.localeResolutionCallback, localizationsDelegates: i18nRM.localizationsDelegates, home: const MyHomePage(), ); } }
Now your app is globalized.
arb
files in lib/l10n
follow the following naming convention: app_[LAN_CODE].arb
and it must contain:
arb { "@@locale": [LAN_CODE] }
- For english the name
app_en.arb
is contains:{ "@@locale": "en" }
- For arabic the name
app_ar.arb
is contains:{ "@@locale": "ar" }
- For spanish the name
app_es.arb
is contains:{ "@@locale": "es" }
Now you can add translation messages.
- For english:
{ "helloWorld": "Hello World!", }
- For arabic:
{ "helloWorld": "مرحبا بالجميع!", }
- For spanish:
{ "helloWorld": "¡Hola Mundo!", }
M
- For english:
{ "welcome": "Welcome {name}", "@welcome": { "placeholders": { "name": {} } }, }
- For arabic:
{ "welcome": "مرحبا {name}", }
- For spanish:
{ "welcome": "Hola {name}", }
- For english:
{ "gender": "{gender, select, male {Hi man!} female {Hi woman!} other {Hi there!}}", "@gender": { "placeholders": { "gender": {} } }, }
- For arabic:
{ "gender": "{gender, select, male {مرحبا يارجل!} female {مرحبا يامرأة!} other {مرحبا هناك!}}", }
- For spanish:
{ "gender": "{gender, select, male {Hola el hombre!} female {Hola la mujer!} other {Hola!}}", }
- For english:
{ "plural": "{howMany, plural, =1{1 message} other{{howMany} messages}}", "@plural": { "placeholders": { "howMany": {} } }, }
- For arabic:
{ "plural": "{howMany, plural,=0{صفر رسالة} =1{رسالة واحدة} 2{رسالاتان} few{{howMany} رسائل} other{{howMany} رسالة}}", }
- For spanish:
{ "plural": "{howMany, plural, =1{1 mensaje} other{{howMany} mensajes}}", }
- For english:
{ "formattedNumber": "The formatted number is: {value}", "@formattedNumber": { "placeholders": { "value": { "type": "int", "format": "compactLong" } } }, }
- For arabic:
{ "formattedNumber": "الرقم بعد التهيئة هو {value}", }
- For spanish:
{ "formattedNumber": "El número formateado es: {value}", }
- For english:
{ "date": "It is {date}", "@date": { "placeholders": { "date": { "type": "DateTime", "format": "yMMMMd" } } } }
- For arabic:
{ "date": "اليوم هو {date}" }
- For spanish:
{ "date": "Es {date}" }
For more information about the localization tool, such as dealing with DateTime and handling plurals, see the Internationalization User’s Guide.
Message “format” value | Output for formattedNumber(1200000) |
---|---|
"compact" | "1.2M" |
"compactCurrency" | "$1.2M" |
"compactSimpleCurrency" | "$1.2M" |
"compactLong" | "1.2 million" |
"currency"* | "USD1,200,000.00" |
"decimalPattern" | "1,200,000" |
"decimalPercentPattern" | "120,000,000%" |
"percentPattern" | "120,000,000%" |
"scientificPattern" | "1E6" |
"simpleCurrency" | "$1,200,000.00" |
ICU Name | Skeleton |
---|---|
DAY | d |
ABBR_WEEKDAY | E |
WEEKDAY | EEEE |
ABBR_STANDALONE_MONTH | LLL |
STANDALONE_MONTH | LLLL |
NUM_MONTH | M |
NUM_MONTH_DAY | Md |
NUM_MONTH_WEEKDAY_DAY | MEd |
ABBR_MONTH | MMM |
ABBR_MONTH_DAY | MMMd |
ABBR_MONTH_WEEKDAY_DAY | MMMEd |
MONTH | MMMM |
MONTH_DAY | MMMMd |
MONTH_WEEKDAY_DAY | MMMMEEEEd |
ABBR_QUARTER | QQQ |
QUARTER | QQQQ |
YEAR | y |
YEAR_NUM_MONTH | yM |
YEAR_NUM_MONTH_DAY | yMd |
YEAR_NUM_MONTH_WEEKDAY_DAY | yMEd |
YEAR_ABBR_MONTH | yMMM |
YEAR_ABBR_MONTH_DAY | yMMMd |
YEAR_ABBR_MONTH_WEEKDAY_DAY | yMMMEd |
YEAR_MONTH | yMMMM |
YEAR_MONTH_DAY | yMMMMd |
YEAR_MONTH_WEEKDAY_DAY | yMMMMEEEEd |
YEAR_ABBR_QUARTER | yQQQ |
YEAR_QUARTER | yQQQQ |
HOUR24 | H |
HOUR24_MINUTE | Hm |
HOUR24_MINUTE_SECOND | Hms |
HOUR | j |
HOUR_MINUTE | jm |
HOUR_MINUTE_SECOND | jms |
HOUR_MINUTE_GENERIC_TZ | jmv |
HOUR_MINUTE_TZ | jmz |
HOUR_GENERIC_TZ | jv |
HOUR_TZ | jz |
MINUTE | m |
MINUTE_SECOND | ms |
SECOND | s |