utopia_localization_generator

Utopia USS libs - Localization - Google Sheet-based localization code generator.

Based on flutter_sheet_localization_generator

Install

Run the following command in your project directory:

$ flutter pub add utopia_localization_annotation utopia_localization_utils dev:utopia_localization_generator dev:build_runner

Usage

1. Create a Google Sheet

Create a sheet with your translations (using the format below, an example sheet is available here) :

example

Make sure your sheet is shared:

share

Extract from the link the DOCID and SHEETID values: https://docs.google.com/spreadsheets/d/<DOCID>/edit#gid=<SHEETID>):

2. Create a localization file

Create a localization.dart file that will contain the generated models and data:

// See 1. to get DOCID and SHEETID
// The last parameter is the generated version. You must increment it each time you want to regenerate
// a new version of the labels.
@UtopiaLocalization("DOCID", "SHEETID", 1)
library;

import 'package:utopia_localization_annotation/utopia_localization_annotation.dart';

part 'localization.g.dart';

By default, the following will be generated:

  • An AppLocalizationsData model class with fields/sub-models for all keys from the sheet.
  • An appLocalizationsData global constant with an instance of AppLocalizationsData for every supported locale.
  • fromJson/toJson` methods for all model classes.

3. Configure your app

Configure your app to use the generated localizations by making the modifications to your root App file:

import 'package:utopia_localization_utils/utopia_localization_utils.dart';

import 'localization.dart'; // Your localization file

extension BuildContextAppLocalizationsExtension on BuildContext {
  AppLocalizationsData get strings => localizations();
}

class App extends StatelessWidget {
  //...

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      supportedLocales: appLocalizationsData.supportedLocales,
      localizationsDelegates: [
        const UtopiaLocalizationsDelegate(appLocalizationsData),
        // ... other delegates, like the default Flutter ones:
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      // ...
    );
  }
}

4. Generate & use your localizations

Trigger code generation using: dart run build_runner build or dart run build_runner watch.

Now you can access your localizations from anywhere in your app using context.strings.<key>.

Re-generation

Because of the caching system of build_runner, it can't detect if there's a change on the distant sheet, and it can't know if a new generation is needed.

The third version parameter of the @UtopiaLocalization annotation solves this issue.

Each time you want to trigger a new generation, simply increment that version number and call the build runner again.

Google Sheet format

You can see an example sheet here.

Global format

The file should have :

  • A first header row
    • Column 0 : "Key"
    • then each supported language code ("en", "fr", ...)
  • Following rows for labels
    • Column 0 : the label key (can be a hierarchy, separated by dots)
    • then each translation based on language code of the column

Ignoring a column

Sometimes you may need to add comments for translators. For this, simply add a column with a name between parenthesis, and the column will be completely ignored by the generator.

Example :

Key (Comments) fr en
example.man(Gender.male) This is a man title on home page homme man
example.man(Gender.female) This is a woman title on home page femme woman

Conditionals

It is pretty common to have variants of a label based on a parameter (like a user's gender, or a given month number).

Simply duplicate your entries and end them with (value), where value can be any valid Dart constant value (including enum values, strings, integers and even records). If a duplicated entry without a condition is present, it will be used as a default value.

Key fr en
example.man(Gender.male) homme man
example.man(Gender.female) femme woman
example.man personne person
example.month(0) janivier january
example.month(1) février february

Then, in Dart a function accepting the condition as a parameter will be generated:

context.strings.example.man(user.hasGender ? user.gender : null);
context.strings.example.month(DateTime.now().month);

If passed value doesn't match any present condition, the default value will be used; if default value is not present, an exception will be thrown.

See example for more details.

Parameters

You can insert a {{KEY}} template into a translation value to have dynamic labels.

A Dart function will be generated to be used from your code.

/// Sheet
values.hello | "Hello {{firstName}}!"

/// Code
print(labels.values.hello(firstName: "World"));

Typed parameters

You can also add one of the compatible types (int, double, num, DateTime) to the parameter by suffixing its key with :<type>.

/// Sheet
values.price, "The price is {{price:double}}\$"

/// Code
print(labels.values.price(price: 10.5));

Formatted parameters

You can indicate how the templated value must be formatted by ending the value with a formatting rule in brackets [<rule-key>]. This can be particularly useful for typed parameters.

The available formatting rules depend on the type and generally rely on the intl package.

Type rule-key Generated code
double, int, num decimalPercentPattern, currency, simpleCurrency, compact, compactLong, compactSimpleCurrency, compactCurrency, decimalPattern, percentPattern, scientificPattern NumberFormat.<rule-key>(...)
DateTime Any date format valid pattern DateFormat('<rule-key>', ...).format(...)

Examples:

/// Sheet
values.price | "Price : {{price:double[compactCurrency]}}"

/// Code
print(labels.values.price(price: 2.00));
/// Sheet
values.today | "Today : {{date:DateTime[EEE, M/d/y]}}"

/// Code
print(labels.values.today(date: DateTime.now()));

Changing labels at runtime

By default, fromJson/toJson methods are generated for all localization classes. This allows them to be serialized and de-serialized at runtime, which makes it possible to change the labels without having to recompile the app.

See example for a basic implementation that exports/imports from a JSON file.