Internationalization (apback)

Backend notification and email strings use i18next with locale files under locales/.

Layout

Path Content
locales/en/apback.json English source
locales/{de,fr,it,es-US}/apback.json Translations only (Weblate + bin/translate.mjs)
locale-src/{de,fr,it,es-US}/apback.json English snapshot for machine-translation diffing (CI/local only)

Community locales (es, pt, ta, etc.) have no sidecars. Only en, fr, de, it, pt, and ta are registered at runtime (lib/apbackI18n.ts via lib/api/i18n.ts / initi18n, and the same module for crons).

Game titles in notification bodies use @abstractplay/gameslib apgames:names.{uid} bundles (imported from gameslib/locales/*/apgames.json in initi18n). localizedGameName() in lib/gameDisplayName.ts resolves the title after changeLanguageForPlayer.

Scripts

npm run prune-locales              # Drop stale keys from de/fr/it/es-US + locale-src
node bin/translate.mjs locales/en/apback.json   # Gemini translate de/fr/it/es-US
node bin/check-locale-readiness.mjs               # Audit vs English
node bin/split-locale-src.mjs                    # One-time _src_* extraction (reference)

CI (develop deploy)

On develop only, the deploy workflow prunes stale keys, runs bin/translate.mjs with GEMINI_API_KEY, auto-commits updated locales/{de,fr,it,es-US}/ and locale-src/ files, and verifies locale-src was committed. main deploy does not call Gemini; it ships whatever locales are already on the branch.

Weblate

Weblate syncs locales/ only. locale-src/ is never in scope, so PRs cannot strip translation-tracking metadata. Close any in-flight Weblate PRs that remove _src_* keys before merging the locale-src migration.