StringLane

    Type to search titles, sections and body text across all 58 pages.

    Concept

    Flutter ARB File Format: Example, Placeholders, Plurals

    An ARB file is the JSON file Flutter gen-l10n reads, one per locale. A complete example with placeholders, plurals, select and @ metadata, and what breaks.

    Last updated

    An ARB file (Application Resource Bundle) is a JSON file that holds every translatable string for one locale. Flutter's gen-l10n reads one ARB file per language, such as app_en.arb and app_uk.arb, and generates a typed AppLocalizations class your widgets call. Keys become Dart getters and methods, and @ metadata blocks declare each placeholder's type.

    What does a Flutter ARB file look like?#

    This lib/l10n/app_en.arb covers everything you will write in practice: a plain string, a typed placeholder, a plural, a select, a date and a number.

    {
      "@@locale": "en",
     
      "appTitle": "Notes",
      "@appTitle": {
        "description": "App name shown in the title bar"
      },
     
      "greeting": "Hello, {name}!",
      "@greeting": {
        "description": "Greeting on the home screen",
        "placeholders": {
          "name": { "type": "String", "example": "Ada" }
        }
      },
     
      "fileCount": "{count, plural, =0 {No files} one {{count} file} other {{count} files}}",
      "@fileCount": {
        "description": "Number of files in a folder",
        "placeholders": {
          "count": { "type": "int" }
        }
      },
     
      "sharedBy": "{gender, select, female {She shared this} male {He shared this} other {They shared this}}",
      "@sharedBy": {
        "description": "Who shared the note",
        "placeholders": {
          "gender": { "type": "String" }
        }
      },
     
      "lastEdited": "Last edited {date}",
      "@lastEdited": {
        "placeholders": {
          "date": { "type": "DateTime", "format": "yMMMd" }
        }
      },
     
      "storageUsed": "{percent} of storage used",
      "@storageUsed": {
        "placeholders": {
          "percent": { "type": "double", "format": "percentPattern" }
        }
      }
    }

    Running flutter gen-l10n on it generates these signatures:

    String get appTitle;
    String greeting(String name);
    String fileCount(int count);
    String sharedBy(String gender);
    String lastEdited(DateTime date);
    String storageUsed(double percent);

    Three conventions are at work:

    • @@locale is a file-level attribute naming the language the file holds.
    • Translation keys such as greeting and fileCount become the getters and methods above. They must be valid Dart method names, so file-count stops the build.
    • @key metadata such as @greeting describes the key of the same name: a description for translators, and placeholders with their types. See ARB metadata fields for the rest.

    What does a translated ARB file contain?#

    Only the template file needs the @ blocks. A translation carries the values, and the plural carries the categories its own language needs. Here is app_uk.arb:

    {
      "@@locale": "uk",
      "appTitle": "Нотатки",
      "greeting": "Привіт, {name}!",
      "fileCount": "{count, plural, =0 {Немає файлів} one {{count} файл} few {{count} файли} many {{count} файлів} other {{count} файлу}}",
      "sharedBy": "{gender, select, female {Вона поділилася цим} male {Він поділився цим} other {Поділилися цим}}",
      "lastEdited": "Востаннє змінено {date}",
      "storageUsed": "Використано {percent} сховища"
    }

    English needs one and other; Ukrainian needs one, few, many and other. A Ukrainian translation that copies English's two branches still builds, and renders "3 файлів" where Ukrainian says "3 файли".

    How do placeholders work in an ARB file?#

    Write the placeholder in the value as {name} and declare it in the @key block with a Dart type. The type sets the generated parameter: "type": "DateTime" produces lastEdited(DateTime date). DateTime and numeric placeholders also take a format, which picks an intl pattern such as yMMMd or percentPattern.

    An undeclared placeholder still compiles. gen-l10n infers num inside a plural, String inside a select, DateTime in a date expression, and Object for a plain {name}. A declared type has to fit its use: a plural placeholder declared as String stops the build with Placeholders used in plurals must be of type 'num' or 'int'.

    Placeholders are matched by name, so a translator can move {name} anywhere in the sentence. Matching by name also means two mistakes compile without a word:

    • A translation drops a placeholder. "greeting": "Bonjour !" builds, and French users see a greeting without the name.
    • A translation adds one. "greeting": "Hallo {name}, {extra}!" makes gen-l10n add Object extra to greeting() in every locale, so every existing call site stops compiling, in a file you did not touch.

    Fix placeholder mismatches covers finding both across every locale.

    How do plurals work in an ARB file?#

    ARB values use ICU MessageFormat, so a plural lives inline in the string:

    "fileCount": "{count, plural, =0 {No files} one {{count} file} other {{count} files}}"
    • =0 is an exact match, checked before the categories. gen-l10n builds only =0, =1 and =2 as exact matches: any other, such as =3, fails the whole file. A plural holding both =1 and one (or =0 and zero, =2 and two) is read as one form, and the later branch wins.
    • one, few, many, other are CLDR plural categories, and each language needs its own set.
    • other is required. A plural without it stops the build with Plural expressions must have an "other" case.

    Apostrophes are plain text. gen-l10n reads ' as an ordinary character unless l10n.yaml sets use-escaping: true, so {name} in Remove '{name}' from your profile? is still a placeholder, with quote marks around it.

    Do not use # in an ARB plural. Most ICU runtimes replace # with the number, but gen-l10n does not, and the generated code keeps '# files' as a literal string. Write {count} in every branch.

    How does select work in an ARB file?#

    select picks a branch by a String value you pass in, most often grammatical gender:

    "sharedBy": "{gender, select, female {She shared this} male {He shared this} other {They shared this}}"

    The branch names are yours to choose, and other is required here too. The placeholder must be a String; declaring it as int stops the build with Placeholders used in selects must be of type 'String'.

    Where does Flutter look for ARB files?#

    gen-l10n reads its settings from l10n.yaml at the project root:

    arb-dir: lib/l10n
    template-arb-file: app_en.arb
    output-localization-file: app_localizations.dart

    The template file defines the API: its keys and @ metadata decide which methods exist. Every other file supplies values for the same keys. The locale comes from @@locale or the file name, and the two must agree. A regional file such as app_pt_BR.arb needs a plain app_pt.arb beside it as the fallback, or the build stops.

    What breaks an ARB file without failing the build?#

    Syntax errors stop flutter gen-l10n with a message pointing at the file and key; Flutter gen-l10n errors lists each one with its fix. The failures that matter are the ones that build:

    • A missing translation. gen-l10n prints "de": 1 untranslated message(s) and generates the code. The string falls back to the template locale and ships in English.
    • A dropped or added placeholder, as above.
    • A plural with English's categories in a language that needs more.
    • A # in a plural branch, shown literally instead of the number.
    • A select branch missing from a translation, which silently falls back to other.
    • A plural with both =1 and one, whose earlier branch is never shown.

    None of these fail the build, and most are in a language you may not read.

    How do you check ARB files across every locale?#

    StringLane reads every app_*.arb file in the project and checks each locale against the template: missing keys, dropped or extra placeholders, invalid ICU, missing plural categories, exact plural matches gen-l10n cannot build and branches a =1 and one pair hides, missing select branches, # in plurals, and keys only a translation holds. The free CLI runs the checks from a terminal or CI and writes nothing into your repository:

    npx @stringlane/cli@latest check .

    The ARB editor shows the same checks while you edit, with every locale side by side for each key, and writes back to the same files flutter gen-l10n reads.

    Next steps#

    Frequently asked questions

    What is an ARB file?
    An ARB (Application Resource Bundle) file is a JSON file holding every translatable string for one locale. Flutter's gen-l10n reads the set of them, such as app_en.arb and app_uk.arb, and generates a typed AppLocalizations class your widgets call.
    How do placeholders work in a Flutter ARB file?
    Write the placeholder in the value as {name} and declare it in the matching @key block with a Dart type such as String, int, double or DateTime. An undeclared placeholder still compiles: gen-l10n infers num inside a plural, String inside a select, DateTime in a date expression, and Object everywhere else.
    What happens if a translation drops or adds a placeholder?
    Neither stops the build. A translation that drops {name} compiles and shows the sentence without the value. A translation that adds a placeholder the template does not have makes gen-l10n add that parameter to the method for every locale, so existing call sites stop compiling.
    Why does # render literally in my Flutter app?
    Flutter's gen-l10n does not implement the ICU pound token. Most ICU runtimes replace # with the number; the code Flutter generates keeps it as a literal #. Write the plural variable instead, {count} rather than #, in every branch.
    Does a missing translation fail the Flutter build?
    No. gen-l10n prints a count such as "de": 1 untranslated message(s) and generates the code anyway. The missing string falls back to the template locale, so it ships in English.
    What is the difference between ARB and JSON?
    ARB is JSON with conventions on top. Keys starting with @@ are file-level attributes such as @@locale; a key starting with a single @ is metadata for the translation key of the same name. Every ARB file is valid JSON, but not every JSON file is valid ARB.
    What file name does Flutter expect?
    By default gen-l10n reads app_<locale>.arb from lib/l10n, for example app_en.arb or app_pt_BR.arb. The directory and the template file are set in l10n.yaml with arb-dir and template-arb-file. A regional file such as app_pt_BR.arb also needs a plain app_pt.arb as its fallback.
    The StringLane app showing the key home.welcomeBack, used in three places in Swift files, with four locale cards: English, German and Japanese in place and French flagged PARAM.

    See every locale side by side

    The desktop app flags what is missing or broken on the cell, and saves to your files.

    Download for macOS

    14-day trial, no email