Localization (i18n)

Finch has a built-in i18n system supporting multiple languages. Language strings can be stored in JSON files or defined directly in Dart.

Configuration

Set the language options in FinchConfigs:

FinchConfigs configs = FinchConfigs(
  // Path to directory containing language JSON files
  languagePath: pathTo(env.get('LANGUAGE_PATH', './lib/languages')),

  // Use JSON files (LanguageSource.json) or Dart maps (LanguageSource.dart)
  languageSource: LanguageSource.json,

  // If using LanguageSource.dart, supply the map here:
  // dartLanguages: languageDart,
);

Option 1: JSON Files

Create one JSON file per language in your languagePath directory. The file name is the language code:

lib/languages/en.json

{
  "dir": "ltr",
  "logo.title": "My App",
  "greeting": "Hello, World!",
  "example.params": "My name is {name}, my age is {age}",
  "example.params.arr": "My name is {0}, my age is {1}"
}

lib/languages/fa.json

{
  "dir": "rtl",
  "logo.title": "برنامه من",
  "greeting": "سلام دنیا!",
  "example.params": "نام من {name} است، سن من {age} سال است"
}

The dir key (ltr / rtl) here is a plain translation key like any other — read it in a template with {{ $t('dir') }} (as the Templates example does), not {{ $e.dir }}. $e.dir looks up a differently-named key instead — see Template Events for that distinction.

Option 2: Dart Map

Define languages as a Dart Map<String, Map<String, String>> and point FinchConfigs to it:

// lib/languages/language_dart.g.dart (or any name)
const languageDart = <String, Map<String, String>>{
  'en': {
    'dir': 'ltr',
    'logo.title': 'My App',
    'greeting': 'Hello, World!',
    'example.params': 'My name is {name}, my age is {age}',
  },
  'fa': {
    'dir': 'rtl',
    'logo.title': 'برنامه من',
    'greeting': 'سلام دنیا!',
    'example.params': 'نام من {name} است، سن من {age} سال است',
  },
};
FinchConfigs configs = FinchConfigs(
  languageSource: LanguageSource.dart,
  dartLanguages: languageDart,
);

The finch serve watcher can convert JSON files to a Dart map automatically. The generated file is written to language_dart.g.dart.

Translating in Controllers

Use the .tr extension on any key string. .write() renders the final translated string:

// Simple translation
String text = 'logo.title'.tr.write();

// With named parameters
String text = 'example.params'.tr.write({'name': 'Alice', 'age': 30});

// With positional parameters (array) — there is no plain `.writeArr()`;
// pass the current language explicitly to `writeByLangArr`
String text = 'example.params.arr'.tr.writeByLangArr(rq.getLanguage(), ['Alice', 30]);

Pass translated strings to templates:

rq.addParams({
  'greeting': 'greeting'.tr.write(),
  'userLine': 'example.params'.tr.write({'name': 'Alice', 'age': 30}),
});
return rq.renderView(path: 'pages/home');

Translating in Templates

Use {{ $t('key') }} for inline translation:

<h1>{{ $t('logo.title') }}</h1>
<p>{{ $t('example.params', {'name': user.name, 'age': user.age}) }}</p>
<p>{{ $t('example.params.arr', ['Alice', 30]) }}</p>

Language Switching

Language is determined in this order:

  1. First path segment if it matches a known language code (e.g., /fa/home)
  2. lang data field on API endpoints
  3. language cookie
  4. language session key
  5. Default from settings

Whatever value is resolved is always validated against FinchConfigs.languages; if it isn't one of the configured languages (e.g. an invalid or stale cookie), Finch falls back to 'en'.

Change language from a controller:

rq.changeLanguege('fa');

In a template, generate a URL that switches language:

<a href="{{ $e.urlToLanguage('fa') }}">فارسی</a>
<a href="{{ $e.urlToLanguage('en') }}">English</a>

Available Languages in Templates

{% for lang in $e.langs %}
  <a href="{{ $e.urlToLanguage(lang.code) }}">{{ lang.label }}</a>
{% endfor %}

Each entry: { code: 'en', label: 'English', contry: 'United States' } — note the label/contry fields are empty/unresolved unless you've added the language.<code>_label/language.<code>_contry keys described in Template Events.

TString — Translation Objects

TString wraps a key and lets you defer translation:

var ts = TString('example.params');
rq.addParam('exampleTString', ts.write());

// Or using the .tr shorthand:
rq.addParam('examplePathString', 'example.path'.tr.write());

Positional (Array) Parameters

Instead of named {name} placeholders, a translation string can use positional {0}, {1}, … placeholders:

{
  "example.params.arr": "My name is {0}, my age is {1}"
}

Fill them in from a template with a list argument to $t:

<p>{{ $t('example.params.arr', ['Alexandre', 30]) }}</p>

Or from Dart, with writeByLangArr (there is no plain .writeArr() shorthand — see Translating in Controllers above):

rq.renderString(text: 'example.params.arr'.tr.writeByLangArr(rq.getLanguage(), ['Alexandre', 30]));