Skip to content

How it works

View .md

Most i18n libraries split a text in three: a key in the code, the English text in one JSON file, the translations in others. lang keeps them together.

t('Save', { de: 'Speichern' })

Save is both the English text and the key you read it by: text.Save. You never invent a key like common.actions.save, and the code reads like the UI it renders. When a text is long or its wording changes often, give it a custom key.

The second argument of t() is typed as a record of every other configured language. A missing language, an unknown one or a string where a function is expected are type errors at the line that has the problem. There is no extraction step and no “missing key” warning at runtime, because a missing translation never compiles.

A text’s type is a union of all its translations:

const save = useLang().common.Save
// ^? '[en]: Save' | '[de]: Speichern'

Hovering any text in the editor shows every language. At runtime the value is the plain text of the current language ('Speichern'), and it is assignable to string.

useLang() resolves a subtree for one language and caches it per subtree and language. The result is frozen and keeps its identity, so calling useLang in every component is fine, and React can use the result as a dependency.

Translations are TypeScript. They are bundled like any other module, tree-shaken like any other module, and refactored with the editor’s rename. Splitting them into files is up to you, see Organizing translations.