# Plan: Internationalization (i18n) & Localization

**Status**: Draft
**Author**: Prism Core Team
**Created**: 2026-02-06
**Updated**: 2026-02-06

## Overview

Add full-stack internationalization support to Prism-generated applications. This means every generated label, error message, form placeholder, and notification can be translated — with locale-aware formatting for dates, numbers, and currencies. For any Prism app targeting a non-English or multi-language audience, this removes weeks of manual i18n integration work.

## Goals

- Define supported locales in spec (`i18n:` section)
- Auto-generate translation keys for all generated UI text (labels, buttons, messages, errors)
- Generate `react-i18next` setup with lazy-loaded locale bundles
- Generate backend message catalogs for API error messages and email templates
- Locale-aware formatting: dates, numbers, currencies, pluralization
- Language switcher component in frontend
- RTL (right-to-left) layout support for Arabic, Hebrew, etc.
- CLI tooling for translation file management

## Non-Goals

- Machine translation (users provide translations or use external tools)
- Content translation (user-generated data stored in the database)
- Per-tenant locale settings (deferred to Multi-Tenancy plan)

## Design

### Specification Extensions

```yaml
i18n:
  enabled: true
  default_locale: en
  supported_locales: [en, nb, de, fr, ar]
  fallback_locale: en
  rtl_locales: [ar, he]

  auto_extract: true        # Auto-generate keys from generated UI text
  namespace_per_model: true  # Separate translation file per model

  formatting:
    date: locale             # locale-aware date formatting
    number: locale           # locale-aware number formatting
    currency: USD            # default currency for currency formatting
```

### Technical Approach

#### 1. Frontend i18n (react-i18next)

Generate complete `react-i18next` setup:

```
frontend/src/
├── i18n/
│   ├── config.ts           # i18next configuration
│   ├── locales/
│   │   ├── en/
│   │   │   ├── common.json     # Shared UI strings
│   │   │   ├── auth.json       # Auth-related strings
│   │   │   ├── order.json      # Model-specific strings
│   │   │   └── validation.json # Form validation messages
│   │   ├── nb/
│   │   │   ├── common.json
│   │   │   └── ...
│   │   └── de/
│   │       └── ...
│   └── LanguageSwitcher.tsx
```

Generated translation keys follow a consistent naming pattern:

```json
// en/order.json
{
  "order": {
    "title": "Orders",
    "title_singular": "Order",
    "create": "Create Order",
    "edit": "Edit Order",
    "delete": "Delete Order",
    "delete_confirm": "Are you sure you want to delete this order?",
    "fields": {
      "id": "ID",
      "customer_name": "Customer Name",
      "total": "Total",
      "status": "Status",
      "created_at": "Created At"
    },
    "messages": {
      "created": "Order created successfully",
      "updated": "Order updated successfully",
      "deleted": "Order deleted successfully",
      "not_found": "Order not found"
    }
  }
}
```

#### 2. Auto-Key Extraction

During generation, every hardcoded UI string is replaced with a `t()` call:

```tsx
// Before (current generation)
<h1>Orders</h1>
<button>Create Order</button>

// After (with i18n)
<h1>{t("order.title")}</h1>
<button>{t("order.create")}</button>
```

The generator builds translation files automatically from model names and field names, using sensible defaults (title-cased field names, pluralized model names).

#### 3. Backend Message Catalogs

```python
# Generated message catalog
from prisme.i18n import get_message

# API error responses use catalog
raise HTTPException(
    status_code=404,
    detail=get_message("order.not_found", locale=request.state.locale)
)
```

Backend locale resolution:
1. `Accept-Language` header parsing
2. User profile `preferred_locale` field (if auth enabled)
3. Fallback to `default_locale`

#### 4. RTL Support

For RTL locales, the generator adds:
- `dir="rtl"` attribute on `<html>` element
- Tailwind `rtl:` variant utilities in generated components
- Mirrored layout (sidebar on right, text alignment)
- RTL-aware CSS custom properties

#### 5. Locale-Aware Formatting

```tsx
// Generated formatting utilities
import { useLocale } from '@/i18n/config'

function OrderTotal({ amount }: { amount: number }) {
  const { formatCurrency } = useLocale()
  return <span>{formatCurrency(amount)}</span>
  // en: "$1,234.56"
  // nb: "1 234,56 kr"
  // de: "1.234,56 €"
}
```

#### 6. Language Switcher Component

Auto-generated dropdown with locale display names and flags:

```tsx
// Generated LanguageSwitcher.tsx
function LanguageSwitcher() {
  const { i18n } = useTranslation()
  const locales = [
    { code: "en", name: "English" },
    { code: "nb", name: "Norsk Bokmål" },
    { code: "de", name: "Deutsch" },
  ]
  return (
    <select value={i18n.language} onChange={e => i18n.changeLanguage(e.target.value)}>
      {locales.map(l => <option key={l.code} value={l.code}>{l.name}</option>)}
    </select>
  )
}
```

### CLI Commands

```bash
# Extract new keys from generated code
prism i18n extract

# Check for missing translations
prism i18n check --locale nb
# Output: 12 missing keys in nb/order.json

# Add a new locale
prism i18n add-locale ja

# Export translations for external translators (CSV/XLIFF)
prism i18n export --format csv --locale nb > translations-nb.csv

# Import completed translations
prism i18n import translations-nb.csv
```

### API Changes

- All API error messages use message catalog keys
- `Accept-Language` header respected on all endpoints
- User profile gains `preferred_locale` field (if auth enabled)
- `GET /api/i18n/locales` — list supported locales

### Database Changes

- Optional `preferred_locale` column on User model
- No other schema changes — translations are file-based

## Implementation Steps

1. **Spec model** — Add `I18nConfig` to spec models with validation
2. **Translation key generator** — Auto-extract keys from model names, field names, UI text
3. **Frontend generator** — `react-i18next` setup, locale files, lazy loading
4. **Component updates** — Replace all hardcoded strings with `t()` calls in templates
5. **Backend catalog** — Message catalogs, locale middleware, Accept-Language parsing
6. **RTL support** — Conditional `dir` attribute, Tailwind RTL variants
7. **Formatting** — Date, number, currency formatters with Intl API
8. **Language switcher** — Dropdown component with locale persistence
9. **CLI commands** — extract, check, add-locale, export, import
10. **Tests** — Locale switching, RTL rendering, formatting, missing key detection

## Testing Strategy

- **Unit tests**: Key extraction, formatting functions, locale resolution
- **Integration tests**: Full page renders in each locale, RTL layout verification
- **Snapshot tests**: Generated translation files match expected structure
- **CLI tests**: Extract, check, export/import round-trip

## Rollout Plan

1. Frontend i18n setup + auto-key extraction (core value)
2. Backend message catalogs + locale middleware
3. RTL support
4. CLI tooling (extract, check, export/import)
5. Documentation + translation contributor guide

## Complexity & Effort

**Effort Estimate**: 4-5 weeks (1 full-stack developer)
**Complexity**: MEDIUM-HIGH — touches every generated frontend component

## Dependencies

- Frontend Design System (P17) — complete, provides consistent component structure to instrument
- Enterprise Auth (P12) — complete, provides user profile for locale preference

## Risk Assessment

**Medium Risks**:
- Key naming collisions across models (mitigated: namespace-per-model strategy)
- Translation file size for large apps (mitigated: lazy loading per namespace)
- RTL layout bugs (mitigated: Tailwind RTL plugin + testing)

**Adoption Risk**: LOW — opt-in via `i18n.enabled: true`, zero impact when disabled.

## Open Questions

- Should we integrate with translation management platforms (Crowdin, Lokalise)?
- Should pluralization rules be auto-generated from CLDR data?
- Should model field `display_name` in spec override auto-generated translation keys?
