Reputation: 18728
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?
Upvotes: 16
Views: 18964
Reputation: 21
You can try create extensions for context:
extension BuildContextExt on BuildContext {
AppLocalizations get l10n => AppLocalizations.of(this)!;
}
And then use:
context.l10n
Upvotes: 1
Reputation: 477
12/06/2023
Remember that the AppLocalizations is an abstract class. The classes implementing that abstract class are each of your laguages defined on the .arb files. For example I have an app_en.arb (for english) and an app_es.arb (for spanish), thus, the implementing classes are AppLocalizationsEn and AppLocalizationsEs.
In my case I save the Locale language code on the shared preferences (if there's no value saved yet I take the english language code as default):
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_gen/gen_l10n/app_localizations_en.dart';
import 'package:flutter_gen/gen_l10n/app_localizations_es.dart';
void function(){
AppLocalizations appLocalizations;
String? localeLanguageCode =
businessPreferences.getString("languageKey");
switch (localeLanguageCode) {
case "en":
appLocalizations = AppLocalizationsEn();
break;
case "es":
appLocalizations = AppLocalizationsEs();
break;
default:
appLocalizations = AppLocalizationsEn();
break;
}
}
Upvotes: 1
Reputation: 5780
I combined some of the info from the other responses here (specialy Fleximex's) to a solution which I found quite interesting - and that's the one I'm using. I created an extension on the BuildContext
itself.
extension BuildContextHelper on BuildContext {
AppLocalizations get l {
// if no locale was found, returns a default
return AppLocalizations.of(this) ?? AppLocalizationsEn();
}
}
Using this extension (and importing it), one can use it like this:
context.l.appTitle
or
context.l.helloUser(name)
IMHO it's clean and readable, it's not the shortest though.
Upvotes: 11
Reputation: 121
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)
Upvotes: 3
Reputation: 11
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,
);
}
}
Upvotes: 1
Reputation: 126564
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.
Upvotes: 6
Reputation: 276881
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.
Upvotes: 6