feat(editor): multilingual editor 1/3 — language switch, tree translation state, untranslated page, Translate dialog #201

Merged
dominik.polakovics merged 2 commits from afk/192 into main 2026-10-11 01:00:47 +02:00

Closes #192

First editor slice of the multilingual work: the editor gets its language axis.

What changed

  • Language list: GET /api/site is read once (stores/languages.ts). The URL's language segment is now the source of truth, and an unknown code is repaired to the default language. A harness without /api/site falls back to the one language the tree reports.
  • Tree follows the URL language: the page tree polls GET /api/tree?lang= and GET /api/badges?lang= for the URL's language. In the default language the request is still the bare /api/tree.
    • On a switch, stale in-flight answers are dropped.
    • Route repair judges the page only against a tree loaded in the same language, so the selected page survives a switch.
  • Translation state store (stores/translationState.ts): it loads the tree and badges for each other language and derives the dot for every page and language with langDot. It refreshes after mutations, on git sync and when the tree poll sees a change.
    • "Unpublished" means the file's badge in that language is modified or draft.
  • LanguageSwitch in every module's docheader, through Docheader and a LanguageProvider: one segment per language, each with the selected page's status dot and a tooltip. It reuses DocheaderToggle. On a one-language site the switch is hidden, and a test covers this.
  • Page tree outside the default language:
    • Untranslated pages show in italics, muted, with the default-language code tag, and stay selectable.
    • Each row has a dot, the tree has a legend, and the header shows the language badge.
    • Create, move, reorder and drag are off, with the hint. Inline rename sends the current language.
  • Untranslated-page screen: two cards. The left one has the strict explanation, Translate page…, Open in , the file that would be created and the address after translating. The right one lists the source page's elements.
    • The docheader shows the language and "Not translated" badges. Undo, Discard and View are disabled, and the tabs are hidden.
  • TranslateDialog (reusable, for #200 as well):
    • Four modes: connected (default), free and empty, plus machine translation shown disabled.
    • The subpages switch appears only when translate-plan reports subpages.
    • 409 and 422 messages show inside the dialog.
    • On success it reloads into the translation and shows a notice. The editor has no toasts, so this uses the page module's existing notice strip.
  • Connected-translation guard: when translation.mode === "connected" there are no insert rows, drag handles, drop zones or paste targets. The card menu is reduced to Edit and Enable/Disable, and unused-content move/delete is hidden too.
  • Related fixes:
    • The page ⋯ menu disables "New subpage" outside the default language, and Rename, Enable and Delete on an untranslated page, since the server refuses them.
    • The link picker no longer treats untranslated pages as disabled; their links fall back to the default language.
  • AGENTS.md "Editor modules": the language switch, the tree in that language, and creation in the default language only. I ran grep -ril "translat\|language" scaffold/site/.claude/skills/. No skill describes the editor's language handling, so the skills are untouched.

Deviations / notes

  • The tree has no header "+ New page" button in any language. Pages are created from the row menu's "New subpage" and the missing-home "Create". Both are disabled outside the default language with the hint, and no new button was added.
  • Translation state for languages not on screen only refreshes on this client's own signals: a mutation, a sync, or a tree change in the language on screen. A per-page endpoint would be an engine follow-up, if one is needed.

Verification

  • cd web && npx tsc --noEmit -p .: clean.
  • npm test: 94 files, 2561 tests, all pass.
    • Existing tests are unmodified apart from the /api/site fixture in mockApi.ts and one added LinkPicker test.
    • New tests cover the language store, route repair, tree language switching, langDot, the translate API, the switch (including the unsaved-changes guard), the tree's untranslated rendering, the dialog's request shapes and errors, the untranslated-page screen and the connected guard.
  • npm run build: ok.
  • go vet ./... and go test ./... pass, including the drift tests internal/element/agentsdoc_test.go and scaffold/skills_test.go.
  • Not done: the manual harness serve walk-through on testdata/multilingual-site.

🤖 Generated with Claude Code

Closes #192 First editor slice of the multilingual work: the editor gets its language axis. ## What changed - **Language list**: `GET /api/site` is read once (`stores/languages.ts`). The URL's language segment is now the source of truth, and an unknown code is repaired to the default language. A harness without `/api/site` falls back to the one language the tree reports. - **Tree follows the URL language**: the page tree polls `GET /api/tree?lang=` and `GET /api/badges?lang=` for the URL's language. In the default language the request is still the bare `/api/tree`. - On a switch, stale in-flight answers are dropped. - Route repair judges the page only against a tree loaded in the same language, so the selected page survives a switch. - **Translation state store** (`stores/translationState.ts`): it loads the tree and badges for each other language and derives the dot for every page and language with `langDot`. It refreshes after mutations, on git sync and when the tree poll sees a change. - "Unpublished" means the file's badge in that language is `modified` or `draft`. - **LanguageSwitch** in every module's docheader, through `Docheader` and a `LanguageProvider`: one segment per language, each with the selected page's status dot and a tooltip. It reuses `DocheaderToggle`. On a one-language site the switch is hidden, and a test covers this. - **Page tree outside the default language**: - Untranslated pages show in italics, muted, with the default-language code tag, and stay selectable. - Each row has a dot, the tree has a legend, and the header shows the language badge. - Create, move, reorder and drag are off, with the hint. Inline rename sends the current language. - **Untranslated-page screen**: two cards. The left one has the strict explanation, Translate page…, Open in <default>, the file that would be created and the address after translating. The right one lists the source page's elements. - The docheader shows the language and "Not translated" badges. Undo, Discard and View are disabled, and the tabs are hidden. - **TranslateDialog** (reusable, for #200 as well): - Four modes: connected (default), free and empty, plus machine translation shown disabled. - The subpages switch appears only when translate-plan reports subpages. - 409 and 422 messages show inside the dialog. - On success it reloads into the translation and shows a notice. The editor has no toasts, so this uses the page module's existing notice strip. - **Connected-translation guard**: when `translation.mode === "connected"` there are no insert rows, drag handles, drop zones or paste targets. The card menu is reduced to Edit and Enable/Disable, and unused-content move/delete is hidden too. - Related fixes: - The page ⋯ menu disables "New subpage" outside the default language, and Rename, Enable and Delete on an untranslated page, since the server refuses them. - The link picker no longer treats untranslated pages as disabled; their links fall back to the default language. - **AGENTS.md** "Editor modules": the language switch, the tree in that language, and creation in the default language only. I ran `grep -ril "translat\|language" scaffold/site/.claude/skills/`. No skill describes the editor's language handling, so the skills are untouched. ## Deviations / notes - The tree has no header "+ New page" button in any language. Pages are created from the row menu's "New subpage" and the missing-home "Create". Both are disabled outside the default language with the hint, and no new button was added. - Translation state for languages not on screen only refreshes on this client's own signals: a mutation, a sync, or a tree change in the language on screen. A per-page endpoint would be an engine follow-up, if one is needed. ## Verification - `cd web && npx tsc --noEmit -p .`: clean. - `npm test`: 94 files, 2561 tests, all pass. - Existing tests are unmodified apart from the `/api/site` fixture in `mockApi.ts` and one added LinkPicker test. - New tests cover the language store, route repair, tree language switching, `langDot`, the translate API, the switch (including the unsaved-changes guard), the tree's untranslated rendering, the dialog's request shapes and errors, the untranslated-page screen and the connected guard. - `npm run build`: ok. - `go vet ./...` and `go test ./...` pass, including the drift tests `internal/element/agentsdoc_test.go` and `scaffold/skills_test.go`. - Not done: the manual `harness serve` walk-through on `testdata/multilingual-site`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
feat(editor): language switch, tree translation state, untranslated page and Translate dialog
All checks were successful
ci / build-and-test (pull_request) Successful in 13m33s
e8a5bf913d
- LanguageSwitch in every module docheader (hidden on a one-language site):
  one segment per language with the selected page's status dot; switching
  keeps module and page and runs the unsaved-changes guard.
- Page tree outside the default language: untranslated rows in italics with
  the default code tag, per-row dots, a legend, a language badge; create,
  move and reorder off with the hint; inline rename stays on.
- Untranslated-page screen and a reusable TranslateDialog (connected / free /
  empty, machine disabled, subpages row from translate-plan, server errors
  shown in the dialog).
- Connected translations hide insert rows, drag handles, drop zones, paste
  and structural card-menu items.
- Page ⋯ menu: no subpage outside the default language, no rename/enable/
  delete on an untranslated page; the link picker keeps untranslated pages
  linkable.
- AGENTS.md "Editor modules": the language switch and default-language
  creation.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Author
Owner

[autoland] verdict: pass

[autoland] verdict: pass
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/deckle!201
No description provided.