# Crucible i18n Guide

Crucible uses Python's built-in `gettext` for internationalization. Currently supported: English (default), Traditional Chinese (zh-TW).

## For Users

Crucible auto-detects your system locale (`LANG`). To override:

```bash
export CRUCIBLE_LANG=zh-TW   # Traditional Chinese
export CRUCIBLE_LANG=en       # English
```

Priority: `CRUCIBLE_LANG` > `LC_ALL` > `LC_MESSAGES` > `LANG` > `en`

## Adding a New Language

### Prerequisites

Install GNU gettext tools:

```bash
# macOS
brew install gettext

# Ubuntu/Debian
sudo apt install gettext
```

### Step 1: Create .po file

```bash
# Replace LANG_CODE with your locale (e.g., ja, ko, de, fr, zh_CN)
mkdir -p src/crucible/locales/LANG_CODE/LC_MESSAGES

msginit \
  -i src/crucible/locales/crucible.pot \
  -o src/crucible/locales/LANG_CODE/LC_MESSAGES/crucible.po \
  -l LANG_CODE \
  --no-translator
```

### Step 2: Translate

Edit the `.po` file. Each entry looks like:

```po
#: src/crucible/cli.py:146
msgid "crucible — automated experiment loop."
msgstr ""   ← fill this in
```

Guidelines:
- Technical terms keep English: git, commit, branch, tag, metric, Docker, config.yaml, agent, token, cache
- Shell commands stay as-is: `crucible run --tag run1`, `claude login`
- Format placeholders must be preserved **exactly**: `{tag}`, `{cost:.2f}`, `{content!r}`
- Status values stay English: keep, discard, crash, skip, violation
- Match the concise CLI tone — short and direct

### Step 3: Validate and compile

```bash
# Validate format string placeholders match
msgfmt --check src/crucible/locales/LANG_CODE/LC_MESSAGES/crucible.po

# Compile to binary .mo
msgfmt -o src/crucible/locales/LANG_CODE/LC_MESSAGES/crucible.mo \
  src/crucible/locales/LANG_CODE/LC_MESSAGES/crucible.po
```

### Step 4: Register the locale

Edit `src/crucible/i18n.py`, add your locale to `get_locale()`:

```python
def get_locale() -> str:
    lang = (
        os.environ.get("CRUCIBLE_LANG")
        or os.environ.get("LC_ALL")
        or os.environ.get("LC_MESSAGES")
        or os.environ.get("LANG", "en")
    )
    if lang.startswith("zh_TW") or lang.lower() == "zh-tw":
        return "zh_TW"
    if lang.startswith("ja"):        # ← add like this
        return "ja"
    return "en"
```

### Step 5: Test

```bash
CRUCIBLE_LANG=LANG_CODE crucible --help
CRUCIBLE_LANG=LANG_CODE crucible new --list .
```

### Step 6: Commit

Commit both `.po` (source) and `.mo` (compiled) files. The `.mo` is committed so users don't need gettext tools installed.

```bash
git add src/crucible/locales/ src/crucible/i18n.py
git commit -m "feat(i18n): add LANG_CODE translation"
```

## Updating Translations After Code Changes

When translatable strings change in the source code:

```bash
# 1. Re-extract .pot template
xgettext -L Python -o src/crucible/locales/crucible.pot \
  --from-code=UTF-8 --keyword=_ --no-wrap \
  $(find src/crucible -name '*.py' ! -path '*/examples/*')

# 2. Merge into each .po file
msgmerge -U src/crucible/locales/zh_TW/LC_MESSAGES/crucible.po \
  src/crucible/locales/crucible.pot

# 3. Translate new entries (search for empty msgstr "")
grep -n 'msgstr ""' src/crucible/locales/zh_TW/LC_MESSAGES/crucible.po

# 4. IMPORTANT: Remove #, fuzzy flags — gettext silently ignores fuzzy entries!
grep -n "fuzzy" src/crucible/locales/zh_TW/LC_MESSAGES/crucible.po

# 5. Validate and recompile
msgfmt --check src/crucible/locales/zh_TW/LC_MESSAGES/crucible.po
msgfmt -o src/crucible/locales/zh_TW/LC_MESSAGES/crucible.mo \
  src/crucible/locales/zh_TW/LC_MESSAGES/crucible.po
```

## Architecture

```
src/crucible/
├── i18n.py                          # Locale detection + gettext init
└── locales/
    ├── crucible.pot                  # Template (extracted from source)
    └── zh_TW/
        └── LC_MESSAGES/
            ├── crucible.po          # Human-readable translations
            └── crucible.mo          # Compiled binary
```

### What gets translated

| Module | Scope |
|--------|-------|
| `cli.py` | All command help, option help, interactive prompts, status output |
| `orchestrator.py` | Logger messages (budget, stop, iteration status) |
| `preflight.py` | Auth errors, fix suggestions |
| `validator.py` | Check names and result messages |
| `wizard.py` | User-facing prompts and errors |
| `postmortem.py` | `render_text()` section headers and labels |
| `sandbox.py` | Docker build messages |

### What stays English

- Agent-facing prompts (`context.py`, `postmortem._build_insights_prompt()`)
- Guardrails `Violation.message` (fed back to agent)
- `results.tsv` status values and headers
- Log prefixes (`[iter N]`, `[beam-N]`, `[profile]`)

## Common Pitfalls

1. **`#, fuzzy` flag** — `msgmerge` auto-marks entries as fuzzy when msgid changes. Gettext silently ignores fuzzy translations at runtime. Always remove fuzzy flags after verifying the translation is correct.

2. **Click docstrings** — Click takes command descriptions from function docstrings, which gettext cannot translate. Use `@cli.command(help=_("..."))` instead.

3. **Format placeholders** — A mismatched `{var}` between msgid and msgstr will cause a runtime `KeyError`. Use `msgfmt --check` to catch these.

4. **Testing** — `tests/conftest.py` pins `CRUCIBLE_LANG=en` so all tests run in English regardless of developer locale.
