i18n #
Description
i18n loads translations from a directory and looks them up by language and key.
Translation files
Both formats are read from the same directory, and may be mixed.
.tr
The primary format. Each entry is a key line, the text below it, and a ----- separator:
msg_hello
Hello
-----
goods
goods|item|items
The file name is the language: translations/en.tr defines the en translations.
.json
Useful when translations are produced by an external localization tool, or when they are large enough to be worth splitting per feature.
translations/en.json defines the en translations, exactly like en.tr does:
{
"msg_hello": "Hello",
"menu": {
"file": "File",
"edit": { "undo": "Undo" }
}
}
Nested objects are flattened with ., so the above defines msg_hello, menu.file and menu.edit.undo.
A file inside a language subdirectory is namespaced by its own name, so translations can be split per feature. translations/zh/dashboard.json defines zh keys under dashboard.:
{ "title": "Dashboard", "widgets": { "clock": "Clock" } }
gives dashboard.title and dashboard.widgets.clock.
Non-string scalar values use their JSON representation, so 42, true, and null are loaded as "42", "true", and "null". Arrays are ignored.
When the same key is defined by both formats, the .tr value wins.
Usage
import i18n
println(i18n.tr('en', 'msg_hello'))
println(i18n.tr_plural('en', 'goods', 2))
tr and tr_plural read from the translations directory. Use load_tr_map_from_dir with tr_from_map / tr_plural_from_map to read from another directory.
Inline defaults and missing translations
The Option APIs tr_opt, tr_from_map_opt, tr_plural_opt, and tr_plural_from_map_opt return none for a missing or empty translation. Use or to supply display text at the call site:
label := i18n.tr_opt('zh', 'common.success') or { 'Successfully' }
items := i18n.tr_plural_opt('zh', 'goods', 2) or { 'Items' }
Each Option lookup accepts an optional final LookupConfig. Set default_language to try another language before the inline fallback, or choose a missing-key policy:
label := i18n.tr_from_map_opt(translations, 'zh', 'common.success',
default_language: 'en'
) or { 'Successfully' }
key := i18n.tr_opt('zh', 'missing', missing_key_behavior: .key) or { 'Fallback' }
blank := i18n.tr_opt('zh', 'missing', missing_key_behavior: .empty) or { 'Fallback' }
The default policy is .or_text, which returns none; .key returns the key and .empty returns a present empty string. A real translation always takes precedence. Configuration belongs to each call and does not change other lookups. The existing string APIs retain their key fallback and missing-translation diagnostic.
Embedding translations in the executable
Reading translations from a directory at run time means shipping that directory next to the program. To carry them inside the executable instead, embed the files with $embed_file and load them with load_tr_map_from_embedded:
import i18n
const translations = i18n.load_tr_map_from_embedded('translations', [
$embed_file('translations/en.tr'),
$embed_file('translations/pt-br.tr'),
$embed_file('translations/zh/dashboard.json'),
])
println(i18n.tr_from_map(translations, 'en', 'msg_hello'))
The first argument is the translations directory, spelled the way the embedded paths spell it. Each file's path inside it gives its language and key prefix, just as it does when the directory is read from disk.
[!NOTE]
Only a-prodbuild (or-os crossC output) puts the files' contents into the
executable. A development build keeps just their paths, to keep rebuilds cheap, and
reads the files from those paths when the translations are loaded. It works where it
was built, but panics on a machine without the files, so build anything you ship with
-prod.
Translations that come from somewhere else, such as a database or a download, can be loaded with load_tr_map_from_files, which takes a map from each file's path inside the translations directory (en.tr, zh/dashboard.json) to its text. The paths may use / or \ between their parts.
Formatting
new_formatter(tag) supports en/en-US, de/de-DE, and fr/fr-FR. Unsupported tags return an error. The formatter provides number_int, number, currency, percent, and date. Numbers use locale grouping and decimal separators. number(x, decimals) accepts 0 through 9 fractional digits, rounds the shortest decimal representation half away from zero, and normalizes negative zero. NaN and infinity retain their ordinary string representations.
Currency formatting recognizes USD, EUR, GBP, JPY, CHF, CNY, CAD, and AUD; unknown codes use the code as a symbol and two fractional digits. Negative amounts put the sign before a prefixed currency symbol. Percent formatting multiplies a ratio by 100 and uses one fractional digit. Dates omit time and use month/day/year for English, day.month.year for German, and day/month/year for French.
compare_collated and sort_collated use ASCII case folding, selected Latin accent and ligature mappings, and ignore combining marks U+0300 through U+036F. Original UTF-8 byte order breaks ties. Other characters retain their code point order. This is a deterministic limited Latin ordering, not full Unicode or locale-specific collation.
Constants #
const default_translations_dir = 'translations'
fn compare_collated #
fn compare_collated(a string, b string) int
compare_collated compares Latin strings with ASCII case and selected Latin accent folding (ä sorts with a), ignoring U+0300..U+036F combining marks. Original byte order breaks ties; this is not full Unicode locale collation.
fn load_tr_map #
fn load_tr_map() map[string]map[string]string
load_tr_map loads all .tr files from the default translations directory.
fn load_tr_map_from_dir #
fn load_tr_map_from_dir(dir string) map[string]map[string]string
load_tr_map_from_dir loads all .tr and .json files from dir into a lang -> key -> text map. A .tr entry wins when the same key is defined by both formats, since .tr is the primary format.
fn load_tr_map_from_embedded #
fn load_tr_map_from_embedded(dir string, files []embed_file.EmbedFileData) map[string]map[string]string
load_tr_map_from_embedded builds translations from files embedded in the program with $embed_file, so it does not depend on a translations directory being installed next to the executable:
const translations = i18n.load_tr_map_from_embedded('translations', [
$embed_file('translations/en.tr'),
$embed_file('translations/zh/dashboard.json'),
])
dir is the translations directory, spelled the way the embedded paths spell it. A file's path inside it decides its language and key prefix, as it does for load_tr_map_from_dir. A file outside dir is read as if it were directly in it.
Only a -prod build (or -os cross C output) puts the files' contents into the executable. A development build keeps just their paths, to keep rebuilds cheap, and reads the files from those paths when the translations are loaded. It works where it was built, but panics where the files are missing, so build anything you ship to other machines with -prod.
fn load_tr_map_from_files #
fn load_tr_map_from_files(files map[string]string) map[string]map[string]string
load_tr_map_from_files builds translations from file contents instead of reading them from disk. files maps each file's path inside the translations directory (en.tr, en.json, zh/dashboard.json) to its text, and is read exactly as load_tr_map_from_dir reads the same files from a directory. A path may use either / or \ between its parts, so one built with os.join_path works everywhere, and its . and .. parts are resolved before the language is taken from it.
fn new_formatter #
fn new_formatter(tag string) !Formatter
new_formatter creates a Formatter for a BCP-47-ish tag. Supported tags: 'en', 'en-US', 'de', 'de-DE', 'fr', 'fr-FR'. Anything else is an error.
fn sort_collated #
fn sort_collated(mut arr []string)
sort_collated sorts strings in place using compare_collated.
fn tr #
fn tr(lang string, key string) string
tr returns the translation for key from the default translations directory.
fn tr_from_map #
fn tr_from_map(translations map[string]map[string]string, lang string, key string) string
tr_from_map returns the translation for key from translations.
fn tr_from_map_opt #
fn tr_from_map_opt(translations map[string]map[string]string, lang string, key string, config LookupConfig) ?string
tr_from_map_opt returns a non-empty translation, checking the default language next. Missing translations return none unless config selects the key or an empty string.
fn tr_opt #
fn tr_opt(lang string, key string, config LookupConfig) ?string
tr_opt returns a translation from the default directory, or none when it is missing.
fn tr_plural #
fn tr_plural(lang string, key string, amount int) string
tr_plural returns the pluralized translation for key from the default translations directory.
fn tr_plural_from_map #
fn tr_plural_from_map(translations map[string]map[string]string, lang string, key string, amount int) string
tr_plural_from_map returns the pluralized translation for key from translations.
fn tr_plural_from_map_opt #
fn tr_plural_from_map_opt(translations map[string]map[string]string, lang string, key string, amount int, config LookupConfig) ?string
tr_plural_from_map_opt returns a pluralized translation, with the same fallback and missing-key policy as tr_from_map_opt.
fn tr_plural_opt #
fn tr_plural_opt(lang string, key string, amount int, config LookupConfig) ?string
tr_plural_opt returns a pluralized translation from the default directory, or none.
fn MissingKeyBehavior.from #
fn MissingKeyBehavior.from[W](input W) !MissingKeyBehavior
from returns the MissingKeyBehavior value of an integer or of a field name, or an error if there is none.
enum MissingKeyBehavior #
enum MissingKeyBehavior {
or_text
key
empty
}
MissingKeyBehavior controls an Option lookup when no non-empty translation is available.
struct Formatter #
struct Formatter {
tag string
group_sep string
decimal_sep string
date_order string // 'mdy' | 'dmy' | 'ymd'
date_sep string
currency_after bool
currency_space bool
percent_space bool
}
Formatter formats numbers, currency, percentages and dates for one locale, and sorts strings in locale-insensitive collation order. It covers the formatting half of Swift Foundation / java.text; message translation stays in the tr_* functions.
fn (Formatter) number_int #
fn (f &Formatter) number_int[T](x T) string
number_int formats an integer with locale grouping: 1234567 -> '1,234,567' (en), '1.234.567' (de), '1 234 567' (fr).
fn (Formatter) number #
fn (f &Formatter) number(x f64, decimals int) string
number formats a float with decimals fractional digits and locale grouping and decimal separator: 1234567.891 -> '1,234,567.89' (en, 2 decimals). Rounding is half away from zero on the shortest decimal representation. NaN and infinities pass through without grouping.
fn (Formatter) currency #
fn (f &Formatter) currency(x f64, code string) string
currency formats an amount in the given ISO currency code with that currency's usual decimals: USD/EUR/GBP 2, JPY 0. Placement follows the locale: '$1,234.50' (en), '1.234,50 €' (de). Unknown codes fall back to the code itself with 2 decimals.
fn (Formatter) percent #
fn (f &Formatter) percent(x f64) string
percent formats a ratio: 0.123 -> '12.3%' (en), '12,3 %' (de/fr).
fn (Formatter) date #
fn (f &Formatter) date(t time.Time) string
date formats a calendar date without time: 2024-03-05 -> '03/05/2024' (en), '05.03.2024' (de), '05/03/2024' (fr).
struct LookupConfig #
struct LookupConfig {
pub:
missing_key_behavior MissingKeyBehavior
default_language string
}
LookupConfig configures Option lookups without changing the legacy string APIs.
- README
- Constants
- fn compare_collated
- fn load_tr_map
- fn load_tr_map_from_dir
- fn load_tr_map_from_embedded
- fn load_tr_map_from_files
- fn new_formatter
- fn sort_collated
- fn tr
- fn tr_from_map
- fn tr_from_map_opt
- fn tr_opt
- fn tr_plural
- fn tr_plural_from_map
- fn tr_plural_from_map_opt
- fn tr_plural_opt
- fn MissingKeyBehavior.from
- enum MissingKeyBehavior
- struct Formatter
- struct LookupConfig