**Scope:** Let the user choose which language(s) the //Caption// column edits, by adding additional Caption columns each bound to a different language; never two columns for the same language; choices persist across reloads.
== What's wanted ==
- The //Caption// column is currently single-language (English, hard-coded). The header reads "Caption EN" via a placeholder `DEFAULT_DESCRIPTION_LANG = "EN"` constant in `src/table.jsx:1266` whose own comment flags this as the missing piece.
- Users should be able to (a) **change** the active Caption column's language, or (b) **add another** Caption column for a different language. Two visible Caption columns for the same language must be impossible.
- The set of languages the user has enabled (and the column count) is part of //user preferences//, persisted across reloads and across devices via the existing `Preferences.json` user-store page (no new persistence schema required — see Persistence below).
== Where in the code ==
- `src/table.jsx`
- `TABLE_COLUMNS` (~line 70-90): the single `description` column descriptor with its caption-specific header info popover. Per-language variants need column descriptors that share the same look but each carry a `lang` field.
- `Cell()` `case "description":` (~line 1841) and `CellEditor` `case "description":` (~line 2198) read/write `item.description`. New per-lang cells need to read/write `item.descriptions[lang]`.
- `HeaderCell` (~line 1268) currently appends `DEFAULT_DESCRIPTION_LANG` to the label. Replace with the column's `lang` (uppercased, e.g. "EN", "NL").
- `EDITABLE_KEYS` (~line 141): the editable check is currently keyed on a static `Set<string>`. Per-lang columns will need a per-key suffix (`description:nl`) or a more general predicate that recognises caption keys.
- `getAllColumns(customProps)` (~line 203): today returns `[...TABLE_COLUMNS, ...customProps]`. It should also expand the canonical `description` template into one descriptor per active caption language, so visibility/order/widths flow through without special-casing.
- `src/api/normalize.js`
- `normalizeStashFile` (~line 213): `descriptions: { en: '', nl: '' }` is the existing partial shape. Initialise `descriptions` as `{}` and populate from SDC labels in `normalizePublishedFile` (~line 489) for **every** language present, not just `en`.
- `src/wikitext-templates.js` `formatDescription` (~line 161): currently emits `{{en|...}}` and `{{nl|...}}` only. Make it emit one block per language present in `item.descriptions`. Keep the legacy fallback for `item.description` (treat as `en`).
- `src/api/user-store.js` `DRAFT_FIELDS` (~line 395): add `descriptions` so per-language drafts persist alongside the legacy `description` string.
- `src/columns-modal.jsx`: add a way to pick which language to show when adding a Caption column (so two columns can't share a language).
== Persistence model ==
Per the maintainer's "Don't persist derived data" rule, **only the user's choices belong in `Preferences.json`**:
- The list of caption-column descriptors (which languages, which order, which widths) is already stored as part of `columnState.visible` / `columnState.order` / `columnState.widths`. No new pref shape needed — the column key //is// the language identifier (e.g. `description:nl`).
- The legacy `"description"` key (no suffix) continues to mean "English caption" for backward compat with stored prefs / drafts.
- Per-language draft text (`descriptions[lang]`) goes through `setDraft` like any other field, into `Metadata.json` (user-authored content).
== Default language ==
- The base column key `description` keeps meaning English (zero migration cost, matches the existing data flow).
- Adding a new caption column defaults to the user's browser locale (`navigator.language`) two-letter prefix, falling back to `en`. The duplicate-language guard ensures the picker shows only languages not already on screen.
- The set of offered languages comes from a small static list (the most common Commons languages: `en`, `nl`, `de`, `fr`, `es`, `it`, `pt`, `pl`, `ru`, `ja`, `zh`, `ar`). The list is curated, not exhaustive — this matches the column-defaults approach for licences (a curated catalog).
== Acceptance criteria ==
- [ ] User can change the language of the existing Caption column from a header control or the columns modal.
- [ ] User can add a second (third, …) Caption column with a different language.
- [ ] The duplicate-language guard prevents two visible Caption columns from sharing a language (the picker hides already-used languages).
- [ ] Edits to one language do not affect the other.
- [ ] The choice (language set, order, count) persists across page reloads in the same browser.
- [ ] The choice persists across browsers/devices for the same user (via `Preferences.json` round-trip).
- [ ] The published wikitext emits one `{{<lang>|1=...}}` block per non-empty caption language, in the order shown in the table.
- [ ] Existing rows with the legacy `description: "..."` field still render under the English column (no data loss).
- [ ] `npm run build` (which runs the undefined-identifier scanner) passes.
== Out of scope ==
- Pushing captions as SDC labels via `wbeditentity.labels` (currently only //claims// are pushed via `addStructuredData`). The on-wiki rendering still relies on the wikitext `{{lang|1=...}}` blocks. SDC label publish is a separate task.
- Per-row "this row uses these languages" overrides — every visible column applies to every row.
- Unbounded language picker (the curated list is the v1; an autocomplete over MediaWiki's site list can come later if requested).