Internationalization¶
Status: ๐ง Partial โ Backend translation API, database models, and both frontends are wired for i18next with all 6 languages; โ
MultilingualAIServiceis now called fromfirst_login_service.py(lazily, with sentinel guard) and fromaria_personal_intelligence_service.py. โ Player-client shipsnpm run extract-strings/npm run validate-translations; โ admin-ui shipsnpm run i18n:extract/npm run i18n:validateon tip46bce720(services/admin-ui/scripts/extract-strings.jsandvalidate-translations.js). โ Reserved namespaces (ai,marketing,errors,validation) are loaded on tip (46bce720) by both frontends โ player-clientns: ['common','game','auth','ai','marketing','errors','validation']; admin-uins: ['common','admin','auth','ai','marketing','errors','validation']. Remaining gaps: DB not seeded with all 510 keys in non-English locales on a fresh deploy; German partial. (updated 2026-08-17; tip stamp re-verified 2026-08-21 vs46bce720โ Admin Soft-ORDER #789 โ Soft-HOLD unlock.)
Six-language i18next setup spanning the player client, admin UI, AI dialogue, and translation-management endpoints (English / Spanish / French / Portuguese / Chinese launch-complete; German Partial). Translations ship as bundled JSON namespaces and are also exposed via a database-backed admin API for live editing.
1. Languages¶
| Language | Code | Keys | Status |
|---|---|---|---|
| English | en |
510 | Base (100%) |
| Spanish | es |
510 | Complete (100%) |
| French | fr |
510 | Complete (100%) |
| Chinese Simplified | zh |
510 | Complete (100%) |
| Portuguese | pt |
510 | Complete (100%, Brazilian conventions) |
| German | de |
~135 | Partial โ declared in services/player-client/src/i18n.ts |
Four content namespaces (common, auth, admin, game) hold the 510-key English inventory (table in ยง2). Both frontends on tip 9dc7ed12 also load reserved namespaces ai, marketing, errors, validation (7 ns entries in each i18n.ts). Those reserved namespaces are loaded at runtime and are not included in the 510-key content total โ do not invent reserved-ns key counts, and do not read this page as โthe product has only four namespaces.โ
Translations were authored via AI linguistic generation and adapted for gaming terminology (e.g. Comandante / Commandant / ๆๆฅๅฎ / Comandante; Nave Exploradora / Vaisseau รclaireur / ไพฆๅฏ่น / Nave Explorador).
2. Delivery model¶
Translations live in the gameserver database (translation_keys table) and are served to clients over HTTP. The frontends use i18next-http-backend to fetch namespaces lazily from /api/v1/i18n/{lang}/{namespace}; results are cached in localStorage with a versioned cache-bust key.
Namespace breakdown:
| Namespace | Keys (English base) |
|---|---|
common |
121 |
auth |
69 |
admin |
154 |
game |
166 |
Reserved namespaces (ai, marketing, errors, validation) are in the i18n.ts ns arrays on both frontends on tip 9dc7ed12 but have no key counts in this table (not part of the 510-key content total).
Per-language frontend wiring lives at:
- services/admin-ui/src/i18n.ts + components/common/LanguageSwitcher.tsx
- services/player-client/src/i18n.ts + components/common/LanguageSwitcher.tsx
Admin LanguageSwitcher honesty (tip 46bce720): tip LanguageSwitcher.tsx no longer hardcodes completionPercentage: code === 'en' ? 100 : 0 for non-English launch locales. Launch-complete codes (en/es/fr/zh/pt via LAUNCH_COMPLETE_CODES) get static 100% (staticCompletion), then the switcher overlays GET /api/v1/i18n/admin/progress/{code} when the response yields a finite percent (fallback comment: launch-complete locales stay 100%, never a 0% bar). Do not read this page as โAdmin shows 0% for Complete localesโ โ that gap is closed on tip. Residual honesty: German remains Partial in the language table; DB seed gaps on a fresh deploy can still make live progress differ from the static 100% fallback. Also residual on tip 46bce720: the progress overlay catch is empty โ 403/429 keep static 100% and can masquerade as launch-complete green (same Admin #1336 / LEG-1265 land path as admin-ui.md ยง i18n).
PC Soft-HOLD tip-pending (tip 46bce720 โ not tip-shipped; do not remint): Fibril #1210 โ player-client LanguageSwitcher API-down fallback still uses zh-CN (runtime zh) and completionPercentage: 0 for non-en (LanguageSwitcher.tsx:58โ59). Fibril #1209 โ services/player-client/scripts/validate-translations.js KNOWN_NAMESPACES remains {common, game, auth} while i18n.ts ns has 7 entries. Ops ledger: player-client.md ยง i18n.
Both apps use i18next 23.16, react-i18next 13.5, i18next-http-backend, and the browser language detector.
Language detection¶
Detection order is ['localStorage', 'navigator', 'htmlTag'] โ the player's persisted choice wins, then the browser's navigator.language, then a <html lang="โฆ"> declaration as last resort. The chosen code is cached in localStorage under the key i18nextLng.
English-fallback payload¶
When the backend /i18n/{lang}/{namespace} fetch fails (network error, server unreachable, or backend not yet seeded), the player client falls back to a hardcoded 14-key English minimum-viable vocabulary (welcome, dashboard, login, logout, loading, error, ship, sector, credits, cargo, trade, combat, planets, galaxy) so every screen still renders something legible. This is the only player-visible string fallback โ once any namespace successfully loads from the backend, the cache replaces the hardcoded payload.
3. Backend¶
Service & models¶
services/gameserver/src/models/translation.pyโlanguages,translation_keys,user_language_preferencestables.services/gameserver/src/services/translation_service.pyโ fallback handling, caching, user-preference persistence.services/gameserver/src/services/multilingual_ai_service.pyโ multilingual context for AI responses.
API (/api/v1/i18n/)¶
Public
| Method | Path | Purpose |
|--------|------|---------|
| GET | /i18n/languages | List supported languages |
| GET | /i18n/detect | Auto-detect from request |
| GET | /i18n/{lang} | All translations for a language |
| GET | /i18n/{lang}/{namespace} | Namespace-scoped translations |
User
| Method | Path | Purpose |
|--------|------|---------|
| GET | /i18n/user/preference | Get preferred language |
| POST | /i18n/user/preference | Set preferred language |
| GET | /i18n/user/ai-context | Get AI language context |
Admin
| Method | Path | Purpose |
|--------|------|---------|
| GET | /i18n/admin/progress/{lang} | Translation completion |
| POST | /i18n/admin/translation/{lang}/{ns} | Update single translation |
| POST | /i18n/admin/bulk/{lang}/{ns} | Bulk import |
| POST | /i18n/admin/initialize | Initialize system |
11 endpoints total (4 public, 3 user, 4 admin).
Database¶
languages(id, code, name, native_name, direction, is_active, completion_percentage)
translation_keys(id, key, language_id, namespace_id, value, context, is_verified)
user_language_preferences(id, user_id, language_id, manual_override)
4. Adding new translatable content¶
1. Pick a namespace¶
common (shared UI), auth, admin, game, plus tip-loaded reserved namespaces ai, marketing, errors, validation (see Status).
2. Add the key¶
Add it via the admin API or import (English base, admin namespace):
{
"newFeature": {
"title": "New Feature",
"description": "Description of the new feature",
"actions": { "enable": "Enable", "disable": "Disable" }
}
}
3. Use it in components¶
import { useTranslation } from 'react-i18next';
const { t } = useTranslation('admin');
return <h2>{t('newFeature.title')}</h2>;
Patterns:
- Interpolation: t('welcome', { name: 'John' })
- Pluralization: t('items', { count: 5 })
- Fallback: t('key', { defaultValue: 'Default' })
4. Push to the database (optional)¶
curl -X POST /api/v1/i18n/admin/bulk/en/admin \
-H "Authorization: Bearer $TOKEN" \
-d '{"translations": {...}, "overwrite": true}'
The database path supports admin-managed dynamic translations; the file path is the day-to-day default for static text bundles.
5. Translator workflow¶
Admin UI¶
- Log into admin with translator permissions.
- Settings โ Translation Management.
- Pick language + namespace.
- Edit; check "Verified" once reviewed.
Direct API¶
curl -X POST /api/v1/i18n/admin/translation/es/admin \
-H "Authorization: Bearer $TOKEN" \
-d '{
"key": "dashboard.title",
"value": "Panel de Control Central",
"context": "Main dashboard page title"
}'
Quality bar¶
- Translate meaning, not words.
- Use established gaming terminology.
- Watch character-length constraints on UI elements.
- Preserve tone / formality per language conventions.
- Test in actual interface.
Per-language conventions¶
| Language | Notes |
|---|---|
| Spanish | Formal usted for system messages; balance formal/informal in UI; consider regional variants |
| French | Maintain language purity; correct accent marks; formal address; consider French-Canadian forks if needed |
| Chinese | Simplified character set; appropriate formality; gaming-context terminology |
| Portuguese | Brazilian conventions by default; gaming idioms |
6. Community contributions¶
- Create account, request translator permissions.
- Complete style-guide training.
- Start with simple/common translations.
- Peer review in language teams.
- Native-speaker validation before promoting to verified.
Recognition: translation credits, language-team leaderboards, contributor badges.
7. Progress tracking¶
curl -X GET /api/v1/i18n/admin/progress/es \
-H "Authorization: Bearer $TOKEN"
Returns completion %, verified vs unverified counts, per-namespace breakdown, last-updated timestamps.
8. Tooling¶
โ
Shipped (player-client) โ services/player-client/package.json on tip 9dc7ed12:
npm run extract-stringsโ scanssrc/**/*.{ts,tsx}fort('โฆ')call sites and emits a sorted JSON array of unique keys to stdout. Use as a draft checklist when adding new content.npm run validate-translationsโ checks that everyuseTranslation()call declares a namespace in the script's hardcoded known set (common/game/authfor player-client;common/admin/authfor admin-ui), and validates the structural integrity of any static seed JSON files found undergameserver/i18n/en/. Exits non-zero on hard errors (parse failures, unknown namespaces). Runtimei18n.tson both apps loads four additional reserved namespaces (ai,marketing,errors,validation); the offlineKNOWN_NAMESPACESset does not include those four โ that script-vs-runtime gap is documented here, not a claim that the scripts already know all seven.
Scripts live at services/player-client/scripts/extract-strings.js and services/player-client/scripts/validate-translations.js.
โ
Shipped (admin-ui) โ services/admin-ui/package.json on the same tip (script names differ from player-client; do not alias):
npm run i18n:extractโnode ./scripts/extract-strings.jsnpm run i18n:validateโnode ./scripts/validate-translations.js(KNOWN_NAMESPACES=common/admin/authonly; runtimei18n.tsstill loads the four reserved namespaces โ same script-vs-runtime gap as player-client)
Scripts live at services/admin-ui/scripts/extract-strings.js and services/admin-ui/scripts/validate-translations.js.
Note: Because translations live in the DB (not static locale files), key-set completeness can only be verified against a running gameserver. The server-side /i18n/admin/bulk/{lang}/{ns} endpoint validates shape on import; the validate-translations script covers the offline/CI subset. Draft keys are added via the admin API or the Translation Management page.
Right-to-left languages are not currently in scope; adding one (e.g. Arabic) will require RTL layout work in both UIs.