The monorepo implements a unified internationalization (i18n) strategy using Paraglide JS. This architecture ensures type-safe, performant, and developer-friendly multi-language support across all frontend applications (Panel, Cycle Planner, ClawUI, and Platform).
Overview
The i18n system provides support for English (default), Simplified Chinese, and Malay. It utilizes a build-time compilation strategy where translations are converted into type-safe JavaScript functions, enabling efficient tree-shaking and eliminating runtime JSON loading.Key Capabilities
- Type-Safe Messages: Translations are compiled into JS functions with full TypeScript inference.
- Shared Dictionary: Common UI strings (buttons, forms, errors) are defined centrally in
frontend/shared-i18n/. - Hybrid Persistence: Language preferences are stored in
localStoragefor instant access and synced to the database for cross-device persistence. - Framework Agnostic: Works seamlessly with both SolidStart (instant client-side switching) and Astro (page-reload switching).
Domain Glossary
Core Concepts
- Locale: A language identifier (e.g., “en”, “zh”, “ms”) determining the active translation set and formatting rules.
- Message: A translatable string defined in JSON, compiled by Paraglide into a JS function (e.g.,
m.button_save()). - Translation: The target-language version of a message.
- Merge Strategy: The build process that combines shared translations with app-specific overrides:
shared + app-specific → merge → Paraglide compile.
Process Concepts
- Language Detection Chain: Ordered sequence:
localStorage→browser setting→English fallback. - Machine Translation: Automated pipeline using
inlang CLIto generate initial translations for review. - Tree-Shaking: Automatic exclusion of unused message strings from the final production bundle.
Implementation Details
1. Technology Stack
- Compiler/Runtime:
@inlang/paraglide-js - Automation:
@inlang/cli(Machine Translation) - Formatting: Native
IntlAPIs (DateTimeFormat,NumberFormat) - Orchestration: Turbo tasks (
build:i18n)
2. Directory Structure
3. Usage in Code
In SolidStart components, use thecreateReactiveTranslator utility to ensure translations update reactively when the language changes.
4. Detection & Persistence
TheuseLanguage hook (SolidStart) or utility functions (Astro) manage the locale state. When a user logs in, the language_preference is fetched from the backend and synced to localStorage.
Decision Rationale
Why Paraglide?
- Performance: Build-time compilation enables tree-shaking and avoids runtime I/O overhead.
- Safety: TypeScript ensures developers don’t call missing keys or provide incorrect parameters.
- Unified Strategy: A single tool that supports both our reactive (SolidJS) and static/SSR (Astro) frameworks.
Why Shared + App-Specific Structure?
- DRY Principle: Avoid duplicating common strings like “Save” or “Cancel” across four different apps.
- Isolation: Keep domain-specific terminology (e.g., “organization” in Panel) isolated to the relevant application.
- Flexibility: Apps can override shared strings if they require a specific context-aware label.
Invariants & Limitations
- Compile-Time Artifacts: Translations are bundled into the app; new languages require a rebuild.
- LTR Only: All supported MVP languages (en, zh, ms) are Left-to-Right.
- Independent Preferences: Switching language in one app (e.g., Panel) does not automatically switch it in another (e.g., Platform) until the next sync/login.
- No HTML in Messages: Messages are plain text; UI components should wrap them for styling.
Related Files
frontend/shared-i18n/- Central translation sources.docs/frontend/I18N_README.md- Technical implementation details and walkthroughs.openspec/changes/i18n-paraglide-implementation/- Original feature proposal and specs.