Florian
Florian

Reputation: 659

Flutter 1.22 Internationalization with variable as key

I implemented the new (official) localization for Flutter (https://pascalw.me/blog/2020/10/02/flutter-1.22-internationalization.html) and everything is working fine, except that I don't know how to get the translation for a variable key.

The translation is in the ARB file, but how can I access it?

Normally I access translations using Translations.of(context).formsBack, but now I would like to get the translation of value["labels"]["label"].

Something like Translations.of(context).(value["labels"]["label"]) does not work of course.

Upvotes: 5

Views: 3338

Answers (5)

Jose Solorzano
Jose Solorzano

Reputation: 470

You can implement your own localization class where translations are looked up by key, so you don't have to rely on the gen-l10n tool. You can add your keys to json files under assets/resources. Make sure you declare your assets in pubspec.yaml:

flutter:
  assets:
    - assets/images/
    - assets/resources/

You custom class could look like this:

import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart' show rootBundle;

class CustomLocalization {
  final Locale _locale;
  final Map<String, String> _entries;
  static final CustomLocalizationDelegate delegate =
      CustomLocalizationDelegate();

  CustomLocalization(this._locale, this._entries);

  static CustomLocalization? of(BuildContext context) {
    return Localizations.of<CustomLocalization>(context, CustomLocalization);
  }

  String get(String key) {
    return _entries[key] ?? "??:$key";
  }
}

class CustomLocalizationDelegate
    extends LocalizationsDelegate<CustomLocalization> {
  @override
  bool isSupported(Locale locale) =>
      <String>['en', 'es'].contains(locale.languageCode);

  @override
  bool shouldReload(CustomLocalizationDelegate old) => false;

  @override
  Future<CustomLocalization> load(Locale locale) async {
    String language = locale.languageCode;
    // Load translations from assets i18n_en.json or i18n_es.json
    String json = await rootBundle.loadString("resources/i18n_$language.json");
    Map<String, String> entries = Map.castFrom(jsonDecode(json));
    return CustomLocalization(locale, entries);
  }
}

Use CustomLocalization.delegate and CustomLocalization.of() in a similar way as you use AppLocalizations.

Upvotes: 0

Jose Solorzano
Jose Solorzano

Reputation: 470

If you try to apply the JSON solution provided but you get this error:

[app_en.arb:languages] ICU Lexing Error: Unexpected character. {"en": "English", "es": "Spanish"}

First add the following to l10n.yaml:

use-escaping: true

Then define your key as follows:

"languages": "'{'\"en\": \"English\", \"es\": \"Spanish\"'}'"

Upvotes: 0

Răzvan Puiu
Răzvan Puiu

Reputation: 731

To provide an example for https://stackoverflow.com/users/5638943/kristi-jorgji 's answer (which works fine):

app_en.arb ->

 {
     "languages": "{\"en\": \"English\", \"ro\": \"Romanian\"}"
 }

localization_controller.dart ->

  String getLocaleName(BuildContext ctx, String languageCode) {
    return jsonDecode(AppLocalizations.of(ctx)!.languages)[languageCode];
  }

getLocaleName(context, 'ro') -> "Romanian"

Upvotes: 2

Kristi Jorgji
Kristi Jorgji

Reputation: 1731

You can store a key in translation as json string. Then you read it, parse it to Map<string,string> and access dynamically what you need.

Been using this approach with great success

Upvotes: 0

pascalw
pascalw

Reputation: 76

I don't think this is possible with gen_l10n. The code that is generated by gen_l10n looks like this (somewhat abbreviated):

/// The translations for English (`en`).
class TranslationsEn extends Translations {
  TranslationsEn([String locale = 'en']) : super(locale);

  @override
  String get confirmDialogBtnOk => 'Yes';

  @override
  String get confirmDialogBtnCancel => 'No';
}

As you can see it doesn't generate any code to perform a dynamic lookup.

For most cases code generation like this is a nice advantage since you get auto completion and type safety, but it does mean it's more difficult to accommodate these kinds of dynamic use cases.

The only thing you can do is manually write a lookup table, or choose another i18n solution that does support dynamic lookups.

A lookup table could look something like this. Just make sure you always pass in the current build context, so the l10n code can lookup the current locale.

class DynamicTranslations {
  String get(BuildContext context, String messageId) {
    switch(messageId) {
      case 'confirmDialogBtnOk':
        return Translations.of(context).confirmDialogBtnOk;
      case 'confirmDialogBtnCancel':
        return Translations.of(context).confirmDialogBtnCancel;
      default:
        throw Exception('Unknown message: $messageId');
    }
  }
}

Upvotes: 6

Related Questions