16

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?

7 Answers 7

12

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.

Sign up to request clarification or add additional context in comments.

1 Comment

But how do you manage situations, where you are steering logic with translations inside of your bloc/cubits where you would like to access the context for translation but you cannot?
6

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.

2 Comments

Thanks! I noticed that there is a didChangeLocales() method as well - would it be better to use that than didChangeDependencies()?
@MagnusW You could use a combination of both. I added what I mean to my answer.
6

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:

  • The locale changed
  • The LocalizationsDelegate obtained was asynchronously loaded
  • MaterialApp/CupertinoApp got updated with new translations

If you're not properly calling Localization.of inside build as you should, then in those scenarios your UI may fail to properly update.

4 Comments

Thanks, this is what I was suspecting. But let's say I don't care about changing the language of my app (without the need of a restart) when the user changes the device locale, is there still any danger of keeping a global reference to the Localization instance? After all, I'd bet that most users don't change their locale after setting up a device for the first time...
Yes. Like I said, that can also be needed when the translations are loaded asynchronously.
I'm trying to do localizations, but there are problems when I want to get the strings oninitState which is before the context is made . I also have several service objects which work on user selected translated menu button strings. or return strings that need to be translated. I'm thinking of switching to easy localization
Unfortunately, you forget that there is a need for localization even outside the usual UI. Background services, various event handlers, notfications, situations without a direct UI.
3

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) 

1 Comment

I like these suggestions, but the first several utilize a translate method that does not exist in the intl API as far as I can find. Is this meant to be a placeholder for a custom function that can handle dynamic keys?
1

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, ); } } 

Comments

1

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; } } 

Comments

1

You can try create extensions for context:

extension BuildContextExt on BuildContext { AppLocalizations get l10n => AppLocalizations.of(this)!; } 

And then use:

context.l10n 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.