# Internationalization (i18n)

> 🇯🇵 [日本語版](i18n.ja.md)

Praxia ships in 8 languages. Both the landing page and the Streamlit UI auto-detect from the browser / OS and offer manual override.

## Supported languages

| Code | Name | Browser/OS detection |
|---|---|---|
| `en` | English | (default) |
| `ja` | 日本語 | `ja*` |
| `zh-CN` | 简体中文 | `zh*` (incl. `zh-Hans-CN`, `zh-TW`) |
| `ko` | 한국어 | `ko*` |
| `es` | Español | `es*` (incl. `es-MX`, `es-AR`) |
| `fr` | Français | `fr*` |
| `de` | Deutsch | `de*` (incl. `de-AT`, `de-CH`) |
| `pt-BR` | Português (BR) | `pt*` |

## Landing page

- Detection order: `localStorage["praxia-lang"]` → URL `?lang=…` → `navigator.languages` → `en`
- Manual override: chip-style language selector in the top nav (persisted to `localStorage`)
- Translation scope: hero (h1, sub, kicker, CTAs), nav links, hero meta tags, top-of-section headings (Try it / Why Praxia / Who it's for / Why OSS matters / FAQ), footer category headings, footer tag, scenario picker labels (I am / I want to / Run preview / Copy command)
- Body content (FAQ entries, code examples, feature card body, scenario simulations) remains in English. Non-English visitors see a small "translation in progress" notice; the rest is English so all visitors get the same technical content
- SEO: `<link rel="alternate" hreflang="…">` for all 8 languages

## Streamlit UI

```python
from praxia.ui.i18n import t, language_selector_in_sidebar

language_selector_in_sidebar()      # call once near the top
st.title(t("app.title"))
st.caption(t("app.tagline"))
tab1, tab2, ... = st.tabs([t("tab.run_flow"), t("tab.skill"), ...])
```

Detection order: `st.session_state.praxia_lang` → `PRAXIA_UI_LANG` env → `LANG` env (POSIX `LANG=ja_JP.UTF-8` etc.) → `en`. Manual override via the sidebar dropdown persists for the session.

Translation scope: app title / tagline, sidebar field labels, all 11 tab labels, common verbs (Run / Save / Delete / Cancel / Input / Output), memory mode labels.

Long-form business output (skill responses, framework explanations) is in whatever language the LLM produces — independent of the UI shell language. To make a skill produce Japanese output, prompt it in Japanese.

## Adding a new language

1. Add the code to `SUPPORTED` in [`web-publish/i18n.js`](../web-publish/i18n.js) and [`praxia/ui/i18n.py`](../praxia/ui/i18n.py).
2. Add a display name in `LANG_DISPLAY` in both files.
3. For each translation key in the dictionaries (`T` in JS, `_T` in Python), add an entry for the new language. The completeness test (`tests/evaluation/test_eval_i18n.py::TestCompleteness::test_every_key_has_every_language`) will fail until every key is covered.
4. Run `pytest tests/evaluation/test_eval_i18n.py -v` to verify.
5. Add an `<link rel="alternate" hreflang="…">` to `web-publish/index.html`.
6. Submit a PR — the eval suite catches missing translations automatically.

## Adding a new translation key

1. Add the key + all 8 language values to `_T` in `praxia/ui/i18n.py` (UI) or `T` in `web-publish/i18n.js` (landing).
2. In templates, use `data-i18n="key"` (landing) or `t("key")` (UI).
3. Run the test suite — `TestCompleteness` will flag any partial coverage.

## Why this scope

Translating an entire 1500-line landing page is impractical for a small OSS team. Translating the **top-of-funnel UI strings** (what affects first-impression conversion) is high-leverage. Body content remains in English so:

- Search engines index in English (best technical SEO)
- Code examples remain accurate (English-only API names)
- Long-tail content updates don't need 8-way translation each time
- Visitors who land on a translated nav can skim the rest with browser auto-translate if needed

If you'd like body content fully translated for a specific language, open an issue — community translation PRs are welcome.
