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 46bce720 (services/admin-ui/scripts/extract-strings.js and validate-translations.js). โœ… Reserved namespaces (ai, marketing, errors, validation) are loaded on tip (46bce720) 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. (updated 2026-08-17; tip stamp re-verified 2026-08-21 vs 46bce720 โ€” 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_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). Runtime i18n.ts on both apps loads four additional reserved namespaces (ai, marketing, errors, validation); the offline KNOWN_NAMESPACES set 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.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.