Gameplane's dashboard and its server messages are translated into other
languages. Each language is a language pack: a folder under
i18n/locales/ with two JSON files. You do not need to write code to add
one. This guide covers the folder layout, the message rules, how to check
your work, and when a language shows in the picker.
Translations are welcome as a partial pack. A pack merges even when it is not finished. It stays hidden from users until enough of it is translated.
i18n/locales/
en/ English reference. Generated. Do not hand-edit app.json.
app.json
meta.json
fr/ your language, tag "fr"
app.json
meta.json
To start a language, copy the whole i18n/locales/en/ folder to
i18n/locales/<tag>/. Use a BCP 47 tag such as fr, pt-BR, or sr-Latn.
The folder name must equal the tag in meta.json.
app.json maps each message id to an entry:
{
"condition.addressassignment.assigned": {
"defaultMessage": "{assignedFrom, select, pool {Address {address} assigned from pool ''{pool}''.} other {Address {address} assigned.}}",
"description": "AddressAssignment condition once an address is assigned. assignedFrom is pool or default."
}
}- Replace
defaultMessagewith your translation. Keepdescriptionas it is. The description is for translators and is not shown in the app. - Keys are sorted by id. Keep them sorted when you add or move entries.
- Use 2-space indentation, UTF-8, and end the file with a newline.
- Do not invent ids. An id that English does not have is an error. You may leave out any id you have not translated yet.
sourceHashis written by the checker. It records which English text your translation was made from. Leave it alone when you edit by hand.
meta.json describes the language:
{
"tag": "fr",
"nativeName": "Français",
"englishName": "French",
"direction": "ltr",
"credits": [{ "name": "Ada Example", "url": "https://example.org" }]
}tagmust equal the folder name.nativeNameis the name of the language in that language.englishNameis the name in English.directionisltrorrtl. A language withrtlmerges but is not listed in the picker yet.creditsis optional. Add yourself here.- Unknown keys are rejected.
Messages use ICU MessageFormat. Follow these rules:
-
Keep every placeholder that English has. Use the same names, such as
{name}. The order can change. -
Do not add a placeholder that English does not have.
-
Date and number formats keep their form, such as
{d, date, short}and{n, number}. -
A literal apostrophe next to a placeholder is written twice, as in
''{pool}''. Copy the English quoting. -
Plurals follow the rules of your language. Use the
othercategory. It is required. For example:{n, plural, one {# serveur} other {# serveurs}} -
{x, select, ...}also works, as in the example above. Keep theotheroption. -
You may use
<b>…</b>and<link>…</link>tags. Do not use any other tag, HTML entity,javascript:link, or<script. The checker rejects them.
- Use a neutral, plain tone. Keep sentences short.
- Do not translate these terms. Keep them as written in English: Gameplane, GameServer, GameTemplate, Backup, BackupSchedule, Restore, Cluster, Module, ModuleSource, NetworkCapture, Kubernetes, LoadBalancer. These are resource kinds and product names.
- Do not invent fixed translations for product words. Ask in the pull request if a term is unclear.
- Keep button labels short. Match the length of the English text when you can, because the layout is tight.
Run the checker from the i18n/ folder to see findings on your pack:
cd i18n
go run ./cmd/i18n-check localesThe checker exits with one of these codes:
| Exit | Meaning |
|---|---|
| 0 | No errors found |
| 1 | At least one finding |
| 2 | Usage error, or a required file could not be read |
Findings use these codes:
| Code | Severity | What it means |
|---|---|---|
malformed-pack |
error | app.json is not valid JSON or has the wrong shape |
invalid-meta |
error | meta.json is missing a field, has an unknown key, or the tag does not match the folder |
icu-syntax |
error | The message does not parse as ICU MessageFormat |
missing-placeholder |
error | A placeholder from English is missing |
extra-placeholder |
error | A placeholder that English does not have |
markup |
error | A tag or entity that is not allowed |
unknown-id |
error | The id is not in English. Remove the entry |
needs-review |
info | English changed after your translation was made. The entry still shows |
Partial packs are fine. A missing id is not a finding. English shows for it.
The checker has more options:
--base <dir>compares completeness with an older copy of the packs.--summary <file>writes a markdown summary.--stampaddssourceHashto translated entries that lack one.--manifest <file>writes the language list as JSON.
You can run the checker on your machine to see findings early. It is not a test suite. CI runs it on every pull request. The job summary shows your pack's completeness before and after your change.
A language is listed in the picker only when both of these are true:
- At least 80% of the English messages have a usable translation.
Entries marked
needs-reviewcount as usable. - The direction is
ltr.
A pack under 80% still merges. It stays hidden until more of it is done. Maintainers can change the 80% threshold.
The dashboard reads the list of languages from a generated manifest. A pack
under 80% will not appear in the picker. To rebuild the manifest and the
compiled packs, run these commands in web/ (they need Go installed), then
start the dev server with make web-dev:
cd web
npm run i18n:manifest
npm run i18n:compileThe build keeps only the entries the checker accepts. Any other entry shows English.
- Open a pull request with your pack. Use a
feat:ordocs:title, for examplefeat: add French language pack. - CI runs the checker and posts the completeness in the job summary.
- A maintainer or a designated language reviewer checks the wording. The pull request needs one human approval before it merges.
- A pull request that changes only
i18n/locales/**getsarea: sharedfrom the auto-label workflow. If its title has no type prefix, it also getstype: docs. Add adocs:prefix to be sure.
You can also translate in the browser on Hosted Weblate:
https://hosted.weblate.org/projects/gameplane/. Only translations that a
human reviewer approves are merged. They arrive as a pull request from the
weblate/translations branch and pass the same checks.
Every user-visible string in the dashboard goes through intl. Do not
put English text straight into JSX. Get the helper from useOptionalIntl:
import { useOptionalIntl } from "@/lib/i18n/format";
const intl = useOptionalIntl();
intl.formatMessage(
{
id: "web.servers.deleted",
defaultMessage: "Server {name} was deleted.",
description: "Toast after a server is deleted. {name} is the server name.",
},
{ name: server.name },
);id,defaultMessageanddescriptionmust be string literals. The extractor reads them at build time.- Dashboard ids use
web.<path>.<slug>. Audit labels useaudit.*. Theserver.*,notify.*andcondition.*ids come from the API and the operator. Do not invent them in the dashboard. - The description tells a translator what the text is and where it
appears. Explain every
{placeholder}. - When you migrate a string, keep the rendered English the same.
- After you change text, run
cd web && npm run i18n:extract. - Commit
i18n/locales/en/app.jsonin the same commit as the code.
npm run i18n:migrate -- [--dry-run] <paths>wraps literal JSX text. It also wrapsaria-label,title,placeholderandaltvalues. Run it with--dry-runfirst.node scripts/i18n-migrate.ts --check srcreports what the lint rules would flag.
- ESLint with
eslint-plugin-formatjschecks all ofweb/src, except tests andsrc/test. - CI fails on extraction drift. It also fails on server and condition
drift. It fails on reference ids that no code uses (
i18n-check --used-ids).
Some text does not come from a literal in the dashboard. Use a helper for it:
- API errors:
serverMessageTextanderrorTextIntl. - Operator conditions:
conditionMessageTextinsrc/lib/i18n/conditionMessage.ts. English keeps the operator's message. - Module text:
useModuleTextanduseModuleTextsinsrc/lib/i18n/moduleText.ts. English uses the template text and fetches nothing.
- Contributing: the general pull request process.
i18n/specs.md: the design of the language packs.