Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Localizations without calling `of(context)` every time

Tags:

flutter

dart

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?

like image 231
Magnus Avatar asked Jun 19 '19 20:06

Magnus


4 Answers

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.

like image 141
Rémi Rousselet Avatar answered Nov 19 '22 04:11

Rémi Rousselet


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.

like image 41
creativecreatorormaybenot Avatar answered Nov 19 '22 04:11

creativecreatorormaybenot


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,
    );
  }
}
like image 45
jithin reddy Avatar answered Nov 19 '22 04:11

jithin reddy


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)
like image 36
Fleximex Avatar answered Nov 19 '22 05:11

Fleximex