flutter internationalization using dynamic string in json file
Asked Answered
S

4

7

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?

Subsumption answered 25/5, 2020 at 6:59 Comment(0)
I
10

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;
}
Informer answered 19/6, 2020 at 10:6 Comment(0)
E
7
  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();
  }
}
Edholm answered 13/6, 2020 at 16:4 Comment(1)
Thank you for your reply. The problem is that I need the placeholder of the value inside the resource file (because this is the file I send to the translator). A good solution I found is using the easy_localization package. I succeed with it to translate from json file but not from an XML file. I opened a new post on this: linkSubsumption
C
4

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.

Coventry answered 19/6, 2020 at 8:47 Comment(0)
L
3

flutter_localizations:

app_en.arb:

{
  "contactDetailsPopupEmailCopiedMessage": "Copied {email} to clipboard",
  "@contactDetailsPopupEmailCopiedMessage": {
    "description": "Message being displayed in a snackbar upon long-clicking email in contact details popup",
    "placeholders": {
      "email": {
        "type": "String",
        "example": "[email protected]"
      }
    }
  }
}

app_localizations.dart:

abstract class AppLocalizations {
  /// Message being displayed in a snackbar upon long-clicking email in contact details popup
  ///
  /// In en, this message translates to:
  /// **'Copied {email} to clipboard'**
  String contactDetailsPopupEmailCopiedMessage(String email);
}

usage:

l10n.contactDetailsPopupEmailCopiedMessage("[email protected]")

For more details take a look here.

Linell answered 26/4, 2021 at 20:7 Comment(2)
What about multiple dynamic args in a single string?Continuum
Cool, you can do that by adding multiple arguments...Continuum

© 2022 - 2024 — McMap. All rights reserved.