# I18N-3 — Backfill Swahili in `messagingTranslations` and restore the four-locale shape test

**Target repo:** `ui-core-micha` (branch `main`)
**Tier:** 3 — shared-core, and it re-tightens a test the estate deliberately loosened.

---

## A. Envelope

### Goal

`src/i18n/messagingTranslations.ts` carries **116 keys with `de`/`fr`/`en` and zero `sw`**. Every one of the
other six bundles carries all four locales:

| bundle | de | fr | en | sw |
|---|---|---|---|---|
| `auth` | ✓ | ✓ | ✓ | ✓ |
| `charts` | 12 | 12 | 12 | 12 |
| **`messaging`** | **116** | **116** | **116** | **0** |
| `notifications` | 30 | 30 | 30 | 30 |
| `onboarding` | 27 | 27 | 27 | 27 |
| `sectionNav` | 3 | 3 | 3 | 3 |
| `userMenu` | 3 | 3 | 3 | 3 |

`I18N-1` hit this while writing its aggregate test: the required "every value keeps its `{de,fr,en,sw}` shape"
assertion failed, the gap was confirmed as pre-existing rather than caused by the merge, and the operator
scoped the test down to `{de,fr,en}` with the backfill recorded as a follow-up. **The follow-up was never
written, and a scoped-down test tends to stay scoped down.** That is what this WO closes.

### Scope

1. **`src/i18n/messagingTranslations.ts`** — add `sw` to all 116 keys.
2. **`tests/i18nAggregate.test.js`** — restore the shape assertion to `{de,fr,en,sw}` and **remove the comment
   recording the scope-down**, since it no longer describes the state.
3. Nothing else. No other bundle is touched; they already conform.

### The translation quality question, which is this WO's real risk

**Machine-translated UI strings that nobody on the team can check are a worse outcome than an honest gap.**
Messaging strings are user-facing and include the kinds of phrase that go wrong invisibly — actions,
confirmations, error states, relative time. So:

- **Name the source of the translations in the register note.** If they are machine-produced, say so plainly
  rather than shipping them as if reviewed.
- **Flag any key the translator was unsure of** rather than guessing. A `sw` value that is wrong is harder to
  find later than one that is missing, because the shape test will then pass.
- **Do not invent terminology.** Where `notifications` or `onboarding` already has a Swahili term for the same
  concept — send, reply, unread, conversation, attachment — **reuse their wording** rather than coining a
  second one. Those bundles are the existing precedent and consistency across them matters more than any
  individual phrasing.

### Who this is actually for

`sw` is configured as a locale in **hram** and in no other app measured. hram does **not** register
`messagingTranslations` today, so nothing is currently broken for a user — this is prevention, and the honest
framing is: it closes an inconsistency and re-arms a test, rather than fixing a live defect.

That matters for the effort trade-off, and it is why the quality bar above is not negotiable: there is no
urgency justifying unreviewed strings.

### Non-goals / do not touch

- **The other six bundles.** They conform.
- **The aggregate itself, the directory ratchet, or the key/value fidelity and collision checks.** Only the
  locale-shape assertion changes.
- **`de`/`fr`/`en` values.** Not a re-translation pass.
- **Adding an eighth locale**, or any change to how consumers register strings (`I18N-2`'s territory).

### Risks

- **116 unreviewable strings.** The mitigation is disclosure, not confidence — see above.
- **Re-tightening the test is the irreversible half.** Once the assertion demands four locales, any future
  bundle addition must supply `sw` or fail CI. That is the intent, and it is worth stating so the next person
  adding a bundle is not surprised.
- **A push to `main` publishes.** See Part C.

### Required tests to WRITE

None new — **`tests/i18nAggregate.test.js`'s restored four-locale assertion IS the test**, and re-arming it is
scope item 2. It will fail until the backfill is complete, which is the correct behaviour and the reason it
belongs in the same change.

Scoped run: `i18nAggregate` plus the messaging export specs. Not the full suite — no dependency or theme
change.

### Parity guardrail

No API, behaviour or visual change for `de`/`fr`/`en` consumers. Purely additive.

---

## B. Implementation map

*Filled by the Orchestrator on `git pull` — see `AGENTS.md` → "Work Order".*

**Files to change (exactly two):**
- [`src/i18n/messagingTranslations.ts`](../src/i18n/messagingTranslations.ts) — 118 lines, 116 keys
  (`export const messagingTranslations = { 'Key': { de, en, fr }, ... }`), every value currently a
  3-locale object. Add a fourth `sw: '...'` to every one, matching the existing key ordering
  `de, en, fr` → append `sw` last (matches the property order already used in
  `notificationsTranslations.ts`/`onboardingTranslations.ts`, both `de, fr, en, sw`, so put `sw`
  last regardless of the de/en/fr internal order already in this file — do not reorder de/en/fr).
- [`tests/i18nAggregate.test.js`](../tests/i18nAggregate.test.js) — line 30:
  `const REQUIRED_LOCALES = ['de', 'en', 'fr'];` → `['de', 'en', 'fr', 'sw'];`, and delete the
  explanatory comment at lines 27-29 (`// Scoped to the three locales ... see the Notiz on the
  I18N-1 register row ...`) since it will no longer describe the state. Nothing else in this file
  changes — the other three `it()` blocks (aggregate completeness, no-collision, per-bundle
  availability) are untouched.

**Existing Swahili glossary to reuse — do not coin alternate wording for these concepts** (grep
`sw:` across `src/i18n/*.ts` yourself to see full context; this is not exhaustive, check for any
other overlap you find):
- Notification/notify → **Arifa** (`notificationsTranslations.ts`, `onboardingTranslations.ts`)
- Save → **Hifadhi** (`onboardingTranslations.ts`: `Onboarding.SAVE`)
- Close → **Funga** (`notificationsTranslations.ts`: `PopupSurface.CLOSE`)
- Continue → **Endelea** (`onboardingTranslations.ts`: `Onboarding.CONTINUE`)
- Skip → **Ruka** (`onboardingTranslations.ts`: `Onboarding.SKIP`)
- Change/switch (section) → **Badilisha** (`sectionNavTranslations.ts`)
- User menu / profile / logout → **Menyu ya mtumiaji / Wasifu / Toka** (`userMenuTranslations.ts`)
- "Could not be saved/loaded" error pattern → e.g. **"...haikuweza kuhifadhiwa"** /
  **"...haikuweza kupakiwa"** (`notificationsTranslations.ts` `SAVE_ERROR`/`LOAD_ERROR`) — messaging
  has the same error-string shape repeated ~15 times (`... konnte nicht ... werden: {{message}}`),
  reuse this grammatical pattern rather than inventing a new one per key.
- "Please try again" → **"Tafadhali jaribu tena"** (`onboardingTranslations.ts`)

**None of messaging's specific domain terms (conversation, reply, attachment, reaction, poll,
announcement, unread, archive, mute) exist in the other bundles' Swahili** — those need fresh
translation. That is expected and fine; the instruction above is narrower: reuse wording only where
the *same* concept (save/cancel/close/error phrasing/etc.) already has a Swahili rendering
elsewhere, don't invent a second one for it.

**Do-not-touch:** `de`/`en`/`fr` values in `messagingTranslations.ts` (verify byte-identical before
and after — a diff review will check this); the other six bundles; `tests/i18nAggregate.test.js`'s
other three tests, the aggregate export, the directory ratchet, the ordering (`de, en, fr` internal
order in messaging stays as-is, `sw` only appended).

**Progress contract:** `PLAN: …` naming the two files, `PROGRESS: [1/2] backfill sw into
messagingTranslations.ts … done`, `PROGRESS: [2/2] restore REQUIRED_LOCALES + drop scope-down
comment in i18nAggregate.test.js … done`, then run the scoped tests named below and report which
keys (if any) you were unsure of, then `RESULT: DONE`.

## Preamble

The text above is the COMPLETE spec — the committed WO file's content, not a plan to refine; there is
no separate plan file. Read the nearest `AGENTS.md`, the relevant `.codex/skills/<role>/SKILL.md`, and
the app `MEMORY.md` ONLY for conventions. Stay in scope; do not touch auth/permissions/deps/schema/CI
unless the spec says so; do not update `MEMORY.md`. **Do NOT edit `WORK_ORDERS.md`** — the register row
and the review verdicts are the orchestrator's alone. Do NOT `git add`/`commit`/`push` — leave every
change uncommitted in the working tree for the orchestrator's independent review. This WO's "Required
tests to WRITE" is explicitly **none new** — the restored `REQUIRED_LOCALES` assertion IS the test;
run it (and the messaging export specs) to confirm your backfill makes it pass — that is the ONLY test
run you do (NOT the app's affected/full suite, NOT any review). **State plainly in your final report:
the source of the Swahili translations (e.g. "machine-translated via <tool>, not reviewed by a fluent
speaker" — say so honestly, do not imply review that didn't happen) and list any specific key you were
uncertain of**, per the Envelope's explicit disclosure requirement. The orchestrator re-runs the
authoritative set + does the independent review after you finish — those are the gate; your own run
does not count as the gate.

Narrate continuously: a `PLAN: <step1> | <step2> | …` line up front, then a single-line
`PROGRESS: [<n>/<total>] <present-tense action>` before every relevant action (and `… done` on
completion), spaced so no gap exceeds ~2 min, stdout unbuffered, plus exactly one final
`RESULT: DONE|BLOCKED <reason>`.

---

## C. Orchestrator only

> **STOP — if you are the implementer reading this work order as your own specification, this section is NOT
> addressed to you. Skip it entirely.** It tells the Orchestrator how to invoke you. **You ARE that
> invocation — do NOT shell out to `codex exec`, do NOT spawn reviewers, do NOT edit `WORK_ORDERS.md`, and do
> NOT `git add`/`commit`/`push`.**

### Execution directive

Check `.claude/codex-status.md` first. **No line for the current date means use Codex.** A dated `unavailable`
line for today means skip the attempt, implement directly in Claude and name the record — that flips
authorship. Otherwise one probe, outcome written back either way.

### Review routing

Tier 3: **independent `reviewer`** — mandatory. Ask it specifically to check **terminology consistency against
`notifications` and `onboarding`**, and to confirm that no `de`/`fr`/`en` value was altered in passing. **No
`ui_reviewer`** — nothing renders. No `sec_reviewer`.

Neither reviewer can validate Swahili. **Say that in the note** rather than letting a clean verdict imply the
translations were checked.

### Verification

The restored four-locale assertion passing is the verification. Nothing renders, so no gate.

**A push to `main` here IS the npm publish** — `publish.yml` triggers on `push: branches: [main]` with `src/**`
in its paths, and publishes cannot be undone. Additive strings, so a **patch** by scope.

### Register + commit

One row, `I18N-3`. `done` only with the reviewer and verdict named, **the translation source stated**, and any
uncertain keys listed. Also update `I18N-1`'s note: its recorded follow-up is now closed, and leaving it open
there is how the same gap gets rediscovered.

### Mini-handover

Repo: `ui-core-micha`, branch `main`. WO: `work-orders/I18N-3.md`. 116 `sw` values into
`messagingTranslations.ts`, then restore `i18nAggregate.test.js` to `{de,fr,en,sw}` and drop the scope-down
comment. **Reuse `notifications`/`onboarding` terminology; disclose the translation source; flag uncertain
keys instead of guessing.** No app is broken today — this closes an inconsistency and re-arms a test. Tier 3,
`reviewer` mandatory. **Pushing to main publishes.** Follow `orchestrate-codex`.
