Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

flutter internationalization using dynamic string in json file

So far I was using dynamic strings as shown in the solution of this post: Flutter internationalization - Dynamic strings

Here's an example:

AppLocalizations.of(context).userAge(18)

And on AppLocalizations.dart:

userAge(age) => Intl.message(
  "My age is $age",
  name: "userAge",
  args: [age]);
// Return "My age is 18"

But then I read this article about flutter internationalization: https://medium.com/flutter-community/flutter-internationalization-the-easy-way-using-provider-and-json-c47caa4212b2 Which shows how to localize using json files as resource files for the strings. It looks way more convenient so I prefer to use this method, but don't know how to get strings from json file with dynamic values.

Any solution?

like image 936
Simple UX Apps Avatar asked May 25 '20 06:05

Simple UX Apps


3 Answers

To get the string with the dynamic value from JSON file you can use

final age = 18 //user input.
final ageString = 'user_age'
                   .localisedString()
                   .replaceAll(new RegExp(r'\${age}'), age)

en.json

{
  "user_age": "My age is ${age}",
  "user_name_age": "My name is ${name} and age is ${age}"
}

string_extension.dart

extension Localisation on String {
  String localisedString() {
    return stringBy(this) ?? '';
  }
}

Also, you could do something like,

  String localisedString(Map<String, String> args) {
      String str = localisedString();
      args.forEach((key, value) { 
        str = str.replaceAll(new RegExp(r'\${'+key+'}'), value);
      });
      return str;
  }
  
  //usecase
  final userName = 'Spider Man'
  final age = '18'
  final nameAgeString = 'user_name_age'.localisedString({'name': userName, 'age': age})

app_localisation.dart

Map<String, dynamic> _language;

String stringBy(String key) => _language[key] as String ?? 'null';

class AppLocalisationDelegate extends LocalizationsDelegate {
  const AppLocalisationDelegate();

  // override the following method if you want to specify the locale you are supporting.
  final _supportedLocale = ['en'];
  @override
  bool isSupported(Locale locale) => _supportedLocale.contains(locale.languageCode);

  @override
  Future load(Locale locale) async {
    String jsonString = await rootBundle
        .loadString("assets/strings/${locale.languageCode}.json");

    _language = jsonDecode(jsonString) as Map<String, dynamic>;
    print(_language.toString());
    return SynchronousFuture<AppLocalisationDelegate>(
        AppLocalisationDelegate());
  }

  @override
  bool shouldReload(AppLocalisationDelegate old) => false;
}
like image 91
Tapas Pal Avatar answered Oct 16 '22 10:10

Tapas Pal


  1. Create a folder say json in your assets directory. Put your language files in it.

    assets
      json
       - en.json // for English 
       - ru.json  // for Russian
    
  2. Now in en.json, write your string, for example.

    {
      "myAge": "My age is"
    }
    

    Similarly, in ru.json,

    {
      "myAge": "Мой возраст"
    }
    
  3. Add this to the pubspec.yaml file (mind the spaces)

    flutter:
    
      uses-material-design: true
    
      assets:
        - assets/json/
    

    Run flutter pub get


Initial work done. Let's move to the code side.

Copy this boilerplate code in your file:

Map<String, dynamic> language;

class AppLocalizations {
  static AppLocalizations of(BuildContext context) {
    return Localizations.of<AppLocalizations>(context, AppLocalizations);
  }

  String getText(String key) => language[key];

  String userAge(int age) => '${getText('myAge')} $age';
}

class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
  const AppLocalizationsDelegate();

  @override
  bool isSupported(Locale locale) => ['en', 'ru'].contains(locale.languageCode);

  @override
  Future<AppLocalizations> load(Locale locale) async {
    final string = await rootBundle.loadString('assets/json/${locale.languageCode}.json');
    language = json.decode(string);
    return SynchronousFuture<AppLocalizations>(AppLocalizations());
  }

  @override
  bool shouldReload(AppLocalizationsDelegate old) => false;
}

Set up few things in MaterialApp widget:

void main() {
  runApp(
    MaterialApp(
      locale: Locale('ru'), // switch between "en" and "ru" to see effect
      localizationsDelegates: [const AppLocalizationsDelegate()],
      supportedLocales: [const Locale('en'), const Locale('ru')],
      home: HomePage(),
    ),
  );
}

Now, you can simply use above delegate:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var age = AppLocalizations.of(context).userAge(18);
    // prints "My age is 18" for 'en' and "Мой возраст 18" for 'ru' locale. 
    print(age); 

    return Scaffold();
  }
}
like image 7
CopsOnRoad Avatar answered Oct 16 '22 08:10

CopsOnRoad


I've tried various solutions to implement localization, and the best I've come across is the Flutter Intl plugin for VS Code or Android Studio/IntelliJ made by Localizely.com (not affiliated).

With it, basically you install the plugin using the marketplace/plugin library, then initialize for your project using the menu option. This creates a default english locale in lib/l10n/intl_en.arb (which sounds scary but is actually just JSON) and sets up all the scaffolding for the internationalization in lib/generated.

You also have to add the following to your dependencies.

flutter_localizations:
    sdk: flutter

You can then add keys to this file and they'll be automatically available in your app, by importing generated/l10n.dart which contains a class called S.

To get flutter to use it, wherever it is that you initialize your MaterialApp, make sure to pass S.delegate into MaterialApp's localizationsDelegates parameter (most likely as part of an array with GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, and possibly GlobalCupertinoLocalizations.delegate.) You also have to add S.delegate.supportedLocales to MaterialApp's supportedLocales.

To add more locales, use the option in the menu (in intellij at least) or simply create more intl_.arb files, and the plugin will automatically recognize this and set up the relevant code.

Say you have an intl_en file with the following:

{ "name": "Name" }

You'd then use S.of(context).name to use the string in your code.

All this is more eloquently explained on localizely's website.


Now, to use keys in these .arb files, you simply have to wrap it in {...}. So for example:

{ "choose1OfNumOptions": "Choose 1 of {numoptions} options" }

would lead to a usage of S.of(context).choose1OfNumOptions(numOptions);. I don't know that the plugin supports the full ARB specification but it does support at least the basics.

Also, I'm not using Localizely but it seems like it'd be a pretty useful way to manage the translations and the plugin integrates automatically, although I think it's also pretty horrendously overpriced - at least for my app, which happens to have a ton of text. I actually just have a google sheet where I store all my translations, and when it's time to update it I download it as a .tsv and wrote a simple little parser to write out to the .arb files.

like image 4
rmtmckenzie Avatar answered Oct 16 '22 09:10

rmtmckenzie