Skip to content

Internationalization

Status: ๐Ÿšง Partial โ€” Backend translation API, database models, and both frontends are wired for i18next with all 6 languages; โœ… MultilingualAIService is now called from first_login_service.py (lazily, with sentinel guard) and from aria_personal_intelligence_service.py. โœ… Player-client ships npm run extract-strings / npm run validate-translations; โœ… admin-ui ships npm run i18n:extract / npm run i18n:validate on tip aee475ed6 (services/admin-ui/scripts/extract-strings.js and validate-translations.js). โœ… Reserved namespaces (ai, marketing, errors, validation) are loaded on tip (aee475ed6) by both frontends โ€” player-client ns: ['common','game','auth','ai','marketing','errors','validation']; admin-ui ns: ['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. (Re-verified 2026-08-31 vs origin/feat aee475ed6 โ€” tip delta PC Soft-ORDER #1251/#1252 TypeError densify only; i18n backend/frontend wiring unchanged.)

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 da4ec0e5): 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. โœ… Progress HTTP honesty tip-PRESENT via #778: 403/401 and 429 surface progressError alerts (LanguageSwitcher.tsx:75-88,117-120). 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. โœ… changeLanguage honesty tip-PRESENT via #1186 MERGED (mergeCommit 0987726c3; Admin LEG-3236): i18n.changeLanguage catch sets changeLanguageError TypeError densify + generic recoverable copy with role="alert" (LanguageSwitcher.tsx:102-133 on tip da4ec0e5) โ€” not console-only. See admin-ui.md ยง i18n.

PC Soft-HOLD #1210/#1209 CLOSED tip-PRESENT (tip 63a2cabb1): player-client LanguageSwitcher API-down / static fallback uses runtime code zh (not zh-CN) and STATIC_COMPLETION_PERCENT (en/es/fr/zh/pt=100, de=26) in buildStaticLanguages() (LanguageSwitcher.tsx:27-46). scripts/validate-translations.js KNOWN_NAMESPACES lists all 7 namespaces (common,game,auth,ai,marketing,errors,validation) matching i18n.ts ns. German remains Partial; DB-seed gaps on a fresh deploy can still diverge from static percents. 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_preferences tables.
  • 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

  1. Log into admin with translator permissions.
  2. Settings โ†’ Translation Management.
  3. Pick language + namespace.
  4. 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

  1. Create account, request translator permissions.
  2. Complete style-guide training.
  3. Start with simple/common translations.
  4. Peer review in language teams.
  5. 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 โ€” scans src/**/*.{ts,tsx} for t('โ€ฆ') 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 every useTranslation() call declares a namespace in the script's hardcoded known set (common/game/auth for player-client; common/admin/auth for admin-ui), and validates the structural integrity of any static seed JSON files found under gameserver/i18n/en/. Exits non-zero on hard errors (parse failures, unknown namespaces). On tip 63a2cabb1, player-client KNOWN_NAMESPACES already includes all seven runtime namespaces (common,game,auth,ai,marketing,errors,validation) โ€” Soft-HOLD #1209 tip-PRESENT/closed. Admin-ui validate script may still use a narrower set (common/admin/auth); do not conflate the two apps.

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.js
  • npm run i18n:validate โ€” node ./scripts/validate-translations.js (KNOWN_NAMESPACES = common/admin/auth only; runtime i18n.ts still 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.