Localization
Gopher speaks English and German. It has two mechanisms, because a manifest and a panel do not read text in the same way.
The panel: catalogs in lib/i18n/
| File | What it holds |
|---|---|
lib/i18n/en.ts | The English catalog. It is the reference for the other locales. |
lib/i18n/de.ts | The German catalog. |
lib/i18n/catalog.ts | The types. Catalog comes from the English catalog. |
lib/i18n/index.ts | The locale of the user, t(), and the plural forms. |
components/i18n.tsx | I18nProvider and useI18n(). |
components/LanguagePicker.tsx | The control in the header of the panel. |
A key is flat and has dots, for example status.signed-out.title. The dots make groups for a
reader; the code does not walk them as a path. A value is a string, or a set of plural forms that
Intl.PluralRules selects from. A {name} mark in a value is a placeholder. t() puts a value
in its place, and it gives a number to Intl.NumberFormat first.
A component calls useI18n() and never reads a catalog. Thus one context value re-renders the
whole panel when the user changes the language.
The completeness test is the compiler. lib/i18n/de.ts declares its constant as Catalog,
and Catalog comes from the keys of the English catalog. Thus npm run compile fails if a
translation has no value for a key, invents a key, or gives a string where a plural is necessary.
The compiler cannot test what a machine runs. tests/i18n.test.ts covers that instead: which
locale detectLocale reads from its sources, the guards, and how t() and the plural forms put
values into the text.
The manifest: public/_locales/
A manifest cannot read a TypeScript file. browser.i18n is the only mechanism that can localize
a manifest. Five strings live apart from the rest because of this. They are the name, the short
name, and the description, plus the titles of the icon and the keyboard command. The default_locale
key is en (see permissions and configuration).
Which language the panel shows
The setting is auto, en, or de, and it is in browser.storage.local with the other panel
state. auto reads two sources, in this order:
browser.i18n.getUILanguage(), the language of the browser interface. This is the same source that selects the text of the manifest, thus the name of the extension and the panel agree.navigator.languages, the content languages of the user. Gopher reads this source only when the first source names a language that Gopher does not have. A person can have an English interface and ask each page for German, and that is a statement about the language that the person reads.
Only the first subtag decides, thus de-AT gets the German catalog. If no source names a
language that Gopher has, the answer is English.
Two limits, and neither has a repair
- The text of the manifest obeys the browser, not the panel. The browser reads
_locales/before the extension starts, andbrowser.i18nhas no way to select a different language. Thus a German panel in an English browser keeps an English name in the list of extensions. The panel is the surface that the user reads, thus the panel gets the choice. - The first frame can be English.
browser.storage.localhas no synchronous read. The first render uses the language of the browser, and the panel renders again when storage answers. This is visible only to a user who selected a language that is not the language of the browser. The alternative is an empty first frame for every user.
To add a language
- Copy
lib/i18n/en.tstolib/i18n/{tag}.tsand translate each value. Declare the constant asCatalog. The compiler then names each key that is missing. - Add the tag to
LOCALESand the catalog toCATALOGSinlib/i18n/index.ts. - Add the name of the language, written in that language, to
LOCALE_NAMES. - Add
public/_locales/{tag}/messages.jsonwith the five manifest strings. - Run
npm run compileandnpm run build.
A language with more plural categories than English (Polish, Russian, Arabic) needs no change to
the English catalog. PluralMessage, in lib/i18n/catalog.ts, marks one and other as necessary,
and the other categories of the CLDR as optional.
What is not in a catalog
The name “Gopher” is a product name and does not change (see principles).
The message field of a failure (lib/messages.ts) is for a developer who reads the console;
the user reads the status.* text of the banner.