'How to enforce a new property has a value set at compile time in dart?

I am new to dart/flutter and I am adding internationalization on my app.

As flutter don't define a proper way to implement internationalization (according to the video I watched), devs are free to do it in any way they think its better.

The video showed an implementation using json files. So on "lang" folder I have my json files with all strings and their respective translations.

lang folder en.json pt.json

Now imagine this is a large file with lots of entries. And I have to make a big change on my app, removing some strings, adding others and adding a new language. This is very error prone as I may forget to add some key-value entry in any of the json files. It happened sometimes and the error is thrown at runtime.

In java there is a way to enforce the dev don't forget to add the same values in all variations of the same property at compile time. Maybe it's not the best way to achieve this, but one way is using an enum like in the image below.

enum with values in java

Now if I want to add a new language, I just add a new parameter in the enum constructor and there is no way to forget adding a translation because the code would not even compile.

new language on enum

To fix the error I just have to add the value on the Strings (I forgot the "es" case here):

enum error fix

And then I can use the values doing something like this (the code to find the locale using the context could even be moved to inside the .value() method):

using enum values

So is there a good way to achieve this in dart/flutter? To enforce all the languages have all the translated texts at compile time?



Solution 1:[1]

My method looks like this:

First, define the interface, and its implementations:

abstract class StringsInterface {
  String get greet;
}

class EnglishStrings implements StringsInterface {
  String get greet => "Hello";
}

class SpanishStrings implements StringsInterface {
  String get greet => "Hola";
}

Next, define a class to store an instance of the current locale, as well as configure which class to use, for which language code:

class Strings {
  static StringsInterface current = EnglishStrings();
  static late Locale _locale;

  static Locale get locale => _locale;

  static set locale(Locale locale) {
    _locale = locale;
    current = stringsForLocale(locale);
  }

  static StringsInterface stringsForLocale(Locale locale) {
    switch(locale.languageCode) {
      // Here is where you define which class gets used for which language code
      case 'es': return SpanishStrings();
      default: return EnglishStrings();
    }
  }
}

Set the locale for the Strings class at runtime:

void main() async {
  /// load from your own future 
  /// (e.g. pull from a persisted value stored on device in SharedPreferences/UserDefaults)
  Strings.locale = Locale(await Future<String>.value('en'));
  runApp(const MyApp());
}

Finally, use like this:

Text(Strings.current.greet)

Solution 2:[2]

As of dart 2.17 you can now use enhanced enums with members:

enum Strings {
  welcome(en: 'Welcome', pt: 'Bem-vindo'),
  goodbye(en: 'Good bye', pt: 'Adeus');

  final String en;
  final String pt;

  const Strings({
    required this.en,
    required this.pt,
  });

  String value(String lang) {
    switch (lang) {
      case 'pt':
        return pt;
      case 'en':
      default:
        return en;
    }
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jared Anderton
Solution 2