-
Notifications
You must be signed in to change notification settings - Fork 0
Localization
hailsDotGO is fully translatable. UI strings live in flat JSON locale files compiled into the binary, templates look strings up with a T function, and a community translation workflow (Translator-Workspace) layers approved edits on top at runtime. This page is the contributor reference for how the i18n system in internal/i18n/i18n.go works and how to add keys and languages.
The embedded locales live in internal/i18n/locales/ and are compiled in via go:embed:
-
en.json, English, the source of truth -
de.json, German -
es.json, Spanish -
fr.json, French -
ja.json, Japanese
Keys are flat dot notation strings, namespaced by feature: nav.raids, raids.title, events.starts_in, admin.tab.settings, translate.filter.pending. There is no nesting; every file is a single JSON object of string to string.
en.json defines the complete key set. Other locales may be partial: any key a locale does not define falls back transparently.
TFunc(lang) returns a per request translation function. For each key it checks, in order:
- Pending edit overrides: only in translator preview mode, overlaying the translator's own unreviewed edits
- Disk overlay: approved community edits for the locale (see below)
-
Embedded locale: the compiled in
xx.json -
Embedded English: the compiled in
en.json -
The key itself: a last resort that makes a missing key visible as
some.keyin the UI
So a missing translation shows English, and a key missing even from en.json shows the raw key name.
Server rendered templates call the injected function directly:
<h1>{{T "raids.title"}}</h1>The bundled TypeScript pages cannot call T, so each template's scripts block defines an inline object of pre-translated strings that the bundle reads as a global. base.html defines JSC, the shared js.common.* strings used by shared modules like the Pokemon picker and the counters renderer, and injects SITE_LANG, which the shared game data module uses to pick localized Pokemon names. Page-specific objects follow the same pattern: RD in raids.html, DP in dps.html, PV in pvp.html, SH in shinies.html, EV in events.html, RF and RF2 (plus the RAID_CTX context object) in trainers.html, and TL_STRINGS in translate.html.
The active language comes from the lang cookie (set via the /lang switcher form and persisted to the user row when logged in). Translators previewing a locale use the tl_preview query parameter and cookie instead; the cookie is only honored for iframe requests, so the preview never leaks into normal browsing.
Two database backed mechanisms extend the embedded files without a rebuild:
-
The
localestable controls which languages appear in the public switcher (enabledflag) and records community locales created from the Translator-Workspace. A runtime locale starts from English and is translated entirely through overlays. -
Overlay files live in the server's working
locales/directory (not the embedded one). They are sparse: only keys explicitly approved through the translator workflow. On approval the overlay is backed up and rewritten atomically, then hot applied in memory. At startup, overlay keys that no longer exist in the embeddeden.jsonare dropped as stale, so a new build's strings are never shadowed accidentally.
Approved overlays can be exported per locale or synced back to the repository as a pull request; once merged, the next build embeds them and the overlay becomes redundant. See Translator-Workspace for that flow.
Translation work survives an ordinary deploy without any extra step:
-
Pending edits are rows in the
translation_editstable (status='pending'). The deploy script never touches the database, so unreviewed submissions are always still there after an update. -
Approved overlays are sparse files in the server's working
locales/directory. The deploy script only replaces a fixed set of tracked paths (binary, templates, static assets) and never deletes anything in the install directory, so overlay files are left untouched andInitreloads them when the service restarts.
Two edge cases used to risk losing translated strings; both are now hardened:
-
Renaming or removing an English key. Overlays and submissions are validated against the embedded
en.jsonkey set, so a removed or renamed key drops out of the live overlay on the next start. Rather than discard the translated text,Initnow archives it tolocales/backup/<lang>_stale_<timestamp>.jsonand rewrites the overlay so the same keys are not archived again on later restarts. The text is therefore recoverable. One caveat remains: a still-pending edit for a removed key can no longer be approved, so treat an English key rename as a breaking change and keep the old key or migrate alongside it. -
Overlays exist only on the server. The working
locales/directory is not in version control. The server now auto-mirrors approved overlays to the GitHub sync branch: shortly after startup, on a periodic backstop, and (debounced) whenever an edit is approved. This runs only whenGITHUB_TOKENandGITHUB_REPOare configured; the manual sync button remains available either way. Approved translations therefore reach the repository without anyone remembering to press a button, and survive a server rebuild.
- Add the key and its English text to
internal/i18n/locales/en.json. This must come first: submissions and overlays are validated against the embedded English key set. - Reference it from a template with
{{T "my.new.key"}}, or add it to the page's inline strings object if a script needs it. - Optionally add translations to the other locale files in the same commit; otherwise the key simply shows English until translators catch up.
There are two routes:
-
File route: create
internal/i18n/locales/xx.json(a two letter code) with whatever keys are translated and rebuild. Theinit()loader picks up every embedded JSON file automatically, with no code change. The language stays out of the public switcher until an admin enables it in the locales panel. - Workspace route: a translator creates the locale from the Translator-Workspace with no rebuild at all. It registers as a runtime locale, starts hidden, gets translated through reviewed edits, and can later be promoted to an embedded file via the GitHub sync.
The locale JSON files are embedded with go:embed and parsed by Go's encoding/json, which rejects a leading byte order mark. The files must be UTF-8 without a BOM. In particular, PowerShell's Set-Content -Encoding utf8 (Windows PowerShell 5.1) writes a BOM and will break the build or crash the service at startup. Use an editor or tool that writes plain UTF-8, and when in doubt validate by building and running the Go parser rather than trusting ConvertFrom-Json, which tolerates the BOM that Go rejects.
Repository | Live site | hailsDotGO is a fan-made project, not affiliated with, endorsed by, or connected to Niantic or The Pokémon Company. Game data comes from community sources credited on the Data Sources page.
Start Here
Features
- Raids and Counters
- DPS Calculator
- IV Calculator
- PvP IV Ranker
- Events
- Shiny Tracking
- Trainer Directory
- Raid Finder
- Social Features
- Trust and Awards
- Bug Reports
- Player Reports
- Store
- Accounts and Roles
- Admin Guide
Self-Hosting
Development
Translations