Translation and Internationalization (i18n) for Flutter

i18n_extension .Non-boilerplate Translation and Internationalization (i18n) for Flutter

Start with a widget with some text in it:

Text("Hello, how are you?")

Translate it simply by adding .i18n to the string:

Text("Hello, how are you?".i18n)

Or you can also use identifiers, as you prefer:

Text(greetings.i18n)

If the current locale is 'pt_BR', then the text in the screen will be "Olá, como vai você?", the Portuguese translation to the above text. And so on for any other locales you want to support.

You can also provide different translations depending on modifiers, for example plural quantities:

print("There is 1 item".plural(0)); // Prints 'There are no items'
print("There is 1 item".plural(1)); // Prints 'There is 1 item'
print("There is 1 item".plural(2)); // Prints 'There are 2 items'

And you can invent your own modifiers according to any conditions. For example, some languages have different translations for different genders. So you could create gender versions for Gender modifiers:

print("There is a person".gender(Gender.male)); // Prints 'There is a man'
print("There is a person".gender(Gender.female)); // Prints 'There is a woman'
print("There is a person".gender(Gender.they)); // Prints 'There is a person'

Also, interpolating strings is easy, with the fill method:

// Prints 'Hello John, this is Mary' in English.
// Prints 'Olá John, aqui é Mary' in Portuguese.
print("Hello %s, this is %s".i18n.fill(["John", "Mary"]));

See it working

Try running the example.

Good for simple or complex apps

I'm always interested in creating packages to reduce boilerplate. For example, async_redux is about Redux without boilerplate, and align_positioned is about creating layouts using fewer widgets. This current package is also about reducing boilerplate for translations, so it doesn't do anything you can't already do with plain old Localizations.of(context).

That said, this package is meant both for the one person app developer, and the big company team. It has you covered in all stages of your translation efforts:

When you create your widgets, it makes it easy for you to define which strings should be translated, by simply adding .i18n to them. These strings are called "translatable strings".

When you want to start your translation efforts, it can automatically list for you all strings that need translation. If you miss any strings, or if you later add more strings or modify some of them, it will let you know what changed and how to fix it.

You can then provide your translations manually, in a very easy-to-use format.

Or you can easily integrate it with professional translation services, importing it from, or exporting it to any format you want.

Setup

Wrap your widget tree with the I18n widget, below the MaterialApp, together with the localizationsDelegates and the supportedLocales:

import 'package:i18n_extension/i18n_widget.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

...

@override
Widget build(BuildContext context) {
  return MaterialApp(
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        const Locale('en', "US"),
        const Locale('pt', "BR"),
      ],
      home: I18n(child: ...)
  );
}

Note: To be able to import flutter_localizations.dart you must add this to your pubspec.yaml:

dependencies:
  flutter_localizations:
    sdk: flutter

  i18n_extension: ^4.1.0

The code home: I18n(child: ...) shown above will translate your strings to the current system locale. Or you can override it with your own locale, like this:

I18n(
  initialLocale: Locale("pt", "BR"),
  child: ...

Note: Don't ever put translatable strings in the same widget where you declared the I18n widget, since they may not respond to future locale changes. For example, this is a mistake:

Widget build(BuildContext context) {
  return I18n(
    child: Scaffold(
      appBar: AppBar(title: Text("Hello there".i18n)),
      body: MyScreen(),
  );
}

You may put translatable strings in any widgets down the tree.

A quick recap of Dart locales

The correct way to create a Locale is to provide a language code (usually 2 or 3 lowercase letters) and a country code (usually 2 uppercase letters), as two separate Strings.

For example:

var locale = Locale("en", "US");

print(locale); // Prints `en_US`.
print(locale.languageCode); // Prints `en`.

You can, if you want, omit the country code:

var locale = Locale("en");

print(locale); // Prints `en`.
print(locale.languageCode); // Prints `en`.

But you cannot provide language code and country code as a single String. This is wrong:

// This will create a language called "en_US" and no country code.
var locale = Locale("en_US");

print(locale); // Prints `en_US`.
print(locale.languageCode); // Also prints `en_US`.

To help avoiding this mistake, the i18n_extension may throw an error if your language code contains underscores.

Translating a widget

When you create a widget that has translatable strings, add this default import to the widget's file:

import 'package:i18n_extension/default.i18n.dart';

This will allow you to add .i18n and .plural() to your strings, but won't translate anything.

When you are ready to create your translations, you must create a dart file to hold them. This file can have any name, but I suggest you give it the same name as your widget and change the termination to .i18n.dart.

For example, if your widget is in file my_widget.dart, the translations could be in file my_widget.i18n.dart

You must then remove the previous default import, and instead import your own translation file:

import 'my_widget.i18n.dart';

Your translation file itself will be something like this:

import 'package:i18n_extension/i18n_extension.dart';

extension Localization on String {

  static var _t = Translations("en_us") +
    {
      "en_us": "Hello, how are you?",
      "pt_br": "Olá, como vai você?",
      "es": "¿Hola! Cómo estás?",
      "fr": "Salut, comment ca va?",
      "de": "Hallo, wie geht es dir?",
    };

  String get i18n => localize(this, _t);
}

The above example shows a single translatable string, translated to American English, Brazilian Portuguese, and general Spanish, French and German.

You can, however, translate as many strings as you want, by simply adding more translation maps:

import 'package:i18n_extension/i18n_extension.dart';

extension Localization on String {

    static var _t = Translations("en_us") +
        {
          "en_us": "Hello, how are you?",
          "pt_br": "Olá, como vai você?",
        } +
        {
          "en_us": "Hi",
          "pt_br": "Olá",
        } +
        {
          "en_us": "Goodbye",
          "pt_br": "Adeus",
        };

  String get i18n => localize(this, _t);
}

Strings themselves are the translation keys

The locale you pass in the Translations() constructor is called the default locale. For example, in Translations("en_us") the default locale is en_us. All translatable strings in the widget file should be in the language of that locale.

The strings themselves are used as keys when searching for translations to the other locales. For example, in the Text below, "Hello, how are you?" is both the translation to English, and the key to use when searching for its other translations:

Text("Hello, how are you?".i18n)

If any translation key is missing from the translation maps, the key itself will be used, so the text will still appear in the screen, untranslated.

If the translation key is found, it will choose the language according to the following rules:

It will use the translation to the exact current locale, for example en_us.

If this is absent, it will use the translation to the general language of the current locale, for example en.

If this is absent, it will use the translation to any other locale with the same language, for example en_uk.

If this is absent, it will use the value of the key in the default language.

If this is absent, it will use the key itself as the translation.

Try running the example using strings as translation keys.

Or you can, instead, use identifiers as translation keys

Instead of:

"Hello there".i18n

You can also do:

greetings.i18n

To that end, you can use the Translations.from constructor:

const appbarTitle = "appbarTitle";
const greetings = "greetings";

extension Localization on String {
    
  static final _t = Translations.from("en_us", {
    appbarTitle: {
      "en_us": "i18n Demo",
      "pt_br": "Demonstração i18n",
    },
    greetings: {
      "en_us": "Helo there",
      "pt_br": "Olá como vai",
    },    
  });

  String get i18n => localize(this, _t);    
}

Try running the example using identifiers as translation keys.

Managing keys

Other translation packages force you to define identifier keys for each translation, and use those. For example, an identifier key could be helloHowAreYou or simply greetings. And then you could access it like this: MyLocalizations.of(context).greetings.

However, having to define identifiers is not only a boring task, but it also makes it more difficult to remember the exact text of the widget.

With i18n_extension you can still use identifier keys, but you can also simply type the text you want and add .i18n to them.

If some string is already translated, and you later change it in the widget file, this will break the link between the key and the translation map. However, i18n_extension is smart enough to let you know when that happens, so it's easy to fix. You can even add this check to tests, as to make sure all translations are linked and complete.

When you run your app or tests, each key not found will be recorded to the static set Translations.missingKeys. And if the key is found but there is no translation to the current locale, it will be recorded to Translations.missingTranslations.

You can manually inspect those sets to see if they're empty, or create tests to do that automatically, for example:

expect(Translations.missingKeys, isEmpty);
expect(Translations.missingTranslations, isEmpty);

Note: You can disable the recording of missing keys and translations by doing:

Translations.recordMissingKeys = false;
Translations.recordMissingTranslations = false;

Another thing you may do, if you want, is to set up callbacks that the i18n_extension package will call whenever it detects a missing translation. You can then program these callbacks to throw errors if any translations are missing, or log the problem, or send emails to the person responsible for the translations.

To do that, simply inject your callbacks into Translations.missingKeyCallback and Translations.missingTranslationCallback.

For example:

Translations.missingTranslationCallback =
  (key, locale) =>
    throw TranslationsException("There are no translations in '$locale' for '$key'.");

Defining translations by locale instead of by key

As explained, by using the Translations() constructor you define each key and then provide all its translations at the same time. This is the easiest way when you are doing translations manually, for example, when you speak English and Spanish and are creating yourself the translations to your app.

However, in other situations, such as when you are hiring professional translation services or crowdsourcing translations, it may be easier if you can provide the translations by locale/language, instead of by key. You can do that by using the Translations.byLocale() constructor.

static var _t = Translations.byLocale("en_us") +
    {
      "en_us": {
        "Hi.": "Hi.",
        "Goodbye.": "Goodbye.",
      },
      "es_es": {
        "Hi.": "Hola.",
        "Goodbye.": "Adiós.",
      }
    };

You can also add maps using the + operator:

static var _t = Translations.byLocale("en_us") +
    {
      "en_us": {
        "Hi.": "Hi.",
        "Goodbye.": "Goodbye.",
      },
    } +
    {
      "es_es": {
        "Hi.": "Hola.",
        "Goodbye.": "Adiós.",
      }
    };

Note above, since "en_us" is the default locale you don't need to provide translations for those.

Combining translations

To combine translations you can use the * operator. For example:

var t1 = Translations("en_us") +
    {
      "en_us": "Hi.",
      "pt_br": "Olá.",
    };

var t2 = Translations("en_us") +
    {
      "en_us": "Goodbye.",
      "pt_br": "Adeus.",
    };

var translations = t1 * t2;

print(localize("Hi.", translations, locale: "pt_br");
print(localize("Goodbye.", translations, locale: "pt_br");

Translation modifiers

Sometimes you have different translations that depend on a number quantity. Instead of .i18n you can use .plural() and pass it a number. For example:

int numOfItems = 3;
return Text("You clicked the button %d times".plural(numOfItems));

This will be translated, and if the translated string contains %d it will be replaced by the number.

Then, your translations file should contain something like this:

static var _t = Translations("en_us") +
  {
    "en_us": "You clicked the button %d times"
        .zero("You haven't clicked the button")
        .one("You clicked it once")
        .two("You clicked a couple times")
        .many("You clicked %d times")
        .times(12, "You clicked a dozen times"),
    "pt_br": "Você clicou o botão %d vezes"
        .zero("Você não clicou no botão")
        .one("Você clicou uma única vez")
        .two("Você clicou um par de vezes")
        .many("Você clicou %d vezes")
        .times(12, "Você clicou uma dúzia de vezes"),
  };

String plural(int value) => localizePlural(value, this, _t);

Or, if you want to define your translations by locale:

static var _t = Translations.byLocale("en_us") +
    {
      "en_us": {
        "You clicked the button %d times": 
          "You clicked the button %d times"
            .zero("You haven't clicked the button")
            .one("You clicked it once")
            .two("You clicked a couple times")
            .many("You clicked %d times")
            .times(12, "You clicked a dozen times"),
      },
      "pt_br": {
        "You clicked the button %d times": 
          "Você clicou o botão %d vezes"
            .zero("Você não clicou no botão")
            .one("Você clicou uma única vez")
            .two("Você clicou um par de vezes")
            .many("Você clicou %d vezes")
            .times(12, "Você clicou uma dúzia de vezes"),
      }
    };

The plural modifiers you can use are zero, one, two, three, four, five, six, ten, times (for any number of elements, except 0, 1 and 2), many (for any number of elements, except 1, including 0) , zeroOne (for 0 or 1 elements), and oneOrMore (for 1 and more elements).

Also, according to a Czech speaker, there must be a special modifier for 2, 3 and 4. To that end you can use the twoThreeFour modifier.

Note: It will use the most specific plural modifier. For example, .two is more specific than .many. If no applicable modifier can be found, it will default to the unversioned string. For example, this: "a".zero("b").four("c:") will default to "a" for 1, 2, 3, or more than 5 elements.

Custom modifiers

You can actually create any modifiers you want. For example, some languages have different translations for different genders. So you could create .gender() that accepts Gender modifiers:

enum Gender {they, female, male}

int gnd = Gender.female;
return Text("There is a person".gender(gnd));

Then, your translations file should use .modifier() and localizeVersion() like this:

static var _t = Translations("en_us") +
  {
    "en_us": "There is a person"
        .modifier(Gender.male, "There is a man")
        .modifier(Gender.female, "There is a woman")
        .modifier(Gender.they, "There is a person"),
    "pt_br": "Há uma pessoa"
        .modifier(Gender.male, "Há um homem")
        .modifier(Gender.female, "Há uma mulher")
        .modifier(Gender.they, "Há uma pessoa"),
  };

String gender(Gender gnd) => localizeVersion(gnd, this, _t);

Interpolation

Your translations file may declare a fill method to do interpolations:

static var _t = Translations("en_us") +
  {
    "en_us": "Hello %s, this is %s",
    "pt_br": "Olá %s, aqui é %s",
  };

String get i18n => localize(this, _t);

String fill(List<Object> params) => localizeFill(this, params);

Then you may use it like this:

print("Hello %s, this is %s".i18n.fill(["John", "Mary"]));

The above code will print Hello John, this is Mary if the locale is English, or Olá John, aqui é Mary if it's Portuguese.

It uses the sprintf package internally. I don't know how closely it follows the C sprintf specification, but here it is.

Direct use of translation objects

If you have a translation object you can use the localize function directly to perform translations:

var translations = Translations("en_us") +
    {
      "en_us": "Hi",
      "pt_br": "Olá",
    };

// Prints "Hi".
print(localize("Hi", translations, locale: "en_us");

// Prints "Olá".
print(localize("Hi", translations, locale: "pt_br");

// Prints "Hi".
print(localize("Hi", translations, locale: "not valid");

Changing the current locale

To change the current locale, do this:

I18n.of(context).locale = Locale("pt", "BR");

To return the current locale to the system default, do this:

I18n.of(context).locale = null;

Note: The above will change the current locale only for the i18n_extension, and not for Flutter as a whole.

Reading the current locale

To read the current locale, do this:

// Both ways work:
Locale locale = I18n.of(context).locale;
Locale locale = I18n.locale;

// Or get the locale as a lowercase string. Example: "en_us".
String localeStr = I18n.localeStr;

// Or get the language of the locale, lowercase. Example: "en".
String language = I18n.language;

Observing locale changes

There is a global static callback you can use to observe locale changes:

I18n.observeLocale = 
  ({required Locale oldLocale, required Locale newLocale}) 
      => print("Changed from $oldLocale to $newLocale.");

Importing and exporting

This package is optimized so that you can easily create and manage all of your translations yourself, by hand.

However, for large projects with big teams you probably need to follow a more involved process:

Export all your translatable strings to files in some external format your professional translator, or your crowdsourcing tool uses (see formats below).

Continue developing your app while waiting for the translations.

Import the translation files into the project and test the app in each language you added.

Repeat the process as needed, translating just the changes between each app revision. As necessary, perform additional localization steps yourself.

Formats

The following formats may be used with translations:

PO: https://poedit.net

JSON: Can be used, however it lacks specific features for translation, like plurals and gender.

ARB: This is based on JSON, and is the default format for Flutter localizations. https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification

ICU: https://format-message.github.io/icu-message-format-for-translators/

XLIFF: This is based in XML. https://en.wikipedia.org/wiki/XLIFF

CSV: You can open this with Excel, save it in .XLSX and edit it there. However, beware not to export it back to CSV with the wrong settings (using something else than UTF-8 as encoding). https://en.wikipedia.org/wiki/Comma-separated_values

YAML: Can be used, however it lacks specific features for translation, like plurals and gender.

Importing

Currently, only .PO and .JSON importers are supported out-of-the-box.

Note: Those importers were contributed by Johann Bauer. If you want to help creating importers for any of the other formats above, please PR here: https://github.com/marcglasberg/i18n_extension.

Add your translation files as assets to your app in a directory structure like this:

app
 \_ assets
    \_ locales
       \_ de.po
       \_ fr.po
        ...

Then you can import them using GettextImporter or JSONImporter:

import 'package:i18n_extension/io/import.dart';
import 'package:i18n_extension/i18n_extension.dart';

class MyI18n {
  static TranslationsByLocale translations = Translations.byLocale("en");

  static Future<void> loadTranslations() async {
    translations +=
        await GettextImporter().fromAssetDirectory("assets/locales");
  }
}

extension Localization on String {
  String get i18n => localize(this, MyI18n.translations);
  String plural(int value) => localizePlural(value, this, MyI18n.translations);
  String fill(List<Object> params) => localizeFill(this, params);
}

For usage in main.dart, see here.

Note: When using .po files, make sure not to include the country code, because the locales are generated from the filenames which don't contain the country code and if you'd include the country codes, you'll get errors like this: There are no translations in 'en_us' for "Hello there".

Note: If you need to import any other custom format, remember importing is easy to do because the Translation constructors use maps as input. If you can generate a map from your file format, you can then use the Translation() or Translation.byLocale() constructors to create the translation objects.

The GetStrings exporting utility

An utility script to automatically export all translatable strings from your project was contributed by Johann Bauer. Simply run flutter pub run i18n_extension:getstrings in your project root directory and you will get a list of strings to translate in strings.json. This file can then be sent to your translators or be imported in translation services like Crowdin, Transifex or Lokalise. You can use it as part of your CI pipeline in order to always have your translation templates up to date.

Note the tool simply searches the source code for strings to which getters like .i18n are applied. Since it is not very smart, you should not make it too hard:

print("Hello World!".i18n); // This would work.

// But the tool would not be able to spot this 
// since it doesn't execute the code.
var x = "Hello World";
print(x.i18n);

Other ways to export

As previously discussed, i18n_extension will automatically list all keys into a map if you use some unknown locale, run the app, and manually or automatically go through all the screens. For example, create a Greek locale if your app doesn't have Greek translations, and it will list all keys into Translations.missingTranslationCallback.

Then you can read from this map and create your exported file. There is also this package that goes through all screens automatically.

FAQ

Q: Do I need to maintain the translation files as Dart files?

A: Not really. You do have a Dart file that creates a Translation object, yes, and this object is optimized for easily creating translations by hand. But it creates them from maps. So if you can create maps from some file you can use that file. For example, a simple code generator that reads .json und outputs Dart maps would do the job: var _t = Translations("en_us") + readFromJson("myfile.json").


 

Q: How do you handle changing the locale? Does the I18n class pick up changes to the locale automatically or would you have to restart the app?

A: It should pick changes to the locale automatically. Also, you can change the locale manually at any time by doing I18n.of(context).locale = Locale("pt", "BR");.


 

Q: What's the point of importing 'default.i18n.dart'?

A: This is the default file to import from your widgets. It lets the developer add .i18n to any strings they want to mark as being a "translatable string". Later, someone will have to remove this default file and add another one with the translations. You basically just change the import later. The point of importing 'default.i18n.dart' before you create the translations for that widget is that it will record them as missing translations, so that you don't forget to add those translations later.


 

Q: Can I do translations outside of widgets?

A: Yes, since you don't need access to context. It actually reads the current locale from I18n.locale, which is static, and all the rest is done with pure Dart code. So you can translate anything you want, from any code you want. You can also define a locale on the fly if you want to do translations to a locale different from the current one.


 

Q: By using identifier keys like howAreYou, I know that there's a localization key named howAreYou because otherwise my code wouldn't compile. There is no way to statically verify that "How are you?".i18n will do what I want it to do.

A: i18n_extension lets you decide if you want to use identifier keys like howAreYou or not. Not having to use those was one thing I was trying to achieve. I hate having to come up with these keys. I found that the developer should just type the text they want and be done with it. In other words, in i18n_extension you don't need to type a key; you may type the text itself (in your default language). So there is no need to statically verify anything. Your code will always compile when you type a String, and that exact string will be used for your default language. It will never break.


 

Q: But how can I statically verify that a string has translations? Just showing the translatable string as defined in the source code will not hide that some translations are missing?

A: You can statically verify that a string should have translations because it has .i18n attached to it. What you can't do is statically verify that those translations were actually provided for all supported languages. But this is also the case when you use older methods. With the older methods you also just know it should have translations, because it has a translation key, but the translation itself may be missing, or worse yet, outdated. With i18n_extension at least you know that the translation to the default language exists and is not outdated.


 

Q: What happens if a developer tries to call i18n on a string without translations, wouldn't that be harder to catch?

A: With i18n_extension you can generate a report with all missing translations, and you can even add those checks to tests. In other words, you can just freely modify any translatable string, and before your launch you get the reports and fix all the translations.


 

Q: There are a lot of valid usages for String that don't deal with user-facing messages. I like to use auto-complete to see what methods are available (by typing someString.), and seeing loads of unrelated extension methods in there could be annoying.

A: The translation extension is contained in your widget file. You won't have this extension in scope for your business classes, for example. So .i18n will only appear in your auto-complete inside of your widget classes, where it makes sense.


 

Q: Do I actually need one .i18n.dart (a translations file) per widget?

A: No you don't. It's suggested that you create a translation file per widget if you are doing translations by hand, but that's not a requirement. The reason I think separate files is a good idea is that sometimes internationalization is not only translations. You may need to format dates in specific ways, or make complex functions to create specific strings that depend on variables etc. So in these cases you will probably need somewhere to put this code. In any case, to make translations work all you need a Translation object which you can create in many ways, by adding maps to it using the + operator, or by adding other translation objects together using the * operator. You can create this Translation objects anywhere you want, in a single file per widget, in a single file for many widgets, or in a single file for the whole app. Also, if you are not doing translations by hand but importing strings from translation files, then you don't even need a separate file. You can just add extension Localization on String { String get i18n => localize(this, Translations("en_us") + load("file.json")); } to your own widget file.


 

Q: Won't having multiple files with extension Localization lead to people importing the wrong file and have translations missing?

A: The package records all your missing translations, and you can also easily log or throw an exception if they are missing. So you will know if you import the wrong file. You can also add this reports to your unit tests. It will let you know even if you import the right file and translations are missing in some language, and it will let you know even if you import from .arb files and translations are missing in some language.


 

Q: Are there importers for X?

A: Currently, only .PO and .JSON importers are supported out-of-the-box. Keep in mind this lib development is still new, and I hope the community will help writing more importers/exporters. We hope to have those for .arb .icu .xliff .csv and .yaml, but we're not there yet. However, since the Translations object use maps as input/output, you can use whatever file you want if you convert them to a map yourself.


 

Q: How does it report missing translations?

A: _At the moment you should just print Translations.missingKeys and Translations.missingTranslations. We'll later create a Translations.printReport() function that correlates these two pieces of information and outputs a more readable report.


 

Q: The package says it's "Non-boilerplate", but doesn't .i18n.dart contain boilerplate?

A: The only necessary boilerplate for .i18n.dart files is static var _t = Translations("...") + and String get i18n => localize(this, _t);. The rest are the translations themselves. So, yeah, it's not completely without boilerplate, but saying "Less-boilerplate" is not that catchy.

The Flutter packages I've authored:

My Medium Articles:

My article in the official Flutter documentation:

---
Marcelo Glasberg:
https://github.com/marcglasberg
https://twitter.com/glasbergmarcelo
https://stackoverflow.com/users/3411681/marcg
https://medium.com/@marcglasberg

Use this package as a library

Depend on it

Run this command:

With Flutter:

 $ flutter pub add i18n_extension

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):


dependencies:
  i18n_extension: ^4.1.1

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:i18n_extension/i18n_extension.dart'; 

example/README.md

I18n examples

Please run main.dart in folders:

example/lib/example1

Demonstrates basic translations using a I18n widget.

There are 3 widget files that need translations:

  • main.dart
  • my_screen.dart
  • my_widget.dart

And there is one translations-file for each one:

  • main.i18n.dart
  • my_screen.i18n.dart
  • my_widget.i18n.dart

Note: We could have put all translations into a single translations-file that would be used by all widget files. It's up to you how to organize things.

Note: The translations-files in this example use strings as keys. For example:

"You clicked the button %d times:".plural(counter),

example/lib/example2

Demonstrates basic translations using a I18n widget.

There are 3 widget files that need translations:

  • main.dart
  • my_screen.dart
  • my_widget.dart

But this time we demonstrate how to use a single translations-file for all of them:

  • my.i18n.dart

Note: The translations-files in this example use identifiers as keys. For example:

 youClickedThisNumberOfTimes.plural(counter)

example/lib/example3

Demonstrates having two I18n widgets. It is NOT recommended to have two I18n widgets, at all. However, if for some reason it is inevitable, I've provided the I18n.forceRebuild() method to help you deal with it.

Download Details:

Author: marcglasberg

Source Code: https://github.com/marcglasberg/i18n_extension

#flutter #i18n 

Translation and Internationalization (i18n) for Flutter

A Library tool That Help You Manager I18n Strings in Flutter

intl_converter

A library tool that help you manager i18n strings in flutter,include convert from android "strings-xx.xml". Before to use,you need to know what is LocalizationsDelegate and what is intl_translation.

Flutter use arb file to manager i18n strings. some times you need to refactor an old android project use flutter.

intl_converter can help you convert android strings-xx.xml to flutter's arb file,and auto generate Dart class include all the strings define.You just need to extend the Dart class and registe with LocalizationsDelegate.

Flutter's internationalization reference: https://github.com/flutter/website/tree/master/examples/internationalization/intl_example/lib

Install

dev_dependencies:
  intl_converter: version

Use build args

--scan-dir the path where to scan android style strings-xx.xml files

--out-dir ARB and dart file output path

--gen-class the dart class name of auto generated

--file-name the dart file name of auto generated.default is:"strings_define.dart" (defaults to "strings_define.dart")

--dev-locale use which locale content to generate default dart class

build: flutter packages pub run intl_converter:build --scan-dir=xx --out-dir=yy --gen-class=zz

Use json config 'intl_converter.json'

{  "scan-dir": "assets/i18n",  "out-dir": "lib/i18n/gen",  "gen-class": "AppStringsDefine",  "dev-locale": "zh"}

build: flutter packages pub run intl_converter:build

Use this package as a library

Depend on it

Run this command:

With Dart:

 $ dart pub add intl_converter

With Flutter:

 $ flutter pub add intl_converter

This will add a line like this to your package's pubspec.yaml (and run an implicit dart pub get):

dependencies:  intl_converter: ^1.0.0

Alternatively, your editor might support dart pub get or flutter pub get. Check the docs for your editor to learn more.

Import it

Now in your Dart code, you can use:

import 'package:intl_converter/intl_converter.dart'; 

example/lib/main.dart

import 'package:flutter/material.dart';import 'i18n/app_strings.dart';import 'package:flutter_localizations/flutter_localizations.dart';void main() => runApp(MyApp());class MyApp extends StatelessWidget {  @override  Widget build(BuildContext context) {    Locale defLocale = Locale('en', '');    return MaterialApp(      localizationsDelegates: [        const AppStringsDelegate(),        GlobalMaterialLocalizations.delegate,        GlobalWidgetsLocalizations.delegate      ],      locale: defLocale,      supportedLocales: AppStrings.createSupportedLocale(false),      onGenerateTitle: (BuildContext context) =>          AppStrings.of(context).appTitle,      theme: ThemeData(        primarySwatch: Colors.blue,      ),      home: MyHomePage(defLocale),    );  }}class MyHomePage extends StatefulWidget {  final Locale locale;  MyHomePage(this.locale, {Key key}) : super(key: key);  @override  _MyHomePageState createState() => _MyHomePageState(locale);}class _MyHomePageState extends State<MyHomePage> {  int _counter = 0;  Locale _locale;  _MyHomePageState(this._locale);  void _incrementCounter() {    setState(() {      _counter++;    });  }  @override  Widget build(BuildContext context) {    var scaffold = Scaffold(      appBar: AppBar(        title: Text(AppStrings.of(context).appTitle),      ),      body: Center(        child: Column(          mainAxisAlignment: MainAxisAlignment.center,          children: <Widget>[            Text(              AppStrings.of(context).pushedTitle,            ),            Text(              '$_counter',              style: Theme.of(context).textTheme.headline3,            ),            ElevatedButton(              onPressed: () {                //change locale                Locale nextLocale;                if (_locale.languageCode == 'en') {                  nextLocale = Locale('zh', '');                } else {                  nextLocale = Locale('en', '');                }                AppStrings.load(nextLocale).then((_) {                  setState(() {                    _locale = nextLocale;                  });                });              },              child: Text(AppStrings.of(context).changeLocale),            )          ],        ),      ),      floatingActionButton: FloatingActionButton(        onPressed: _incrementCounter,        tooltip: AppStrings.of(context).increment,        child: Icon(Icons.add),      ), // This trailing comma makes auto-formatting nicer for build methods.    );    return Localizations.override(      context: context,      child: scaffold,      locale: _locale,    );  }} 

#flutter #i18n  

A Library tool That Help You Manager I18n Strings in Flutter

Multi Language Done Easily in React Native (2020)

In this video I’ll show how we can setup a typesafe Language React context provider to support multilingual (i18n) using only React native localize, no other dependencies needed :)

https://gist.github.com/jonasgroendahl/b17bc8ce585c013fe0866af1f533454d

#react #react native #i18n

Multi Language Done Easily in React Native (2020)
Nat  Grady

Nat Grady

1627141080

Easily Translate Angular 12 App Using ngx-translate | Geekstrick

Contents   hide

1) Angular 12 with ngx-translate

2) Add ngx-translate to angular app

3) Setup translations JSON file

4) Create Internationalization Module

4.1) Internationalization Module

4.2) Localization Configuration Service

4.3) Localization Service

5) Using Internationalization Module

5.1) Using translate method

5.2) Using translate pipe

This tutorial will see how we can Easily Translate the Angular 12 App Using the library

Angular 12 with ngx-translate

The internationalization (i18n) library for Angular

ngx-translate:

It is an internationalization library for Angular. It will let you define translations for your content in different languages and you can switch languages easily.

The ngx-translate library provides you a service, a directive, and a pipe to handle any dynamic or static content.

NGX-Translate is also extremely modular. It is written in a way that makes it really easy to replace any part with a custom implementation in case the existing one doesn’t fit your needs.

Add ngx-translate to angular app

Let’s first import the ngx-translate library using the NPM command, Here we going to import 2 packages of ngx-translate

  • @ngx-translate/core: ^13.0.0 – contains core methods
  • @ngx-translate/http-loader: ^6.0.0 – contains methods to load translation files.

#angular 12 #i18n #internationalization

Easily Translate Angular 12 App Using ngx-translate | Geekstrick

React Polyglot Hooks - Hooks for using Polyglot.js with React

React Polyglot Hooks

Hooks for using Polyglot.js with React.

Installation

React Polyglot Hooks is available as an npm package.

// with npm
npm install react-polyglot-hooks

// with yarn
yarn add react-polyglot-hooks

React is required as a peer dependency. Please install version 16.8.3 or later (minimum requirement for hooks).

Usage

React Polyglot Hooks offers 1 wrapper component: <I18n>, 2 hooks: useLocale and useT and 1 helper component: <T>. The hooks and the helper component have to be wrapped with the <I18n> component to work properly.

Here is a quick example to get you started: First, wrap a parent component with <I18n> and provide locale and phrases.

Parent.jsx

import React from 'react';
import { I18n } from 'react-polyglot-hooks';
import Child from './Child';

// ... or any ways to determine current locale
const locale = window.locale || 'en';

// You can put translations in separate files
const phrases = {
  en: { hello: 'Hello, World!' },
  fr: { hello: 'Bonjour, Monde!' },
};

export default function Parent() {
  return (
    <I18n locale={locale} phrases={phrases[locale]}>
      <Child />
    </I18n>
  );
}

Then, in a child component, call the hooks:

Child.jsx

import React from 'react';
import { T, useLocale } from 'react-polyglot-hooks';

export default function Child() {
  const locale = useLocale(); // Current locale: "en"
  return (
    <React.Fragment>
      <span>{locale}</span>
      <T phrase="hello" />
    </React.Fragment>
  );
}

That’s it! For more in-depth examples, check out the examples directory.

Usage with TypeScript

Types are baked in as the project is written in TypeScript.

API

<I18n>

Provides i18n context to the T component and the hooks. Accepts all options supported by Polyglot.js.

Props
Prop Type Required Description
children Node Any node(s) accepted by React.
locale string Current locale, used for pluralization.
phrases { [key: string]: string } Key-value pair of translated phrases, can be nested.
allowMissing boolean Controls whether missing phrase keys in a t call is allowed.
interpolation { prefix: string, suffix: string } Controls the prefix and suffix for an interpolation.
onMissingKey (key: string, options: InterpolationOptions, locale: string) => string A function called when allowMissing is true and a missing key is encountered.
pluralRules { pluralTypes: PluralTypes, pluralTypeToLanguages: PluralTypeToLanguages } Custom pluralization rules to be applied to change language(s) behaviour(s).

<T>

Renders a phrase according to the props.

Props
Props Type Required Description
phrase string Key of the needed phrase.
count number A number to be interpolated with smart_count.
fallback string A fallback to be rendered if the phrase is missing.
interpolations InterpolationOptions See InterpolationOptions below.

useLocale

Returns the current active locale (string).

useT

Returns a special function (t) which takes in parameters and returns a phrase.

t(phrase, InterpolationOptions)

Param Type Required Description
phrase string Key of the needed phrase.
InterpolationOptions number or { [key: string]: ReactNode } A number to be interpolated with smart_count, or a key-value pair to interpolate values into the phrase.

For more details, please visit the documentation of Polyglot.js.

Changelog

The changelog is available here.

Acknowledgements

This project is developed to ease the use of Polyglot.js within React, and is highly influenced by react-polyglot.

Download Details:

Author: pmmmwh
The Demo/Documentation: View The Demo/Documentation
Download Link: Download The Source Code
Official Website: https://github.com/pmmmwh/react-polyglot-hooks
License: This project is licensed under the terms of the MIT License.

#react #polyglot #i18n

React Polyglot Hooks - Hooks for using Polyglot.js with React
Chaverri Davis

Chaverri Davis

1626100080

State of Vue i18n - Vuejs Amsterdam 2021

Kazupon has been contributing in many Open Source activities as a Vue.js core team member so far. Think about vue-i18n, vue-i18n-loader, i18n custom block for SFC, vue-i18n-extensions, extensions for Vue I18n lik ESLint plugin for Vue I18n and more!

Supporting Storybook for Vue.js and Kazupon is Organizing the Vue.js Japan community and organiser of Vue Fes Japan 2018, Vue Fes Japan 2019

He has contributed to Vue.js official some open source projects, he has developed i18n related library and tools for Vue.js, and In Japan and translated official documents translation.


Slides: https://speakerdeck.com/kazupon/state-of-vue-i18n


Rewatch more recordings from our past conferences at our video portal ➡️ https://vuejs.amsterdam/video/


Our upcoming conferences:
➡️ https://vuejs.amsterdam/
➡️ https://reactlive.nl/

#vue #i18n

State of Vue i18n - Vuejs Amsterdam 2021

Next.js + Strapi - Internationalization (i18n)

In this episode we are going to take look at new internationalization features of both Strapi and Nextjs.

As you may remember I already did an episode about localization and internationalization, but at the time of recording that video Strapi didn’t have options for translating content. So we had to create additional fields in our content type that would serve as translations for original fields.

This is an OK solution if you only have 1 additional language, but imagine that you wanna translate your site or app to five languages. That would mean that you would have 5 additional fields for every translatable field.

Not an elegant solution by any stretch.

But with version 3.6 of Strapi you get translation options out of the box. And to make things even better Next.js also got Internationalized Routing in version 10.

So in this episode we are going to take a look at how these two features play together.

Scroll Icon by https://www.flaticon.com/authors/prosymbols from https://www.flaticon.com/

Code used in this tutorial

https://bit.ly/3g6cmna

#next #strapi #i18n

Next.js + Strapi - Internationalization (i18n)

Maintaining Multi-language Angular Applications with i18n

Discover the possibilities of Angular internationalization (i18n) and localization (l10n)

Angular i18n and the localizing of applications had an overhaul with version 9, enabled by the new rendering engine Ivy. In this article, we take a closer look at how this built-in package of Angular now works, while pointing out the benefits and drawbacks we find.

We then set up an application with Angular internationalization and go through the complete process from marking texts for translation, extracting them to translation files, and how we manage these files to get the application deployed and maintained while keeping users all over the world happy with our translations.

Internationalization and localization

It’s easy to get confused with the terms internationalization (i18n) and localization (i10n), and where to draw the line between them. Internationalization is the process of designing your application so that it can be adapted to different locales around the world while localization is the process of building the versions of the applications to different locales.

Together they help us in adapting software to different languages and local variations in the look and feel expected by the target audience.

How localization works with Ivy

The new localization process of  Angular Ivy is based on the concept of  tagged templates. Tags allow you to parse template literals with a function. The tag used here is the global identifier $localize. Instead of translating the strings, the Ivy template compiler converts all template text marked with i18n attributes to $localize tagged strings.

So when we add:

<h1 i18n>Hello World!</h1>

#l10n #programming #angular #i18n #javascript

Maintaining Multi-language Angular Applications with i18n
Nat  Grady

Nat Grady

1622703360

Deep Links With Angular Routing and i18n in Prod Mode

With Angular i18n the different directories for the language build need to be supported. This article shows how to support language-independent deep linking.

In some use cases, it is necessary to provide deep links in an Angular application. Without support for multiple languages, it is simply the URL + the angular route. With Angular i18n the different directories for the language build need to be supported. This article shows how to support language-independent deep linking. The  AngularAndSpring project will serve as an example. It is a Spring Boot application that provides the backend with the rest endpoints and serves the Angular frontend in the ‘resources/static/’ directory.

Frontend

The frontend contains these file and directories:

  • ‘resources/static/index.html’ -> push the user to the frontend matching their browser language
  • ‘resources/static/en/’ -> English Angular compiled frontend package
  • ‘resources/static/de/’ -> german Angular compiled frontend package

The Angular application supports; for example, this route: ‘/details/bsdetail/btcusd’. To access it, the request ‘https://somedomain.de/details/bsdetail/btcusd’ needs to be forwarded to the matching ‘index.html’ of the Angular package. For example, ‘https://somedomain.de/en/index.html’.

#spring boot #angular #i18n #deep linking

Deep Links With Angular Routing and i18n in Prod Mode
Camron  Shields

Camron Shields

1620571551

Internationalization i18n in Express

Our application can support multiple languages so that we can return responses based on users language. In this tutorial we are going to implement that functionality by using i18next in express application.

The repo for this tutorial can be found here https://github.com/basarbk/express-sequelize

#express #i18n #node

Internationalization i18n in Express

A Fabulous February Firefox — 86!

Looking into the near distance, we can see the end of February loitering on the horizon, threatening to give way to March at any moment. To keep you engaged until then, we’d like to introduce you to Firefox 86. The new version features some interesting and fun new goodies including support for the Intl.DisplayNames object, the :autofill pseudo-class, and a much better <iframe> inspection feature in DevTools.

Better

#firefox #mdn #css #firefox #i18n #intl #javascript

A Fabulous February Firefox — 86!

How To Use Internationalization (i18n) in Angular

Learn how use the built-in internationalization (i18n) features in Angular App

Internationalization is the process of supporting multiple languages in your applications.

This can be accomplished in an Angular application through third party libraries, such as ngx-translate, or you can use the built-in i18n functionality.

Note: “i18n” is a numeronym where “18” represents the number of letters between the first letter (“I”) and the last letter (“N”) in the word “internationalization”. As you develop applications, you may also encounter “a11y” which is a numeronym for accessibility.

In this tutorial, you will learn to use the built-in i18n functionality in an Angular application.

#angular #i18n

How To Use Internationalization (i18n) in Angular
Flutter Dev

Flutter Dev

1617178997

Lightweight i18n Solution for Flutter

featured

fast_i18n

Lightweight i18n solution. Use JSON files to create typesafe translations.

Getting Started

Step 1: Add dependencies

dependencies:
  fast_i18n: ^3.0.3

dev_dependencies:
  build_runner: any

Step 2: Create JSON files

Create these files inside your lib directory. Preferably in one common package like lib/i18n. Only files having the .i18n.json file extension will be detected. You can configure it.

strings.i18n.json (default, fallback)

{
  "hello": "Hello $name",
  "save": "Save",
  "login": {
    "success": "Logged in successfully",
    "fail": "Logged in failed"
  }
}

strings_de.i18n.json

{
  "hello": "Hallo $name",
  "save": "Speichern",
  "login": {
    "success": "Login erfolgreich",
    "fail": "Login fehlgeschlagen"
  }
}

Step 3: Generate the dart code

flutter pub run build_runner build

Step 4: Initialize

a) use device locale

void main() {
  WidgetsFlutterBinding.ensureInitialized(); // add this
  LocaleSettings.useDeviceLocale(); // and this
  runApp(MyApp());
}

b) use specific locale

@override
void initState() {
  super.initState();
  String storedLocale = loadFromStorage(); // your logic here
  LocaleSettings.setLocale(storedLocale);
}

Step 4a: Override ‘supportedLocales’

This is optional but recommended.

Standard flutter controls (e.g. back button’s tooltip) will also pick the right locale.

MaterialApp(
  localizationsDelegates: const [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: LocaleSettings.supportedLocales, // <---
)

Step 4b: iOS configuration

File: ios/Runner/Info.plist

<key>CFBundleLocalizations</key>
<array>
   <string>en</string>
   <string>de</string>
</array>

Step 5: Use your translations

Text(t.login.success); // plain
Text(t.hello(name: 'Tom')); // with argument
Text(t.step[3]); // with index (for arrays)
Text(t.type['WARNING']); // with key (for maps)

// advanced
TranslationProvider(child: MyApp()); // wrap your app with TranslationProvider
// [...]
final t = Translations.of(context); // forces a rebuild on locale change
String translateAdvanced = t.hello(name: 'Tom');

API

When the dart code has been generated, you will see some useful classes and functions

t - the translate variable for simple translations

Translations.of(context) - translations which reacts to locale changes

TranslationProvider - App wrapper, used for Translations.of(context)

LocaleSettings.useDeviceLocale() - use the locale of the device

LocaleSettings.setLocale('de') - change the locale

LocaleSettings.setLocaleTyped(AppLocale.en) - change the locale (typed version)

LocaleSettings.currentLocale - get the current locale

LocaleSettings.currentLocaleTyped - get the current locale (typed version)

LocaleSettings.locales - get the supported locales

LocaleSettings.supportedLocales - see step 4a

Configuration

All settings can be set in the build.yaml file. Place it in the root directory.

targets:
  $default:
    builders:
      fast_i18n:i18nBuilder:
        options:
          base_locale: en
          input_directory: lib/i18n
          input_file_pattern: .i18n.json
          output_directory: lib/i18n
          output_file_pattern: .g.dart
          translate_var: t
          enum_name: AppLocale
          key_case: snake
          maps:
            - a
            - b
            - c.d
Key Type Usage Default
base_locale String locale of default json en
input_directory String path to input directory null
input_file_pattern String input file pattern .i18n.json
output_directory String path to output directory null
output_file_pattern String output file pattern .g.dart
translate_var String translate variable name t
enum_name String enum name AppLocale
key_case camel, pascal, snake transform keys (optional) null
maps List<String> entries which should be accessed via keys []

FAQ

How do I add arguments?

Use the $ prefix.

In edge cases you can also wrap it with ${...}.

{
  "greeting": "Hello $name",
  "distance": "${distance}m"
}
t.greeting(name: 'Tom'); // Hello Tom
t.distance(distance: 4.5); // 4.5m

How can I access translations using maps?

Define the maps in your build.yaml. Keep in mind that all nice features like autocompletion are gone.

strings.i18n.json

{
  "welcome": "Welcome",
  "thisIsAMap": {
    "hello world": "hello"
  },
  "classicClass": {
    "hello": "hello",
    "aMapInClass": {
      "hi": "hi"
    }
  }
}

build.yaml

targets:
  $default:
    builders:
      fast_i18n:i18nBuilder:
        options:
          maps:
            - thisIsAMap
            - classicClass.aMapInClass

Now you can access the translations via keys:

String a = t.thisIsAMap['hello world'];
String b = t.classicClass.hello; // the "classical" way
String c = t.classicClass.aMapInClass['hi']; // nested

Can I use lists?

Lists are fully supported. You can also put lists or maps inside lists!

{
  "niceList": [
    "hello",
    "nice",
    [
      "first item in nested list",
      "second item in nested list"
    ],
    {
      "wow": "WOW!",
      "ok": "OK!"
    },
    {
      "a map entry": "access via key",
      "another entry": "access via second key"
    }
  ]
}
String a = t.niceList[1]; // "nice"
String b = t.niceList[2][0]; // "first item in nested list"
String c = t.niceList[3].ok; // "OK!"
String d = t.niceList[4]['a map entry']; // "access via key"

Download Details:

Author: Tienisto
Download Link: Download The Source Code
Official Website: https://github.com/Tienisto/flutter-fast-i18n
License: MIT License

#flutter #i18n

Lightweight i18n Solution for Flutter
Mervin  Kautzer

Mervin Kautzer

1616970540

GitHub Satellite India 2021 - Lightning talk: i18n-ize your app today!

Presented by Amruth Pillai, Software Engineer, Postman

When Amruth Pillai was building his open source project, Reactive Resume, he came to realise that the language in which your users interface with your application can play an important role. Translating your app to the user’s native language can encourage them to spend more time on the app—and convert potential users into customers.

In this talk, Amruth will discuss his learnings from developing Reactive Resume, and the impact of having it translated into 22 languages (and counting) with the help of the community. He’ll also talk about the tools that helped him simplify the process of internationalisation and localisation, while highlighting the benefits that a multi-language UI has provided to both the project and its users.

#github #i18n

GitHub Satellite India 2021 - Lightning talk: i18n-ize your app today!
Fannie  Zemlak

Fannie Zemlak

1615868752

What is the JavaScript Internationalization API (I18n)?

Need to translate your website? Learn how to use the JavaScript Internationalization API (I18n) and how easy it is to implement it in your apps.

English is the world’s most widely used language, yet only one in seven people speak it. It’s the first (native) language of 379 million people, but 917 million speak Mandarin Chinese, 460 million speak Spanish, and 341 million speak Hindi.

Many non-English speakers reside in emerging markets with exponential internet growth. If your web app can be globally translated, your potential target market could increase by 700%!

The JavaScript Internationalization API (also known as i18n) allows you to design web pages and applications in such a way that they can be easily adapted to support the needs of users that speak different languages.

In this article, we’ll look at the various methods the API offers and how you can implement them in your code to reach a wider, more international audience.

Internationalization (I18n) Can Be Tricky

Internationalization looks easy … until you try to do it.

Latin-based languages can be superficially similar. For example, a form requesting a name, email, and date translates like this:

  • Spanish: nombre, email, fecha
  • French: nom, e-mail, date
  • German: name, email, datum

The Gettext internationalization and localization system has been around for several decades, and libraries are available for most programming languages.

#javascript #api #i18n

What is the JavaScript Internationalization API (I18n)?