Internationalisation
SkyLab ships in English, 繁體中文 and 日本語. Four places carry translations, and the rule is the same everywhere: add every key to every language, and translate by hand.
Frontend
- Library: react-i18next. Catalogs live in
frontend/src/locales/{zh-TW,en,ja}/, one JSON file per namespace (common,components,personal,resource,teaching,system,network,ai,login,services). - Files are flat, one key per line, in CRLF. They are not the output of
JSON.stringify; edit them line by line and validate withJSON.parseafterwards. Tools that reformat the whole file produce noisy diffs. - Namespaces follow the page areas; a key is named after the component and the element (
UserGuide.monitoring.alerts.title). enandjaare loaded lazily;zh-TWis the fallback bundled with the app.- The in-app guided tours (
frontend/src/components/UserGuide/pageGuides/) reference keys underUserGuide.*; a new page needs its tour texts in all three languages too. - Language is chosen per browser (the "語言 / Language" item in the sidebar, or the picker in the setup wizard and the onboarding), stored in
localStorage(skylab.lang) and sent to the backend asAccept-Language.
Backend
- API messages (errors, validation, notifications) go through
app.core.i18n.t("<area>.<key>", **params). Catalogs are the files inbackend/app/locales/<lang>/, flatkey -> templatewithstr.formatplaceholders. All files of one language are merged into a single catalog, so the file a key lives in only groups related messages and keys must not collide across files. A key missing inenorjafalls back to zh-TW, then to the key itself. - The language of a request is resolved by
RequestContextMiddlewarefromAccept-Languageand kept in a context variable;t()reads it at raise time, so messages are translated where the error is created, not where it is rendered. - E-mails sent in the background and audit log entries are intentionally not translated: they have no request language.
Desktop client
SkyLab Connect keeps its catalogs in desktop-client/src/lang/; the same three languages, the same rule.
Public site
The introduction site in site/ has one catalog per language, site/src/locales/{zh-TW,en,ja}.json, including the SEO title and description. Each language is its own prerendered URL (/, /en/, /ja/), so the picker there links between pages instead of storing a choice. A test fails when the three files do not have the same keys. See Public site.
Documentation
Guides on this site exist in English and 繁體中文 (website/<page>.md and website/zh-TW/<page>.md). Commands, paths, tables and headings structure stay identical; prose is translated sentence by sentence. Pages that exist in one language only are marked in the sidebar.
Rules that are not negotiable
- No machine translation, no batch regular-expression replacement for
enandja: read the sentence in context and write it. Reviewers compare the three files. - Never leave a key out of a language: react-i18next falls back to the key name, which ends up in the UI.
- Do not translate identifiers, paths, product names or API routes.
- When UI text changes, update the tour text and the matching manual page in the same change.
Related
- Frontend style guide — components and layout rules that the translated text has to fit
- Development — running the frontend locally to check the result