i18n: the text an application shows, in one place
import "gt:i18n" (or the relative path of the submodule). Pure Odin, no platform code, any target. Every text
the user reads goes through i18n.tr, so an application can be translated without going through its code again.
The package is named tk_i18n (the directory is i18n): an application that keeps a thin i18n alias package of its own
needs the two names to differ, because Odin package names are unique within a build. With no such alias, import "gt:i18n"
binds it as i18n all the same.
tr is a procedure group: the compiler picks the form from the arguments.
label := i18n.tr("Open in a new tab") // a text
close := i18n.tr("tab strip", "Close") // a translator's context, then the text
text := fmt.tprintf(i18n.tr(count, "%d request", "%d requests"), count) // singular, plural, by countThe English text is the key. English is the only language for now: each call returns its argument, a string literal in the
executable's read-only data, so a call costs nothing — no lookup, no allocation, no copy — which is what keeps a call in
an interface drawn every frame free. The returned bytes are the same bytes (raw_data(tr(s)) == raw_data(s)).
The three forms
| Call | For |
|---|---|
tr(text) | a plain label or sentence |
tr(context, text) | the same English word with two meanings (tr("tab", "Close") vs tr("dialog", "Close")); the context is for the translator and is never shown |
tr(count, one, other) | a count-dependent form; the caller never writes count == 1, because the choice of form belongs to the language (Polish, Russian and Arabic have several; French reads 0 as singular) |
What a language will be
So that it never costs more than an identity return (the interface is immediate: tr runs for every label, every frame, so a
lookup must not hash the text or walk a map of heap strings):
- one blob, embedded in the executable (
#loadof a repository file: the release stays one file), holding every translated text one after the other; no string of it is allocated on its own, and the blob is not freed or moved; - one flat table, open addressing, in one allocation: the key is the address and the length of the English literal (the pointer is the identity of a literal: a hash of two machine words, not of the characters), the value is the address and the length of the translation in the blob; a text with no entry returns itself;
- changing the language swaps the table, nothing else moves; the English path stays the identity.