13 Tips when working with .arb files for localization

Photo by Sigmund on Unsplash

13 Tips when working with .arb files for localization

ยท

9 min read

Since Flutter version 1.22, Flutter has built-in support for localization.

This works pretty well and you can find the official documentation here:

Internationalizing Flutter apps

I have some tips and tricks that I would like to share with you.

TIP 1: Know your options

If you want to know all the options there are for localization, it is a good idea to run this command and examine the output.

flutter gen-l10n -h

There are more configurable options than there are mentioned in the official documentation. For your convenience, this is the actual output:

johannesroest@WS-SW103 app % fvm flutter gen-l10n -h                                                                                         
Generate localizations for the current project.

Global options:
-h, --help                  Print this usage information.
-v, --verbose               Noisy logging, including all shell commands executed.
                            If used with "--help", shows hidden options. If used with "flutter doctor", shows additional diagnostic information. (Use "-vv" to force verbose logging in those cases.)
-d, --device-id             Target device id or name (prefixes allowed).
    --version               Reports the version of this tool.
    --suppress-analytics    Suppress analytics reporting when this command runs.

Usage: flutter gen-l10n [arguments]
-h, --help                                                      Print this usage information.
    --arb-dir                                                   The directory where the template and translated arb files are located.
                                                                (defaults to "lib/l10n")
    --output-dir                                                The directory where the generated localization classes will be written if the synthetic-package flag is set to false.

                                                                If output-dir is specified and the synthetic-package flag is enabled, this option will be ignored by the tool.

                                                                The app must import the file specified in the "--output-localization-file" option from this directory. If unspecified, this defaults to the same directory as the input directory specified in "--arb-dir".
    --template-arb-file                                         The template arb file that will be used as the basis for generating the Dart localization and messages files.
                                                                (defaults to "app_en.arb")
    --output-localization-file                                  The filename for the output localization and localizations delegate classes.
                                                                (defaults to "app_localizations.dart")
    --untranslated-messages-file                                The location of a file that describes the localization messages have not been translated yet. Using this option will create a JSON file at the target location, in the following format:

                                                                    "locale": ["message_1", "message_2" ... "message_n"]

                                                                If this option is not specified, a summary of the messages that have not been translated will be printed on the command line.
    --output-class                                              The Dart class name to use for the output localization and localizations delegate classes.
                                                                (defaults to "AppLocalizations")
    --preferred-supported-locales=<locale>                      The list of preferred supported locales for the application. By default, the tool will generate the supported locales list in alphabetical order. Use this flag if you would like to default to a different locale. For example, pass in "en_US" if you would like your app to default to American English on devices that support it. Pass this option multiple times to define multiple items.
    --header                                                    The header to prepend to the generated Dart localizations files. This option takes in a string.

                                                                For example, pass in "/// All localized files." if you would like this string prepended to the generated Dart file.

                                                                Alternatively, see the "--header-file" option to pass in a text file for longer headers.
    --header-file                                               The header to prepend to the generated Dart localizations files. The value of this option is the name of the file that contains the header text which will be inserted at the top of each generated Dart file.

                                                                Alternatively, see the "--header" option to pass in a string for a simpler header.

                                                                This file should be placed in the directory specified in "--arb-dir".
    --[no-]use-deferred-loading                                 Whether to generate the Dart localization file with locales imported as deferred, allowing for lazy loading of each locale in Flutter web.

                                                                This can reduce a web appโ€™s initial startup time by decreasing the size of the JavaScript bundle. When this flag is set to true, the messages for a particular locale are only downloaded and loaded by the Flutter app as they are needed. For projects with a lot of different locales and many localization strings, it can be an performance improvement to have deferred loading. For projects with a small number of locales, the difference is negligible, and might slow down the start up compared to bundling the localizations with the rest of the application.

                                                                This flag does not affect other platforms such as mobile or desktop.
    --gen-inputs-and-outputs-list=<path-to-output-directory>    When specified, the tool generates a JSON file containing the tool's inputs and outputs named gen_l10n_inputs_and_outputs.json.

                                                                This can be useful for keeping track of which files of the Flutter project were used when generating the latest set of localizations. For example, the Flutter tool's build system uses this file to keep track of when to call gen_l10n during hot reload.

                                                                The value of this option is the directory where the JSON file will be generated.

                                                                When null, the JSON file will not be generated.
    --[no-]synthetic-package                                    Determines whether or not the generated output files will be generated as a synthetic package or at a specified directory in the Flutter project.

                                                                This flag is set to true by default.

                                                                When synthetic-package is set to false, it will generate the localizations files in the directory specified by arb-dir by default.

                                                                If output-dir is specified, files will be generated there.
                                                                (defaults to on)
    --project-dir=<absolute/path/to/flutter/project>            When specified, the tool uses the path passed into this option as the directory of the root Flutter project.

                                                                When null, the relative path to the present working directory will be used.
    --[no-]required-resource-attributes                         Requires all resource ids to contain a corresponding resource attribute.

                                                                By default, simple messages will not require metadata, but it is highly recommended as this provides context for the meaning of a message to readers.

                                                                Resource attributes are still required for plural messages.
    --[no-]nullable-getter                                      Whether or not the localizations class getter is nullable.

                                                                By default, this value is set to true so that Localizations.of(context) returns a nullable value for backwards compatibility. If this value is set to true, then a null check is performed on the returned value of Localizations.of(context), removing the need for null checking in user code.

TIP 2: Use l10n.yaml

I like to store the options in the l10n.yaml file and check it into version control.

This is the configuration I use:

arb-dir: lib/l10n/arb
output-dir: lib/l10n/generated
template-arb-file: intl_nl.arb
output-localization-file: l10n.dart
output-class: S
synthetic-package: false
nullable-getter: false
#untranslated-messages-file: l10n_errors.txt

TIP 3: Do not use a synthetic package

synthetic-package: false

I would like to set this value to false. So I can actually see and 'check in' the code that is generated.

This isn't fail-safe, as I sometimes have to flip this value to true for one compilation and back to false to fix a build error. I do not get this error a lot (it might be fixed in the meantime). I will update this post when I get this error again.

TIP 4: Configure the folders for the source and target files

arb-dir: lib/l10n/arb
output-dir: lib/l10n/generated

These are the folders where the localization files are stored. The arb-dir is the folder where the tool can find the 'source' arb files. The output-dir is the folder where the tool places the generated dart files.

Keep in mind, that the output-dir is only used when synthetic-package is set to false.

TIP 5: Name the output localization file

output-localization-file: l10n.dart

This defines the file that you will have to import in each dart file where you want to use localization. It is good to use something memorable. For me, this name works great with code completion when typing import 'l10n'.

import 'package:count_app/l10n/generated/l10n.dart';

TIP 6: Choose a short name for the localization class

output-class: S

By default, the class name for the generated localizations is named 'AppLocalizations'. While this name is accurate, I think it's way too long if you need it in many locations in your source code.

// A bit too long 
AppLocalizations.of(context)
// This look better 
S.of(context)

TIP 7: Don't generate nullable getters

nullable-getter: false

Unless you have some code that has to be compatible with nullable by default, I recommend setting this value to false. Otherwise, you will have to do a null check on each call to S.of(context)!, which is cumbersome and looks ugly.

TIP 8: Specify the template arb file

template-arb-file: intl_nl.arb

If you use placeholders in your translations, you need to specify the type (string, int etc) and the name of that placeholder. This information is not needed in every arb file, but only in the 'template' arb file.

  "s100LoggingOnToBranch": "Je gaat je aanmelden bij vestiging **{branchId}**.",
  "@s100LoggingOnToBranch": {
    "placeholders": {
      "branchId": {
        "type": "String"
      }
    }
  },

TIP 9: Translate per page and number your pages

I like to number my pages and translate per page. Although this gives some duplicated translation values, I like the flexibility that it gives.

A sample of page names are:

  • s100_sign_in_ax_page.dart
  • s200_main_menu_page.dart
  • s300_select_document_page.dart
  • s310_download_documents_page.dart
  • s320_upload_documents_page.dart

I use this numbering, to group my translations. All translations for pages start with 'sxxx'. This makes refactoring also a bit easier. If I want to delete a page, I can easily delete the corresponding translations as well. Or if I want to change a translation I can easily see on which page that translation is (supposed to be) used.

It also narrows down the translations to choose from while working on a specific page, as all the translations on that page will start with the same identifier, e.g. s100*.

TIP 10: Regenerate the translations while programming

Flutter does support 'hot reload' when adding or changing localizations while debugging. This is awesome, but the code is not generated on each change when there is no debug session running.

You can force the code to be regenerated with this command:

flutter gen-l10n

This way you can easily add translations and enjoy code completion while programming.

TIP 11: Get an overview of missing translations

Uncommenting the line #untranslated-messages-file and running flutter gen-l10n, will help you to find translations that are not yet translated for all languages.

#untranslated-messages-file: l10n_errors.txt

TIP 12: Sort the arb files alphabetically with arb_utils

You can only compare arb files comfortably if the order of the translations in each arb file is the same. It is really cumbersome and error-prone to manage the order of translations by hand.

Luckily there is a package that you can use to sort the arb files. It also keeps the placeholder lines in the template arb file together with the corresponding translation line.

https://pub.dev/packages/arb_utils

This command will sort the contents of an arb file alphabetically.

dart pub global run arb_utils:sort lib/l10n/arb/intl_nl.arb;

The result will be a sorted arb file ๐ŸŽ‰

  "s110DialogPincodeErrorTitle": "Waarschuwing",
  "s110LoggingOnToBranch": "Je gaat je aanmelden bij vestiging **{branchId}**.",
  "@s110LoggingOnToBranch": {
    "placeholders": {
      "branchId": {
        "type": "String"
      }
    }
  },
  "s110SubTitle": "Meld je aan",

TIP 13: Script it!

To support my workflow, I have added a script to my Flutter project in a /scripts folder. Although this script is for macOS, the intentions are clear and obvious, so it would not be too difficult to change this for a Windows environment.

This script sorts all my arb files, and after that, it calls flutter gen-l10n to generate the dart translation classes.

Now it is super easy to sort the arb files and generate the dart code by just executing a simple script.

#!/bin/zsh
pushd ..;
dart pub global run arb_utils:sort lib/l10n/arb/intl_fr.arb;
dart pub global run arb_utils:sort lib/l10n/arb/intl_nl.arb;
fvm flutter gen-l10n
popd;
ย