18 KiB
Article localization implementation handoff
Baseline imported 2026-10-31 and refreshed 2026-11-01 (completion pass) from .claude/design. This is a target design, not a statement that the feature exists. The user authorized this import and reconciliation. Feature and QA acceptance still happen per slice before implementation.
Authority and imported artifacts
For localization, use this document for integration rules and corrections, localization-tables.md (generated from source/locale-spec.js) for the product tables, screens/localization-*.md and states.md for the state inventory, and localization-corrections.md for what the last design pass rendered, inspected and left open. Use source/LocaleFrame.dc.html for layout and rendered copy. This document supersedes conflicting board notes and older title/slug assumptions. Existing accepted features/ behavior remains binding until a new accepted slice explicitly changes it. Report any remaining conflict at specification time; do not silently change behavior to match a mockup.
Imported sources are the Localization board, LocaleFrame, locale-spec.js, locale-fixtures.js (the single fixture set every list, count and pager derives from) and localization-changelog.md (state IDs and copy changed by the third pass). Since the third pass (2026-11-03), LocaleFrame draws inside the shared AppFrame, which gained optional editor-header slots and a width option; LocalizationShell is gone. Do not introduce a second application shell in production: continue using DashboardShell and existing editor components.
The LocaleLive prototype was deliberately not imported. The Prototype and Handoff boards remain unchanged and do not demonstrate localization. This written handoff replaces the missing localization handoff, not the missing interactive prototype. No rendered visual parity, keyboard flow, browser console check or production test is claimed by this import.
Product model
A story groups article versions within one site. Each version keeps its article ID, creator/permissions, language, title, body, slug, SEO/social fields, alt text, publication and timestamps. Enforce one version per normalized locale per story. Files are shared references, not duplicated uploads. Authors and image selections are copied independently when requested. All versions share the story's site. Site changes therefore use an atomic story move rather than moving one linked version alone.
Site default, current editing language, temporary comparison reference and tracked review source are distinct. Changing ?ref= changes only the comparison pane. It does not update the tracked source. No machine translation, cross-site grouping, bulk publishing, synced scrolling or text diff is included.
Use existing ID conventions for new records unless the accepted backend slice establishes a reason to differ. Example st_01 in mockups is illustrative; it does not mandate string IDs.
Languages and existing-content setup
Owner manages /settings/sites/:key/languages. Editors who need language metadata can read the allowed metadata without gaining site-management access. Gate all row menus and Re-enable actions as well as Add language.
At least one language is enabled and the default is enabled. Choose another default before retiring the current one. Remove only unused, non-default languages. Disable preserves content and online versions; editing remains permitted, but draft publication and new translation creation require re-enabling. Unpublish remains available under existing permissions. Existing disabled-language versions remain accessible through permitted lists and direct IDs.
Preserve full supported locale tags such as pt-BR. If only a broader tag is supported, explain the suggested substitution and require confirmation. No locale means an explicit choice, not an inferred translation. Setup assigns a language and a separate story to each existing article; it does not guess translation links.
Setup must be idempotent and atomic for its declared scope. The backend slice must define create/setup concurrency and safe retries before coding: an unassigned article must not become stranded or assigned to a different default halfway through setup. Keep stable article IDs, bodies, publication and API reads. Test migrations through the repository’s applicable persistence tests; HTTP fakes alone do not establish migration correctness.
Editing, permissions and publication
Retain existing per-version read/update/publish/delete scopes and creator semantics. Translation membership grants no additional permission. Publishing and updating are separate permissions. A user allowed to update an online article can change its live content by saving, even without publish permission; this preserves the current direct-update model. This feature does not add unpublished revisions or editorial approval of updates.
Use this copy for an online editable version: “This version is online. Saving updates the live page right away.” For draft writers without publish: “Saving keeps it as a draft.” Do not claim saving an online article produces a draft.
Both viewports use Save and publish for a dirty offline version when update and publish are allowed. Save succeeds before publish is sent. Save failure retains dirty data and sends no publish request. If save succeeds but publishing fails, content is saved offline; offer retry publication without pretending the save failed. Recheck conflicts/permissions server-side on both operations. No transactional combined endpoint is promised. Unpublish does not discard or implicitly save local edits; explain the resulting saved/offline state and retain the draft buffer.
Titles become editable for single-language and multilingual articles, using existing 3–200 character validation. Title edits never regenerate an existing slug. Explicit slug edits are permitted before or after publication. Warn: “Links to the old address will stop working. RenewCMS does not create redirects.” No redirect capability is promised.
Retain title, body, search fields and base revision after failures. Reload-theirs requires discard confirmation; download local work before discarding when requested. Overwrite uses the revision shown by the conflict dialog as its expected revision; do not silently fetch a newer revision immediately before saving and thereby bypass confirmation of another writer's changes. A subsequent update must produce a fresh conflict.
Local recovery is scoped to authenticated user and article ID. Do not restore another user's content after account switching. Define cleanup on successful save, explicit discard, logout and access revocation in the slice acceptance criteria. Draft recovery must not grant server access.
Visibility and list semantics
Only readable versions contribute titles, author unions, timestamps, coverage, results and counts. Never include hidden siblings in public alternates or error payloads. Readable creation conflicts may link to the version; otherwise show “French can't be created for this story.” An unreadable-version conflict has no ID, title or Open action. Missing means no readable version, not proof no stored version exists. Do not advertise stronger anti-enumeration guarantees than that policy provides.
Authenticated editorial all-language view counts stories, uses the readable default-language title or deterministic first readable locale, and opens that representative version. Updated is the latest readable version update; authors are the union of readable authors. Selected-language view counts versions. Missing-language view counts stories in sites enabling the target language and searches readable source titles. Other language-filtered searches match that language's titles. All-language search matches any readable title.
Use lang and tab=all|missing|review consistently in SPA and editorial API requests. Omit default tab=all. Missing requires a selected language. Review works in the active story/version mode. Preserve page, size, q, status, site, seo and existing sort syntax. Author filter is a new capability, not shipped behavior. Reset page on filter changes. Preserve the existing page-size choices and URL normalization.
For combined filters in story mode require a single readable version to satisfy the version-level predicates together; do not match an English author's version and a different French draft to fabricate a combined match. The accepted list slice specifies tab counts and their filter scope explicitly, preserving existing count behavior where possible. The union-language filter must distinguish disabled languages; missing excludes sites not enabling the selected language.
Preserve public /v1/article/ pagination, object shape and access rules. Do not silently change anonymous lists to grouped stories. Define an explicit opt-in editorial grouping contract in the accepted backend list slice; new clients must not depend on an unannounced response-type switch.
API target and compatibility
Existing /v1/article/ POST/GET, /v1/article/{id} GET/PATCH/DELETE and PATCH publish/unpublish remain. The SPA keeps /article/:id/edit and the existing new-article entry. API capability tables describe proposed extensions, not functioning endpoints. Preserve snake_case and existing validation/error conventions; accepted HTTP specs establish exact bodies and status codes. Do not copy mockup JSON errors over current plain-text contracts indiscriminately.
Proposed extensions include language config/setup, article language/story metadata, translation creation, source/review tracking and atomic story moves. Use one translation-creation implementation for both UI entry points. Source must belong to the same story and be readable at submit. Copy snapshots and source revision are captured together server-side. Start-empty creates no tracked source and shows No source until explicitly linked.
Slugs become unique within (site, lang). Exact public lookup is /v1/public/site/{key}/lang/{lang}/article/{slug} and never falls back. Story resolution is /v1/public/site/{key}/story/{story_id}?lang=fr&fallback=default. Without a published requested version, return not-found unless fallback was explicitly requested and the default version is published. Never try an arbitrary third language. Return actual lang, requested_lang and fallback status as appropriate. A default change affects requests omitting language; existing article-ID and explicit-language reads remain stable. Remove contrary “existing URLs never change” claims for language-omitted resolution.
Public alternates include eligible published versions only. Disabled published versions stay available by ID/exact read but are excluded from discovery alternates as designed. Decide noindex handling consistently with existing SEO output during the metadata slice.
Website URLs are not CMS API URLs. Use existing site URL conventions where compatible; a proposed owner-configured public URL pattern enables locale-aware links. No pattern/configuration means “Public URL not configured”; do not fabricate page URLs or change legacy canonical output before a slice explicitly accepts that change. Validate placeholders, URL scheme and encoding in the backend contract.
Locale and SEO corrections
Article language determines content lang, direction and alternate-language identity; the SPA interface locale remains user-controlled. Existing Site.Locale currently participates in social metadata. Do not describe it as an existing SPA formatting setting: SPA formatting uses its own i18n locale. The feature changes article social metadata resolution deliberately rather than pretending the old field had no SEO purpose.
Preserve full article tags. Resolve social locale from explicit per-language configuration, a supported regional tag mapping, or a matching existing site locale; otherwise omit it and show Not set. Validate mappings instead of inventing a region. Bare languages do not imply a particular country. Respect existing canonical overrides.
Local SEO fallback uses the local title/body/excerpt according to existing fallback behavior. Do not silently inject a different language's site text. If a site textual fallback is used, its language must match the article language; default-language changes do not relabel existing site text. Image defaults may still be shared, with per-version alt text. The metadata slice must specify exact fallback order and conditional copy; “Description empty, tag omitted” is not universally correct when a local body/excerpt can supply it.
Review and concurrency
Store source identity, source content revision, translation revision, reviewer and review time. Review confirmation must check both revisions and that the tracked source is unchanged. Source replacement must be in the same story, readable and cycle-free. Replacing it sets Needs review. Keep past review history when the source is deleted or access is temporarily lost; do not reveal unreadable source metadata. Access loss disables review while allowing permitted translation edits.
Content revision changes include title/body/excerpt/slug/alt/SEO/social text. Authors, site moves, image selection and publication do not increment the localization content revision. Use a separate edit concurrency version if needed so non-content updates still cannot be overwritten silently. Changed-field summaries must aggregate fields changed since the reviewed source revision, not merely the latest update. No text diff or immutable historical-source viewer is promised.
Visual validation and implementation gaps
The board contains 16 groups and 183 states (L6 and L9 are release 2). L7s (single-language list) replaces the old Back-office Articles groups as the list's pixel-gate reference. Screen files map stable IDs to exported selectors. Markups are illustrations, not production accessible controls. Implement actual Nuxt UI input/menu/tab behavior. Retain the full existing editor toolbar; the shortened mockup toolbar does not authorize removing features.
The existing visual checker must be extended to discover localization's groups, state tables and 1024px reference where needed, with scoped fixtures and API mocks. Do not claim task visual:check can already verify the import. No threshold increases or exceptions are authorized. Clock-sensitive skips only apply to states whose rendering actually depends on current time, not whole features containing dates.
Known board limitations to settle in the relevant accepted slice: partial sample rows despite full-page totals; alternate empty-state fixtures; desktop/mobile action parity; read-only config controls; title validation bounds; source reference SEO metadata; restore/reload interactions; and state fixtures consistent across lists and editor. If matching the board would violate this handoff or an accepted scenario, document the discrepancy and obtain the workflow's explicit design decision before coding. Do not waive visual checks or silently improve the reference in a coder stage.
Recommended slice order
- Language value rules, site configuration and existing-content setup/migration contracts.
- Story/version storage, unique constraints and authorized translation creation.
- Article update/title/slug and revision conflict contracts, preserving public compatibility.
- Site language settings and setup SPA.
- Editor language navigation, translation dialog and publication scope.
- Editorial grouped/language/missing lists with mobile filters and pagination.
- Public story resolution, localized URL configuration and SEO metadata.
- Optional comparison pane, tracked-source review and freshness UI.
- Atomic story moves/deletion and integration regression coverage.
Split further to keep accepted slices reviewable. Include required backend support before each SPA slice. Each slice follows specifier acceptance → coder → size-gated cleaner/architect → hardener → qa. This order is planning guidance, not acceptance of Gherkin or permission to skip a gate.
Open decisions and known design gaps (2026-11-01)
Settle these during specification of the slice named, before coding.
| Topic | Gap | Slice |
|---|---|---|
| Setup route | The route is /settings/sites/:key/languages/setup but the page lists every site, and the proposed setup API is global. Decide one global page reached from a per-site banner, or a per-site page. |
Languages and setup |
| Visual coverage | About 45 of the original 169 states were rendered and inspected by the designer (see localization-corrections.md). Icons were never checked. Unseen frames may be broken; the visual check will reveal them. |
All SPA slices |
| Header widths | Editor header widths between 1024 and 1440 are specified, not drawn. The checker captures 1440, 1024 and 390 only. | Editor |
| Mobile Languages rows | Rows are taller than needed. Tune in implementation. | Languages and setup |
| Partial fixtures | Archive, Notes and Gallery exist only as single states. | Languages and setup |
| Mobile list page 2 | The designer could not reproduce the overflow-chip and tab-strip issue on mobile page 2; they removed a stray character and gave the "+N" chip a 44 px tap area. Check it against the app. | Article list |
Third-pass decisions (2026-11-03)
- No Languages item in the sidebar. Languages is reached from Settings → Sites → [site] → Languages, and from the setup banner.
- Mobile list rows show the version count on its own line.
- The Public URL pattern preview uses the site's default language (Journal previews the English address).
- A deleted source and an unreadable source show the same message after submit: "The source isn't available to you".
- "Save over theirs" is an error-coloured outline button with a warning icon; Keep my changes stays the only filled button.
- "Delete article" is atomic: on failure nothing is removed and every version stays.