I find the localization procedure using the official Flutter localization plugin cumbersome. To display a localized string I have to call AppLocalizations.of(context).myAppTitle
- not exactly sleek or easy to glance over in a huge nested Widget tree with lots of localized strings. Not to mention it looks ugly.
Is there a way to make the usage nicer? For example, can I use a global variable or a static class with a AppLocalizations
instance member to make the access easier? For example declaring a top level AppLocalizations
variable
// Somewhere in the global scope
AppLocalizations l;
// main.dart
class _MyAppState extends State<MyApp>{
@override
void initState() {
super.initState();
getLocaleSomehow().then((locale){
l = Localization(locale);
setState((){});
});
}
}
Then I could simply call
Text(l.myAppTitle)
So in an essence what I'm asking is "what are the dangers and/or disadvantages of not calling AppLocalizations.of(context)
?"
If I really do need to use the .of(BuildContext)
method to access the AppLocalizations
instance - can I at least store it in my StatefulWidget
? I'm thinking something like
class DetailsPage extends StatefulWidget{
AppLocalizations _l;
@override
Widget build(BuildContext context) {
_l = AppLocalizations.of(context);
// ... build widgets ...
}
}
Or is there any other way to make the localization less cumbersome?
Yes, it is needed. You could work around it, but that is a bad idea.
The reason for this is that Localization.of<T>(context, T)
may update over time. A few situations where it does are:
If you're not properly calling Localization.of
inside build as you should, then in those scenarios your UI may fail to properly update.
It is totally fine to store the Localization
object inside of your State
and it works very well in that case.
If you want to only make it look nicer, you could also just declare the variable in the build method:
@override
Widget build(BuildContext context) {
final l = Localization.of(context);
return Text(l.myAppTitle);
}
In a StatefulWidget
, you could also re-assign the variable in didChangeDependencies
or just assign it once using the null-aware ??=
operator because the object will not change over time:
class _MyStatefulWidgetState extends State<MyStatefulWidget> with WidgetsBindingObserver {
Localization l;
@override
didChangeDependencies() {
WidgetsBinding.instance.addObserver(this);
l ??= Localization.of(context);
super.didChangeDependencies();
}
@override
void didChangeLocales(List<Locale> locale) {
l = Localization.of(context);
super.didChangeLocales(locale);
}
@override
dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) => Text(l.myAppTite);
}
In didChangeLocales
, you can re-assign every time. This makes sure that the variable always holds the appropriate locale and is initialized at first build (with didChangeDependencies
). Notice that I also included a WidgetsBindingObserver
, which you need to handle as shown in the code.
You can create your own text widget and do localization there.You can replace all your text widgets with your own MyText widget.
class MyText extends StatelessWidget {
String data;
InlineSpan textSpan;
TextStyle style;
StrutStyle strutStyle;
TextAlign textAlign;
TextDirection textDirection;
Locale locale;
bool softWrap;
TextOverflow overflow;
double textScaleFactor;
int maxLines;
String semanticsLabel;
TextWidthBasis textWidthBasis;
MyText(
this.data, {
Key key,
this.style,
this.strutStyle,
this.textAlign,
this.textDirection,
this.locale,
this.softWrap,
this.overflow,
this.textScaleFactor,
this.maxLines,
this.semanticsLabel,
this.textWidthBasis,
});
@override
Widget build(BuildContext context) {
return Text(
Localization.of(context).data,
style: style,
semanticsLabel: semanticsLabel,
locale: locale,
key: key,
textAlign: textAlign,
maxLines: maxLines,
overflow: overflow,
softWrap: softWrap,
strutStyle: strutStyle,
textDirection: textDirection,
textScaleFactor: textScaleFactor,
textWidthBasis: textWidthBasis,
);
}
}
By using Flutter extensions you can now simply extend The StatelessWidget
and StatefulWidget
, or the generic Widget
to provide a translate method. Two different solutions:
1. on Widget
extension TranslationHelper on Widget {
String tr(BuildContext context, String key) {
return AppLocalizations.of(context).translate(key);
}
}
Then in the build
method of a StatelessWidget
you can call tr(context, 'title')
. For the build
method of a StatefulWidget
you have to call widget.tr(context, 'title')
.
2. on StatelessWidget
and StatefulWidget
For a more consistent calling of the translate function you can extend StatelessWidget
and StatefulWidget
, respectively:
extension TranslationHelperStateless on StatelessWidget {
String tr(BuildContext context, String key) {
return AppLocalizations.of(context).translate(key);
}
}
extension TranslationHelperStateful<T extends StatefulWidget> on State<T> {
String tr(BuildContext context, String key) {
return AppLocalizations.of(context).translate(key);
}
}
For both build
methods in StatelessWidget
and StatefulWidget
you can call:
tr(context, 'title')
With StatefulWidget
there is one risk as a developer you need to avoid. Which is calling the tr()
method in a place where you can access context
but where the build
method has not ran yet, like initState
. Make sure to call tr()
always in the build
methods.
3. on StatelessWidget
and StatefulWidget
, but not using the translate
method
You can extend StatelessWidget
and StatefulWidget
and return the AppLocalizations
, like this:
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
extension TranslationHelperStateless on StatelessWidget {
AppLocalizations tr(BuildContext context) {
return AppLocalizations.of(context)!;
}
}
extension TranslationHelperStateful<T extends StatefulWidget> on State<T> {
AppLocalizations tr(BuildContext context) {
return AppLocalizations.of(context)!;
}
}
For both build
methods in StatelessWidget
and StatefulWidget
you can call:
tr(context).title
or
tr(context).helloUser(name)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With