Files
RenewCMS/docs/design/source/locale-spec.js
T

3482 lines
86 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
window.LOC = {
"groups": [
{
"id": "L1",
"title": "Site languages",
"route": "/settings/sites/:key/languages",
"api": "proposed: GET/PUT /v1/site/{key}/languages",
"note": "Navigation: Settings → Sites → [site] → Languages only. The site name is in the heading and breadcrumb. Site formatting locale, default content language, article language and social locale are four separate values. At least one language stays enabled and the default must be enabled: to retire the default, first Make default on another language, then Disable. Disable and Remove never delete or unpublish content. Disabled languages show a Disabled tag and a Re-enable action; their versions stay readable and editable per permission. Drafts can be published only after Re-enable (\"French is disabled on this site. Re-enable it to publish.\"). The language filter lists them under Disabled. Social locale per language has three states: Derived (from the language code, e.g. pt-BR gives pt_BR), Set (an owner typed it, e.g. fr_FR) and Not set (og:locale left out). Only owners edit it.",
"release": 1,
"states": [
[
"Single language",
"Owner opens Languages on Archive (one language, pt-BR kept)",
"sl",
"single",
"",
"loc.l1.single",
"Archive",
"loc.l1.single"
],
[
"Multilingual",
"Journal: four enabled, two disabled (Spanish, Italian)",
"sl",
"multi",
"tri",
"loc.l1.multi",
"Journal",
"loc.l1.multi"
],
[
"Loading",
"Request in flight",
"sl",
"loading",
"",
"loc.l1.loading",
"Journal",
"loc.l1.loading"
],
[
"Load failed",
"Network or server error",
"sl",
"error",
"",
"loc.l1.error",
"Journal",
"loc.l1.error"
],
[
"Add a language",
"Add language",
"sl",
"add",
"",
"loc.l1.add",
"Journal",
"loc.l1.add"
],
[
"Add failed",
"Server error, selection kept",
"sl",
"saveFail",
"",
"loc.l1.saveFail",
"Journal",
"loc.l1.saveFail"
],
[
"Duplicate language",
"Already enabled",
"sl",
"adddup",
"",
"loc.l1.adddup",
"Journal",
"loc.l1.adddup"
],
[
"Only a broader tag supported",
"Add Portuguese (Portugal): substitution explained, confirmation required",
"sl",
"broader",
"",
"loc.l1.broader",
"Journal",
"loc.l1.broader"
],
[
"Make default",
"Row menu",
"sl",
"default",
"",
"loc.l1.default",
"Journal",
"loc.l1.default"
],
[
"Set default failed",
"Server error, nothing changed",
"sl",
"defaultFail",
"",
"loc.l1.defaultFail",
"Journal",
"loc.l1.defaultFail"
],
[
"Disable with content",
"Row menu → Disable German",
"sl",
"disable",
"",
"loc.l1.disable",
"Journal",
"loc.l1.disable"
],
[
"Languages changed",
"Another owner changed them: \"Languages changed. Reload.\"",
"sl",
"stale",
"",
"loc.l1.stale",
"Journal",
"loc.l1.stale"
],
[
"Disable default blocked",
"Disable English",
"sl",
"disableDefault",
"",
"loc.lang.disableDefault",
"Journal",
"loc.l1.disableDefault"
],
[
"Disable last blocked",
"Only one enabled",
"sl",
"disableLast",
"",
"loc.lang.disableLast",
"Journal",
"loc.l1.disableLast"
],
[
"Re-enable",
"Spanish row → Re-enable",
"sl",
"reenable",
"",
"loc.l1.reenable",
"Journal",
"loc.l1.reenable"
],
[
"Re-enabled",
"Toast after success",
"sl",
"reenableDone",
"",
"loc.l1.reenableDone",
"Journal",
"loc.l1.reenableDone"
],
[
"Re-enable failed",
"Dialog error, still disabled",
"sl",
"reenableFail",
"",
"loc.l1.reenableFail",
"Journal",
"loc.l1.reenableFail"
],
[
"Remove (no content)",
"Italian (0 versions)",
"sl",
"remove",
"",
"loc.l1.remove",
"Journal",
"loc.l1.remove"
],
[
"Remove failed",
"Dialog error, still listed",
"sl",
"removeFail",
"",
"loc.l1.removeFail",
"Journal",
"loc.l1.removeFail"
],
[
"Remove blocked",
"Spanish has versions",
"sl",
"removeBlocked",
"",
"loc.l1.removeBlocked",
"Journal",
"loc.l1.removeBlocked"
],
[
"Read-only view",
"Non-owner: list and social locale readable; no controls",
"sl",
"readonly",
"",
"loc.l1.readonly",
"Journal",
"loc.l1.readonly"
],
[
"Saved",
"After Add language",
"sl",
"saved",
"",
"loc.l1.saved",
"Journal",
"loc.l1.saved"
],
[
"Social locale: three states",
"Atelier: Derived en_GB and pt_BR, Set fr_FR, Not set",
"sl",
"social",
"tri",
"loc.l1.social",
"Atelier",
"loc.l1.social"
],
[
"Social locale: edit",
"Edit on French (Set: fr_FR)",
"sl",
"socialEdit",
"",
"loc.l1.socialEdit",
"Atelier",
"loc.l1.socialEdit"
],
[
"Social locale: invalid",
"fr-FR typed with a hyphen",
"sl",
"socialInvalid",
"",
"loc.l1.socialInvalid",
"Atelier",
"loc.l1.socialInvalid"
]
]
},
{
"id": "L1b",
"title": "Public URL pattern (owner setting)",
"route": "/settings/sites/:key/public-url",
"api": "proposed: GET/PUT /v1/site/{key} { public_url_pattern }",
"note": "Owner-only. One pattern per site, for example https://example.com/{lang}/{slug}. Supported placeholders: {lang} (language code) and {slug} (the article address in that language). {slug} is required: without it every article would share one address. The preview uses a real article of the site. When unset, article panels show \"Public URL not configured\" and link here. No URL is invented.",
"release": 1,
"states": [
[
"Set",
"Pattern saved, preview shown",
"pu",
"set",
"",
"loc.l1b.set",
"Journal",
"loc.l1b.set"
],
[
"Editing",
"Live preview follows the field",
"pu",
"editing",
"",
"loc.l1b.editing",
"Journal",
"loc.l1b.editing"
],
[
"Unknown placeholder",
"{language} typed",
"pu",
"unknown",
"",
"loc.l1b.unknown",
"Journal",
"loc.l1b.unknown"
],
[
"Bad scheme",
"ftp:// typed",
"pu",
"scheme",
"",
"loc.l1b.scheme",
"Journal",
"loc.l1b.scheme"
],
[
"Not encoded",
"A space in the path",
"pu",
"encoded",
"",
"loc.l1b.encoded",
"Journal",
"loc.l1b.encoded"
],
[
"Missing {slug}",
"Pattern without {slug}",
"pu",
"noSlug",
"",
"loc.l1b.noSlug",
"Journal",
"loc.l1b.noSlug"
],
[
"Saved",
"Toast",
"pu",
"saved",
"",
"loc.l1b.saved",
"Journal",
"loc.l1b.saved"
],
[
"Unset",
"No pattern; opened from \"Public URL not configured\"",
"pu",
"unset",
"",
"loc.l1b.unset",
"Journal",
"loc.l1b.unset"
],
[
"Read-only view",
"Non-owner: pattern readable, no controls",
"pu",
"viewOnly",
"",
"loc.l1b.viewOnly",
"Journal",
"loc.l1b.viewOnly"
]
]
},
{
"id": "L2",
"title": "First-time setup for existing articles",
"route": "/settings/sites/:key/languages/setup",
"api": "proposed: GET /v1/language/setup, POST /v1/language/setup { sites:[{ key, lang }] } (lang is the full tag, e.g. pt-BR)",
"note": "Opened from the Languages banner of any site; :key is the site it was opened from, and the page lists every site that still has unlabelled articles. Setup assigns a language per site and applies in one transaction. It never translates, links or moves articles. Each site is seeded from its full Site.Locale when a matching language entry exists: pt-BR becomes Portuguese (Brazil). A site with no locale has no seed and the owner must choose. Nothing applies until the owner confirms every row, including a site with no articles. Retrying is safe.",
"release": 1,
"states": [
[
"Ready to assign",
"Journal (en), Notes (no locale, must choose), Archive (pt-BR kept)",
"su",
"ready",
"",
"loc.l2.ready",
"Journal, Notes, Archive",
"loc.l2.ready"
],
[
"No-locale site",
"Notes only: the owner must choose",
"su",
"noLocale",
"",
"loc.l2.noLocale",
"Notes",
"loc.l2.noLocale"
],
[
"Site with no articles",
"Gallery has no articles; en-GB kept, confirm and Save",
"su",
"empty",
"",
"loc.l2.empty",
"Gallery",
"loc.l2.empty"
],
[
"Applying",
"Request in flight",
"su",
"applying",
"",
"loc.l2.applying",
"Journal, Notes, Archive",
"loc.l2.applying"
],
[
"Setup already running",
"Another owner started it; starting again is safe",
"su",
"inProgress",
"",
"loc.l2.inProgress",
"Journal, Notes, Archive",
"loc.l2.inProgress"
],
[
"Done",
"16 articles labelled",
"su",
"done",
"",
"loc.l2.done",
"Journal, Notes, Archive",
"loc.l2.done"
],
[
"Failed",
"Nothing changed; choices kept; retry is safe",
"su",
"fail",
"",
"loc.l2.fail",
"Journal, Notes, Archive",
"loc.l2.fail"
]
]
},
{
"id": "L3",
"title": "New article",
"route": "/articles (New article modal)",
"api": "existing POST /v1/article/ + proposed lang, story_id",
"note": "Single-language sites show no language field. Multilingual sites show Language and a New article / Translation of an existing article choice. The translation path uses the same endpoint and rules as Add translation. The picker lists only articles the user can read; the chosen version is the source.",
"release": 1,
"states": [
[
"Single language",
"Title and site only",
"na",
"single",
"",
"loc.l3.single",
"Archive",
"loc.l3.single"
],
[
"Multilingual: new article",
"Language defaults to the site default",
"na",
"multi",
"",
"loc.l3.multi",
"Journal",
"loc.l3.multi"
],
[
"Translation of an article",
"Article picker + language",
"na",
"translation",
"",
"loc.l3.translation",
"Journal",
"loc.l3.translation"
],
[
"Site changed",
"Selected language not offered by the new site",
"na",
"sitechange",
"",
"loc.l3.sitechange",
"Journal, Notes",
"loc.l3.sitechange"
]
]
},
{
"id": "L4",
"title": "Add a translation",
"route": "/article/{id}/edit?dialog=add-translation&lang=fr",
"api": "proposed: POST /v1/article/{id}/translations",
"note": "Always creates a draft. Source: the user picks which readable version to copy from (default: the default-language version if readable, else the first readable). The source version is captured in the same transaction as the copy. Copied text stays in the source language until edited; the version starts as Needs review. Start empty: no tracked source, so freshness shows No source. The primary action stays in the sticky footer on mobile; the body scrolls.",
"release": 1,
"states": [
[
"Copy from source",
"Editor → language menu → Add translation",
"at",
"form",
"",
"loc.l4.form",
"Journal",
"loc.l4.form"
],
[
"Start empty",
"Title only",
"at",
"empty",
"",
"loc.l4.empty",
"Journal",
"loc.l4.empty"
],
[
"Creating",
"Submit in flight",
"at",
"saving",
"",
"loc.l4.saving",
"Journal",
"loc.l4.saving"
],
[
"Exists, readable",
"Conflict; Open offered",
"at",
"dup",
"",
"loc.dup.readable",
"Journal",
"loc.l4.dup"
],
[
"Exists, not readable",
"Neutral error, no Open",
"at",
"dupHidden",
"",
"loc.dup.hidden",
"Journal",
"loc.l4.dupHidden"
],
[
"Failed",
"Server error, entries kept",
"at",
"fail",
"",
"loc.l4.fail",
"Journal",
"loc.l4.fail"
],
[
"Source deleted",
"Deleted while the dialog was open",
"at",
"gone",
"",
"loc.l4.gone",
"Journal",
"loc.l4.gone"
],
[
"Source no longer readable",
"Access lost between opening and submitting",
"at",
"unreadable",
"",
"loc.l4.unreadable",
"Journal",
"loc.l4.unreadable"
],
[
"Language disabled mid-flow",
"An owner disabled French while the dialog was open",
"at",
"langOff",
"",
"loc.l4.langOff",
"Journal",
"loc.l4.langOff"
],
[
"Long dialog",
"Long title, many authors and images; body scrolls, footer stays",
"at",
"long",
"",
"loc.l4.long",
"Journal",
"loc.l4.long"
]
]
},
{
"id": "L5",
"title": "Editor: header, actions and title",
"route": "/article/{id}/edit (one id per version)",
"api": "proposed: GET /v1/article/{id} → translations[], capabilities[]",
"note": "Header priority and the action matrix are in the tables below. One filled primary button at most: Save and publish when allowed, dirty and offline; otherwise Save when dirty; otherwise Publish when clean and offline. Save stays separate and secondary. Desktop collapse order as width shrinks (1440 → 1024): reference button loses its label, Unpublish moves to the overflow menu, the freshness badge becomes icon-only. Mobile: title, language, publication and dirty badges stay at the top; Save, primary and an overflow button sit in a fixed bottom bar; reference, Unpublish, Edit slug, Move and Delete go to the overflow sheet. The language control is a menu button with a menu (not a listbox).",
"release": 1,
"states": [
[
"Clean draft",
"Open a draft you can publish; title pristine",
"ed",
"default",
"tri",
"loc.l5.default",
"Journal",
"loc.l5.default"
],
[
"Dirty draft",
"Can edit, can't publish: Save is primary",
"ed",
"dirty",
"tri",
"loc.l5.dirty",
"Journal",
"loc.l5.dirty"
],
[
"Dirty draft, publish allowed",
"Save and publish French is primary; Save secondary",
"ed",
"dirtyPublish",
"tri",
"loc.l5.dirtyPublish",
"Journal",
"loc.l5.dirtyPublish"
],
[
"Clean online",
"Unpublish only; no primary",
"ed",
"cleanOnline",
"tri",
"loc.l5.cleanOnline",
"Journal",
"loc.l5.cleanOnline"
],
[
"Dirty online",
"Save updates the live page; Unpublish stays",
"ed",
"onlineDirty",
"tri",
"loc.l5.onlineDirty",
"Journal",
"loc.l5.onlineDirty"
],
[
"Saving",
"Save in flight",
"ed",
"saving",
"",
"loc.l5.saving",
"Journal",
"loc.l5.saving"
],
[
"Saved",
"Toast",
"ed",
"saved",
"",
"loc.l5.saved",
"Journal",
"loc.l5.saved"
],
[
"Save failed",
"Stays unsaved; Try saving again; nothing published",
"ed",
"saveFail",
"tri",
"loc.l5.saveFail",
"Journal",
"loc.l5.saveFail"
],
[
"Publish failed after save",
"Saved as a draft; Retry publish; edits shown as saved",
"ed",
"pubFail",
"tri",
"loc.l5.pubFail",
"Journal",
"loc.l5.pubFail"
],
[
"Overflow menu",
"More actions (sheet on mobile)",
"ed",
"ovf",
"",
"loc.l5.ovf",
"Journal",
"loc.l5.ovf"
],
[
"Menu: four languages",
"Open menu",
"ed",
"menu",
"",
"loc.l5.menu",
"Journal",
"loc.l5.menu"
],
[
"Menu: two languages",
"Two-language site",
"ed",
"two",
"",
"loc.l5.two",
"Journal",
"loc.l5.two"
],
[
"Menu: many languages",
"Scrolls inside the viewport, with filter",
"ed",
"many",
"",
"loc.l5.many",
"Journal",
"loc.l5.many"
],
[
"Switch with unsaved changes",
"Choose English while dirty",
"ed",
"unsaved",
"",
"loc.l5.unsaved",
"Journal",
"loc.l5.unsaved"
],
[
"Loading version",
"Slow switch",
"ed",
"loading",
"",
"loc.l5.loading",
"Journal",
"loc.l5.loading"
],
[
"Switch failed",
"Stays on current version",
"ed",
"navFail",
"",
"loc.l5.navFail",
"Journal",
"loc.l5.navFail"
],
[
"Version unavailable",
"Deleted or no access (neutral)",
"ed",
"gone",
"",
"loc.l5.gone",
"Journal",
"loc.l5.gone"
],
[
"Title: focus",
"Field focused, not changed",
"ed",
"focus",
"",
"loc.l5.focus",
"Journal",
"loc.l5.focus"
],
[
"Title: editing",
"Typing; counter shown",
"ed",
"titleEdit",
"",
"loc.l5.titleEdit",
"Journal",
"loc.l5.titleEdit"
],
[
"Title: unsaved",
"Changed, focus left",
"ed",
"titleUnsaved",
"",
"loc.l5.titleUnsaved",
"Journal",
"loc.l5.titleUnsaved"
],
[
"Title: invalid",
"Empty: \"Enter a title\"",
"ed",
"invalid",
"",
"loc.l5.invalid",
"Journal",
"loc.l5.invalid"
],
[
"Title: too short",
"Under 3 characters",
"ed",
"titleShort",
"",
"loc.l5.titleShort",
"Journal",
"loc.l5.titleShort"
],
[
"Title: too long",
"Over 200 characters",
"ed",
"titleLong",
"",
"loc.l5.titleLong",
"Journal",
"loc.l5.titleLong"
],
[
"Title: saving",
"Save in flight",
"ed",
"titleSaving",
"",
"loc.l5.titleSaving",
"Journal",
"loc.l5.titleSaving"
],
[
"Title: save failed",
"Text kept, still unsaved, Try again",
"ed",
"failed",
"",
"loc.l5.failed",
"Journal",
"loc.l5.failed"
]
]
},
{
"id": "L5b",
"title": "Permission states (per version)",
"route": "/article/{id}/edit",
"api": "capabilities[] on GET /v1/article/{id}; every PATCH/DELETE rechecked on the server",
"note": "Every action renders from its own capability (read, edit, publish, delete, create translation, review). See the capability matrix. Missing-translation rows appear only for users who can create; unreadable siblings are absent with no placeholder and no count. The formatting toolbar is hidden whenever edit is missing.",
"release": 1,
"states": [
[
"P1 Read-only version",
"Banner, read-only fields, no Save, Publish, Delete, toolbar; reference stays",
"perm",
"ro",
"tri",
"loc.perm.readonly",
"Journal",
"loc.l5b.ro"
],
[
"P1 menu",
"No Add translation row",
"perm",
"roMenu",
"",
"loc.l5b.roMenu",
"Journal",
"loc.l5b.roMenu"
],
[
"P2 Edit, no publish",
"Save only; hint about the publish permission",
"perm",
"p2",
"tri",
"loc.perm.editNoPublish",
"Journal",
"loc.l5b.p2"
],
[
"P3 Publish, no edit",
"Publish only; fields read-only",
"perm",
"p3",
"tri",
"loc.perm.publishNoEdit",
"Journal",
"loc.l5b.p3"
],
[
"P4 Sibling readable, not editable",
"Arabic row tagged Read only",
"perm",
"p4",
"",
"loc.perm.siblingReadOnly",
"Journal",
"loc.l5b.p4"
],
[
"P5 Sibling not readable",
"German absent, no count, no placeholder",
"perm",
"p5",
"",
"loc.perm.siblingHidden",
"Journal",
"loc.l5b.p5"
],
[
"P6 Can create, not modify all",
"Delete article and Move disabled with the reason; Delete French version available",
"perm",
"p6",
"",
"loc.perm.createNotAll",
"Journal",
"loc.l5b.p6"
],
[
"Menu without Add translation",
"No create permission; Arabic is missing but offers nothing",
"perm",
"noAdd",
"",
"loc.l5b.noAdd",
"Journal",
"loc.l5b.noAdd"
],
[
"Menu with Add translation",
"Same article, user may create",
"perm",
"withAdd",
"",
"loc.l5b.withAdd",
"Journal",
"loc.l5b.withAdd"
]
]
},
{
"id": "L6",
"title": "Source reference (release 2)",
"route": "/article/{id}/edit?ref=:sourceId",
"api": "proposed: GET /v1/article/{id} (read-only fetch of the reference)",
"note": "Release 2. The temporary reference (?ref=, dashed outline, \"Temporary reference\") and the tracked review source (solid \"Tracked source\" badge in the review banner) are separate: changing the reference never changes the tracked source. Show source reference is in the editor header (desktop, icon-only at 1024) and the overflow sheet (mobile). Each pane scrolls independently. dir and lang go on the title and body of the editing pane and of the reference, never on the app shell.",
"release": 2,
"states": [
[
"Side by side",
"Show source reference",
"src",
"split",
"tri",
"loc.l6.split",
"Journal",
"loc.l6.split"
],
[
"Reference hidden",
"Hide source reference; button returns",
"src",
"closed",
"",
"loc.l6.closed",
"Journal",
"loc.l6.closed"
],
[
"Reopened",
"Scroll positions kept",
"src",
"reopen",
"",
"loc.l6.reopen",
"Journal",
"loc.l6.reopen"
],
[
"Change reference",
"Menu; tracked source stays English",
"src",
"changeRef",
"",
"loc.l6.changeRef",
"Journal",
"loc.l6.changeRef"
],
[
"Narrow desktop (1024)",
"True 1024 frame",
"src",
"narrow",
"narrow",
"loc.l6.narrow",
"Journal",
"loc.l6.narrow"
],
[
"Long content",
"Both panes scroll independently",
"src",
"long",
"",
"loc.l6.long",
"Journal",
"loc.l6.long"
],
[
"Arabic editing, English reference",
"dir=rtl on the Arabic title and body only",
"src",
"rtl",
"",
"loc.l6.rtl",
"Journal",
"loc.l6.rtl"
],
[
"French editing, Arabic reference",
"dir=rtl on the reference pane only",
"src",
"arRef",
"",
"loc.l6.arRef",
"Journal",
"loc.l6.arRef"
],
[
"Mobile: editing",
"Segment",
"src",
"mobileEdit",
"",
"loc.l6.mobileEdit",
"Journal",
"loc.l6.mobileEdit"
],
[
"Mobile: source",
"Segment",
"src",
"mobileSrc",
"",
"loc.l6.mobileSrc",
"Journal",
"loc.l6.mobileSrc"
],
[
"Mobile: closed",
"Show source reference in the bottom bar or sheet",
"src",
"mobileClosed",
"",
"loc.l6.mobileClosed",
"Journal",
"loc.l6.mobileClosed"
],
[
"Reference unavailable",
"Deleted or unreadable",
"src",
"unavail",
"",
"loc.l6.unavail",
"Journal",
"loc.l6.unavail"
]
]
},
{
"id": "L7",
"title": "Article list and coverage",
"route": "/articles?site&lang&tab=all|missing|review&q&status&author&seo&page&size",
"api": "proposed: lang, tab=all|missing|review on GET /v1/article/ (same names as the URL)",
"note": "Journal: 8 articles, 17 versions, page size 5. The list header shows the unit: \"8 articles\" (all languages, one row per article with its translations), \"4 French versions\" (language selected), \"4 articles not in French\" (missing tab), \"7 articles need translation\", \"2 articles need review\". Coverage chips: default language first, then other existing versions, then disabled-language versions, then dashed missing chips (only for languages the user can create). Chips collapse after 5 at 1440 and 4 at 1024 and 390, leaving a \"+N\" chip. Row actions are the same on every width: Open, Open another language, Add translation, Publish or Unpublish, Copy API URL, Delete version; every row has the row menu button. All state is in the URL.",
"release": 1,
"states": [
[
"All languages, page 1",
"5 of 8 articles",
"li",
"all",
"tri",
"loc.l7.all",
"Journal",
"loc.l7.all"
],
[
"All languages, page 2 (last)",
"3 of 8 articles",
"li",
"page2",
"tri",
"loc.l7.page2",
"Journal",
"loc.l7.page2"
],
[
"French selected",
"4 French versions",
"li",
"fr",
"tri",
"loc.l7.fr",
"Journal",
"loc.l7.fr"
],
[
"Not in French",
"4 articles not in French",
"li",
"frMissing",
"tri",
"loc.l7.frMissing",
"Journal",
"loc.l7.frMissing"
],
[
"Nothing missing",
"Press: all articles translated; no summary, no pager",
"li",
"emptyMissing",
"tri",
"loc.l7.emptyMissing",
"Press",
"loc.l7.emptyMissing"
],
[
"No results",
"Search \"zinc\"; every count 0",
"li",
"noResults",
"tri",
"loc.l7.noResults",
"Journal",
"loc.l7.noResults"
],
[
"Loading",
"Counts shown as –",
"li",
"frLoading",
"tri",
"loc.l7.frLoading",
"Journal",
"loc.l7.frLoading"
],
[
"Failed",
"Filters kept",
"li",
"listFail",
"tri",
"loc.l7.listFail",
"Journal",
"loc.l7.listFail"
],
[
"Limited access",
"Reads English only, can't create: no dashed chips",
"li",
"limited",
"tri",
"loc.l7.limited",
"Journal (limited scope)",
"loc.l7.limited"
],
[
"Needs review tab",
"2 articles need review",
"li",
"review",
"tri",
"loc.l7.review",
"Journal",
"loc.l7.review"
],
[
"Author filter",
"Marta Alves: 3 articles, chips and Clear all",
"li",
"author",
"tri",
"loc.l7.author",
"Journal",
"loc.l7.author"
],
[
"Language filter",
"Enabled and Disabled groups",
"li",
"langFilter",
"tri",
"loc.l7.langFilter",
"Journal",
"loc.l7.langFilter"
],
[
"Row menu",
"Same actions on desktop and mobile",
"li",
"rowMenu",
"tri",
"loc.l7.rowMenu",
"Journal",
"loc.l7.rowMenu"
],
[
"Mobile filter sheet",
"Journal, French, Draft: 2 results",
"li",
"filters",
"tri",
"loc.l7.filters",
"Journal",
"loc.l7.filters"
],
[
"Tabs scrolled",
"Not in French reachable; fade and chevrons",
"li",
"tabsScroll",
"",
"loc.l7.tabsScroll",
"Journal",
"loc.l7.tabsScroll"
],
[
"Eight languages, long labels, filters",
"Atelier: chips collapse to +3, +4, +4; Draft and SEO filters",
"li",
"long",
"tri",
"loc.l7.long",
"Atelier",
"loc.l7.long"
]
]
},
{
"id": "L8",
"title": "Publishing and deletion",
"route": "same as the editor",
"api": "existing PATCH /v1/article/{id}/publish and /unpublish and DELETE per article id",
"note": "Every action names its language. No bulk publish. With unsaved edits the primary reads Save and publish and saves first; if the save fails nothing is published and the edits stay unsaved; if the save works and publishing fails the edits show as saved. Unpublish never saves: the saved version goes offline and local edits stay in the editor. Saving an online version updates the live page immediately, and the editor says so. Deletion confirmations name only readable versions.",
"release": 1,
"states": [
[
"Publish French",
"Confirm, English untouched",
"pd",
"publish",
"",
"loc.l8.publish",
"Journal",
"loc.l8.publish"
],
[
"Publishing blocked: language disabled",
"French disabled on the site",
"pd",
"publishDisabled",
"",
"loc.lang.publishDisabled",
"Journal",
"loc.l8.publishDisabled"
],
[
"Save and publish French",
"Unsaved edits: saves first, then publishes",
"pd",
"saveAndPublish",
"",
"loc.l8.saveAndPublish",
"Journal",
"loc.l8.saveAndPublish"
],
[
"Save failed, nothing published",
"Toast; edits kept and retryable",
"pd",
"saveFailPublish",
"",
"loc.l8.saveFailPublish",
"Journal",
"loc.l8.saveFailPublish"
],
[
"Unpublish French",
"Confirm",
"pd",
"unpublish",
"",
"loc.l8.unpublish",
"Journal",
"loc.l8.unpublish"
],
[
"Unpublish with unsaved edits",
"Saved version goes offline; edits kept; nothing saved",
"pd",
"unpublishDirty",
"",
"loc.l8.unpublishDirty",
"Journal",
"loc.l8.unpublishDirty"
],
[
"Delete French version",
"Confirm",
"pd",
"delete",
"",
"loc.l8.delete",
"Journal",
"loc.l8.delete"
],
[
"Delete a source version",
"Warns about review tracking",
"pd",
"deleteSource",
"",
"loc.l8.deleteSource",
"Journal",
"loc.l8.deleteSource"
],
[
"Delete the only version",
"Deletes the article",
"pd",
"deleteLast",
"",
"loc.l8.deleteLast",
"Journal",
"loc.l8.deleteLast"
],
[
"Delete article (all readable versions)",
"Names readable versions only",
"pd",
"deleteStoryOk",
"",
"loc.l8.deleteStoryOk",
"Journal",
"loc.l8.deleteStoryOk"
],
[
"Delete article blocked",
"Neutral, no version named",
"pd",
"deleteStory",
"",
"loc.l8.deleteStory",
"Journal",
"loc.l8.deleteStory"
],
[
"Published",
"Toast",
"pd",
"done",
"",
"loc.l8.done",
"Journal",
"loc.l8.done"
],
[
"Publish failed",
"Toast",
"pd",
"fail",
"",
"loc.l8.fail",
"Journal",
"loc.l8.fail"
]
]
},
{
"id": "L9",
"title": "Freshness and review (release 2)",
"route": "editor banner · list badge",
"api": "proposed: POST /v1/article/{id}/review { source_article_id, source_revision, translation_revision } (internal fields; never shown)",
"note": "Release 2. Publication (solid badge) and freshness (outlined badge with refresh icon) are separate. Labels: No version, Draft, Needs review, Reviewed. A copied draft starts as Needs review. The dialog shows \"Changed fields: Title, Body\", the source version with its date and the translation version with its date. No revision numbers appear in the UI.",
"release": 2,
"states": [
[
"Source updated",
"English changed after review",
"fr",
"warn",
"",
"loc.l9.warn",
"Journal",
"loc.l9.warn"
],
[
"Published, needs review",
"Still online",
"fr",
"pub",
"",
"loc.l9.pub",
"Journal",
"loc.l9.pub"
],
[
"Mark reviewed",
"Confirm with changed fields and dates",
"fr",
"review",
"",
"loc.l9.review",
"Journal",
"loc.l9.review"
],
[
"Source changed again",
"During the review",
"fr",
"changed",
"",
"loc.review.changed",
"Journal",
"loc.l9.changed"
],
[
"Translation changed",
"Saved by someone else while confirming",
"fr",
"changedTr",
"",
"loc.l9.changedTr",
"Journal",
"loc.l9.changedTr"
],
[
"Choose tracked source",
"Cycle prevented",
"fr",
"replace",
"",
"loc.l9.replace",
"Journal",
"loc.l9.replace"
],
[
"Source unavailable (access)",
"History kept",
"fr",
"inaccessible",
"",
"loc.src.inaccessible",
"Journal",
"loc.l9.inaccessible"
],
[
"Source deleted",
"Choose a new source",
"fr",
"gone",
"",
"loc.src.deleted",
"Journal",
"loc.l9.gone"
],
[
"Reviewed",
"Toast",
"fr",
"done",
"",
"loc.l9.done",
"Journal",
"loc.l9.done"
],
[
"Review failed",
"Toast",
"fr",
"fail",
"",
"loc.l9.fail",
"Journal",
"loc.l9.fail"
],
[
"Published and needs review in the list",
"French tab, Needs review: 2 versions",
"li",
"frReview",
"tri",
"loc.l9.frReview",
"Journal",
"loc.l9.frReview"
]
]
},
{
"id": "L10",
"title": "Metadata and public API",
"route": "Search and sharing panel · /integration",
"api": "existing slug, SEO fields; proposed alternates[], lang; exact lookup by site + lang + slug; resolution by story_id",
"note": "Slug is unique per (site, lang). Exact lookup is by site + language + slug and never falls back. Language resolution and fallback use the stable story_id. Website URLs come from the site's Public URL pattern; without it the panel says \"Public URL not configured\" and links to the setting. Canonical is the localized page itself unless an override is set. SEO labels: \"Fallback: French title\", \"No fallback\", \"Empty. English site default not used.\"",
"release": 1,
"states": [
[
"Search and sharing: French",
"Localized slug, canonical, alternates, fallback labels, Edit slug",
"seo",
"",
"",
"loc.seo.noFallback",
"Journal",
"loc.l10.default"
],
[
"Edit slug",
"Published version: warning about old links",
"seo",
"editSlug",
"",
"loc.slug.published",
"Journal",
"loc.l10.editSlug"
],
[
"Public URL not configured",
"No pattern, no fabricated URL",
"seo",
"unset",
"",
"loc.url.unset",
"Journal",
"loc.l10.unset"
],
[
"Social locale not set",
"fr has no region and none is set: og:locale omitted",
"seo",
"noSocial",
"",
"loc.l10.noSocial",
"Journal",
"loc.l10.noSocial"
],
[
"Integration: languages",
"Exact lookup, resolve by article group, errors",
"api",
"",
"",
"loc.l10.api",
"Journal",
"loc.l10.api"
]
]
},
{
"id": "L11",
"title": "Conflicts and sessions",
"route": "editor",
"api": "existing 401/403/404/409 patterns",
"note": "The conflict dialog gives four choices. Keep my changes is the only filled button; the other three are secondary and equal. \"Someone else saved this version at <time>\". No revision numbers. On mobile the dialog is a bottom sheet with Keep my changes first, so it needs no scrolling at 390 px.",
"release": 1,
"states": [
[
"Session expired",
"Draft kept locally",
"pm",
"session",
"",
"loc.l11.session",
"Journal",
"loc.l11.session"
],
[
"Restored after login",
"Editor shows \"Restored unsaved changes from 14:18\"",
"pm",
"restored",
"",
"loc.l11.restored",
"Journal",
"loc.l11.restored"
],
[
"Concurrent update",
"Four choices, local work kept",
"pm",
"conflict",
"",
"loc.conflict",
"Journal",
"loc.l11.conflict"
],
[
"Concurrent update after session expiry",
"Restored changes notice above the choices",
"pm",
"conflictRestored",
"",
"loc.l11.conflictRestored",
"Journal",
"loc.l11.conflictRestored"
],
[
"Reload theirs: confirm",
"\"This discards your unsaved changes.\"",
"pm",
"reloadConfirm",
"",
"loc.l11.reloadConfirm",
"Journal",
"loc.l11.reloadConfirm"
],
[
"Second conflict",
"Save over theirs failed again; nothing overwritten",
"pm",
"conflict2",
"",
"loc.l11.conflict2",
"Journal",
"loc.l11.conflict2"
],
[
"Download started",
"Toast; changes still in the editor",
"pm",
"download",
"",
"loc.l11.download",
"Journal",
"loc.l11.download"
]
]
},
{
"id": "L11b",
"title": "Move article to another site",
"route": "editor → site select",
"api": "proposed: POST /v1/story/{story_id}/move { site }",
"note": "Entry point is the site select in the editor details. Articles move as a whole, only when the destination enables every language they contain (disabled ones included) and the user can edit all versions. The note lists readable versions only; versions the user can't read are not listed or counted. Moves are atomic and revalidate permissions and conflicts in the transaction.",
"release": 1,
"states": [
[
"Site select",
"Note lists all four versions, one tagged Read only",
"pm",
"moveSelect",
"",
"loc.l11b.moveSelect",
"Journal",
"loc.l11b.moveSelect"
],
[
"Site select, hidden version",
"Arabic not readable: not listed or counted",
"pm",
"moveSelectHidden",
"",
"loc.l11b.moveSelectHidden",
"Journal",
"loc.l11b.moveSelectHidden"
],
[
"Confirm",
"Move to Atelier?",
"pm",
"moveConfirm",
"",
"loc.l11b.moveConfirm",
"Journal",
"loc.l11b.moveConfirm"
],
[
"In progress",
"Dialog locked",
"pm",
"moveBusy",
"",
"loc.l11b.moveBusy",
"Journal",
"loc.l11b.moveBusy"
],
[
"Moved",
"Toast",
"pm",
"moveDone",
"",
"loc.l11b.moveDone",
"Journal",
"loc.l11b.moveDone"
],
[
"Language missing in destination",
"Notes enables English only",
"pm",
"move",
"",
"loc.l11b.move",
"Journal, Notes",
"loc.l11b.move"
],
[
"Address conflict",
"Names the conflicting readable version only",
"pm",
"moveSlug",
"",
"loc.l11b.moveSlug",
"Journal, Atelier",
"loc.l11b.moveSlug"
],
[
"No permission",
"Neutral",
"pm",
"movePerm",
"",
"loc.l11b.movePerm",
"Journal",
"loc.l11b.movePerm"
]
]
},
{
"id": "L12",
"title": "Existing surfaces",
"route": "Home · Activity · Media · editor details",
"api": "existing; language added where it applies",
"note": "Single-language sites show none of this. Home counts articles, not versions.",
"release": 1,
"states": [
[
"Home: recent articles",
"Default-language title; code only when not default",
"sf",
"home",
"tri",
"loc.l12.home",
"Journal",
"loc.l12.home"
],
[
"Home: reader of French only",
"Titles in French with code FR",
"sf",
"homeFr",
"tri",
"loc.l12.homeFr",
"Journal",
"loc.l12.homeFr"
],
[
"Activity",
"Events name the language",
"sf",
"activity",
"tri",
"loc.l12.activity",
"Journal",
"loc.l12.activity"
],
[
"Media usage",
"Shared image counted once, versions listed",
"sf",
"media",
"tri",
"loc.l12.media",
"Journal",
"loc.l12.media"
],
[
"Article menu labels",
"Publish and Delete name the language",
"sf",
"menu",
"",
"loc.l12.menu",
"Journal",
"loc.l12.menu"
],
[
"API URL copy",
"Copies the version URL with ?lang=",
"sf",
"apiCopy",
"",
"loc.l12.apiCopy",
"Journal",
"loc.l12.apiCopy"
],
[
"SEO preview",
"Localized address and canonical",
"sf",
"seoPreview",
"",
"loc.l12.seoPreview",
"Journal",
"loc.l12.seoPreview"
],
[
"Site text fallback",
"Used only when its language matches the article",
"sf",
"siteFallback",
"",
"loc.l12.siteFallback",
"Journal",
"loc.l12.siteFallback"
]
]
}
],
"tables": [
{
"title": "Decisions",
"cols": [
"Question",
"Chosen behavior",
"Rationale",
"Existing behavior affected",
"Backend dependency",
"Release"
],
"w": "1fr 1.6fr 1.3fr 1fr 1fr 70px",
"rows": [
[
"Language model",
"One story group, one article id per language, one version per locale",
"Reuses the article editor, permissions and publish flow",
"None for single-language sites",
"group_id, lang on articles; unique (group_id, lang)",
"Initial"
],
[
"Default vs editing vs source",
"Three separate roles. Default is a site setting; editing language is the open article; source is chosen per translation",
"Default is not always the best source",
"None",
"source_article_id per version",
"Initial"
],
[
"Locale scope",
"Per site, list of enabled languages plus one default",
"Matches site-level settings",
"Settings → Sites gains Languages",
"site_languages table",
"Initial"
],
[
"Site locale field",
"Four distinct values: formatting locale (existing Site.Locale, still used for dates and numbers), default content language (new, seeded from the full Site.Locale and confirmed by the owner), article language (per version), social metadata locale (explicit or derived, never guessed)",
"Avoids silent reinterpretation",
"ResolveArticleSEO reads Site.Locale for social metadata (verify in repo); for localized articles it uses the article language",
"None",
"Initial"
],
[
"Disable vs remove",
"Disable keeps content and published versions. Remove only at zero versions",
"No configuration action deletes or unpublishes",
"None",
"Count per language; 409 language_in_use",
"Initial"
],
[
"Existing content",
"Setup assigns a language per site, in one transaction; each article becomes its own story",
"Metadata only; no guessed grouping",
"Articles gain lang",
"Bulk assign endpoint, atomic",
"Initial"
],
[
"Titles",
"Editable after creation. A title change never alters an existing slug. Slug changes only through explicit Edit slug; no redirects are promised (none in the backend)",
"Translated titles must be revisable; URLs stay stable. Same rule on single-language sites",
"CHANGE: titles were immutable. Slug policy applies everywhere",
"Allow title in PUT; slug rules",
"Initial"
],
[
"Slug uniqueness",
"Unique per (site, lang)",
"Same slug in two languages is normal",
"Today unique per site",
"Index change",
"Initial"
],
[
"Language control",
"Dropdown beside the title; rows show version state; interface language unchanged",
"Compact for 2 to 20 languages",
"Editor heading",
"translations[] on GET article",
"Initial"
],
[
"Switching",
"Opens another article id. Dirty form asks Keep editing, Discard, Save and switch",
"URL is the state; no lost work",
"None",
"None",
"Initial"
],
[
"Add translation",
"Draft only; copy or start empty; images/authors reused; copied text flagged Needs review",
"Copying is a start, not completion",
"None",
"POST translations; 409 language_exists",
"Initial"
],
[
"Source reference",
"Optional read-only pane; segment on mobile; no sync",
"Keeps the single editor default",
"None",
"Read access to source",
"Initial"
],
[
"Direction",
"dir on editor and reference panes only",
"Shell stays LTR",
"None",
"Per-language dir from locale",
"Initial"
],
[
"List: all languages",
"One row per story; title from default version, else first accessible; coverage chips",
"Avoids duplicates",
"Articles list",
"Grouped query, access-filtered",
"Initial"
],
[
"List: language selected",
"One row per version; Not in French tab lists stories",
"Units always stated",
"Tabs and counts",
"lang, tab params",
"Initial"
],
[
"Search",
"All languages: every accessible title. Language: that language only",
"Predictable",
"Search",
"Access-filtered",
"Initial"
],
[
"Publishing",
"Per version; copy always names the language; no bulk publish",
"Prevents accidental scope",
"Publish button copy",
"None",
"Initial"
],
[
"Deleting",
"Per version. Last version deletes the story. Article delete needs permission on all",
"Explicit scope",
"Delete dialog",
"None",
"Initial"
],
[
"Freshness",
"Needs review when the source revision changes after the reviewed revision; Mark as reviewed records it",
"Reliable, not timestamp-based",
"None",
"content_revision on versions; reviewed_source_revision",
"Initial"
],
[
"Revision-bumping fields",
"Title, body, excerpt, slug, image alt text, SEO and social text. Not authors, cover, site, publication",
"Only translatable content counts",
"None",
"Revision incremented in same write",
"Initial"
],
[
"Site move",
"Moves the whole story; destination must enable every language; slug conflicts block",
"No splitting or merging",
"Site select in editor",
"Move endpoint, validation",
"Initial"
],
[
"Hidden versions",
"Titles, counts, coverage, language summaries, previews, errors and deletion confirmations name only versions the user can read",
"No leakage",
"List counts, delete dialog",
"Filter at query level",
"Initial"
],
[
"Machine translation",
"Not included",
"Out of scope",
"None",
"None",
"Later"
],
[
"Cross-site stories",
"Not included",
"Out of scope",
"None",
"None",
"Later"
],
[
"Bulk publish / review",
"Not included",
"No clear need",
"None",
"None",
"Later"
],
[
"Slug policy",
"Slug generated once at creation. Changed only by Edit slug, before or after publishing. Published change warns: \"Links to the old address will stop working. RenewCMS does not create redirects.\"",
"One policy, no redirect promise",
"Slug field",
"Unique (site, lang)",
"Initial"
],
[
"Permission model",
"Capabilities per version: read, edit, publish, delete, create-translation, review. UI renders from capabilities[] on the article response, not from role names",
"Matches role/scope permissions",
"Editor, list",
"capabilities[]; server enforces every call",
"Initial"
],
[
"Missing vs hidden",
"\"Not in French\" lists stories where French is absent or unreadable to the user. Add French translation on a story with an unreadable French returns a neutral 409 (see Visibility policy)",
"Discovery cannot leak existence",
"List",
"Conflict check ignores read permission; response is neutral",
"Initial"
],
[
"Source-copy access",
"Read access to the chosen source is rechecked at submit, inside the transaction",
"Access can change while the dialog is open",
"Add translation",
"Neutral 403/404",
"Initial"
],
[
"Disabled languages",
"Versions stay readable and editable; publishing blocked until re-enabled; filter lists them under Disabled",
"No silent unpublishing",
"Site languages",
"enabled flag per site language",
"Initial"
],
[
"Header priority",
"Left to right: language dropdown, publication badge, status badge (unsaved, saving, saved, failed), freshness badge, Read only tag, title input (fills the remaining width, same row on desktop), then reference button, Unpublish, Save, primary, overflow. At most one filled button. Collapse order: reference label, Unpublish to overflow, freshness to icon",
"The primary action is never hidden",
"Editor header",
"None",
"Initial"
],
[
"Public URL pattern",
"Per-site owner setting with {lang} and {slug}; live preview from a real article; unset state links from \"Public URL not configured\"",
"No fabricated website URLs",
"Settings → Sites",
"public_url_pattern on site",
"Initial"
],
[
"Social locale per language",
"Per-language owner field on Languages: Derived (from the tag), Set (typed), Not set. Format language_REGION",
"Never guess a region",
"Languages page",
"social_locale per site language",
"Initial"
],
[
"Release split",
"Source reference (L6) and Freshness and review (L9) are release 2. Frames are drawn in full so they can be checked",
"Smaller first release",
"None",
"None",
"Release 2"
],
[
"UI wording",
"The UI says \"article\", never \"story\". No revision numbers, IDs or HTTP status names in user-facing copy. Internal notes may say story",
"Plain language",
"All localization copy",
"None",
"Initial"
]
]
},
{
"title": "Capability matrix (per version, enforced server-side)",
"cols": [
"Case",
"Read",
"Edit",
"Publish",
"Delete",
"Create translation",
"Review",
"UI result"
],
"w": "1.3fr .5fr .5fr .6fr .6fr .8fr .6fr 2.2fr",
"rows": [
[
"P1 Read-only version",
"Yes",
"No",
"No",
"No",
"If can create on site",
"No",
"Banner \"You can read this version. You can't edit it.\" No Save, Publish, Delete; formatting toolbar hidden; fields read-only; the reference action remains"
],
[
"P2 Edit, no publish",
"Yes",
"Yes",
"No",
"Per scope",
"Per scope",
"If can edit",
"Save shown; Publish hidden; hint \"Publishing needs the publish permission\""
],
[
"P3 Publish, no edit",
"Yes",
"No",
"Yes",
"Per scope",
"Per scope",
"No",
"Publish/Unpublish shown, fields read-only (only if existing permissions allow publish without edit)"
],
[
"P4 Sibling readable, not editable",
"Yes",
"No",
"No",
"No",
"n/a",
"n/a",
"Opens read-only; row tag \"Read only\""
],
[
"P5 Sibling not readable",
"No",
"No",
"No",
"No",
"n/a",
"n/a",
"Row absent; no count, no placeholder"
],
[
"P6 Can create, not modify all",
"Yes",
"Own versions",
"Own",
"Own",
"Yes",
"Own",
"Delete article and Move article disabled with the reason \"You can't delete this article.\" Delete French version stays available if allowed"
],
[
"Owner / Admin",
"Yes",
"Yes",
"Yes",
"Yes",
"Yes",
"Yes",
"All readable versions; not inferred from role label alone"
]
]
},
{
"title": "Field ownership, copy and fallback",
"cols": [
"Field",
"Own per version",
"Copied on \"Copy from source\"",
"Fallback if empty"
],
"w": "1.4fr 1fr 1.4fr 1.6fr",
"rows": [
[
"Title",
"Yes",
"Entered by the editor",
"None (required)"
],
[
"Slug",
"Yes",
"Generated from the new title",
"None"
],
[
"Body",
"Yes",
"Yes, flagged Needs review",
"None"
],
[
"Excerpt",
"Yes",
"Yes",
"First words of body"
],
[
"SEO title and description",
"Yes",
"Yes",
"Title / excerpt, then site defaults"
],
[
"Social text",
"Yes",
"Yes",
"SEO text"
],
[
"Image alt text",
"Yes",
"Yes",
"None"
],
[
"Cover and inline images",
"Reference, files shared",
"Yes, no duplicate files",
"None"
],
[
"Authors",
"Yes",
"Yes, editable",
"None"
],
[
"Site",
"Shared by the story",
"Same",
"None"
],
[
"Publication and timestamps",
"Yes",
"No",
"None"
]
]
},
{
"title": "API: existing vs proposed",
"cols": [
"Capability",
"Status",
"Request",
"Errors"
],
"w": "1fr .8fr 2fr 1.6fr",
"rows": [
[
"List articles",
"Existing, extended",
"GET /v1/article/?page=&size=&q=&status=&site=&seo= (existing). Proposed: lang, tab=all|missing|review (same names as the URL; no separate mode or needs_review)",
"400 invalid page/size (SPA never shows it)"
],
[
"Get article",
"Existing, extended",
"GET /v1/article/{id}. Proposed fields: lang, story_id, capabilities[], translations[] (readable only), source { article_id, revision, status: tracked|inaccessible|deleted }",
"404 for hidden or deleted"
],
[
"Create article",
"Existing, extended",
"POST /v1/article/. Proposed: lang, story_id, source_article_id, copy (routes to translation rules)",
"409 language_unavailable · 422 language_not_enabled"
],
[
"Edit",
"Existing, extended",
"PATCH /v1/article/{id}. Proposed: if_revision for overwrite recheck",
"409 revision_conflict { current_revision }"
],
[
"Publish / unpublish",
"Existing",
"PATCH /v1/article/{id}/publish, /unpublish. Per version",
"403 missing_permission · 422 language_disabled"
],
[
"Delete",
"Existing",
"DELETE /v1/article/{id}. Last version deletes the story",
"403 · 404"
],
[
"Add translation",
"Proposed",
"POST /v1/article/{id}/translations { lang, title, source_article_id, copy }. Path id is any readable version; source_article_id selects the copy source; source_revision captured in the transaction",
"409 language_unavailable (neutral) · 404 (neutral) · 422 · 403"
],
[
"Mark reviewed",
"Proposed",
"POST /v1/article/{id}/review { source_article_id, source_revision, translation_revision }",
"409 source_changed · translation_changed · source_replaced · 403"
],
[
"Change tracked source",
"Proposed",
"PATCH /v1/article/{id}/source { source_article_id|null }",
"422 source_cycle · 422 source_other_story · 403"
],
[
"Site languages",
"Proposed",
"GET/PUT /v1/site/{key}/languages { default, languages[{ lang, enabled }] }, atomic",
"422 default_not_enabled · no_enabled_language · 409 language_in_use · 409 languages_changed"
],
[
"Setup",
"Proposed",
"GET/POST /v1/language/setup { sites:[{ key, lang }] }, idempotent per site key; articles created during setup get the site default; retry after failure is safe. UI route /settings/sites/:key/languages/setup",
"403 · 409 setup_in_progress"
],
[
"Move story",
"Proposed",
"POST /v1/story/{story_id}/move { site }, atomic, revalidates",
"409 slug_conflict · 422 language_not_enabled · 403 (neutral)"
],
[
"Publish with unsaved edits",
"Client sequence",
"PATCH /v1/article/{id} (save) then PATCH /v1/article/{id}/publish. The second call is sent only if the first returns 200. No new endpoint",
"Save failure: nothing published, edits stay unsaved"
],
[
"Public article (two requests)",
"Proposed",
"Exact: GET /v1/public/site/{key}/lang/{lang}/article/{slug}. Resolve: GET /v1/public/site/{key}/story/{story_id}?lang=&fallback=default. Existing article-ID links unchanged",
"See Public lookup"
],
[
"Public URL pattern",
"Proposed",
"GET/PUT /v1/site/{key} { public_url_pattern }. Placeholders {lang}, {slug}. Owner only",
"422 unknown_placeholder · invalid_scheme · not_encoded · missing_slug · 403"
],
[
"Social locale",
"Proposed",
"PUT /v1/site/{key}/languages { languages[{ lang, enabled, social_locale|null }] }",
"422 invalid_social_locale"
]
]
},
{
"title": "Accessibility and interaction",
"cols": [
"Control",
"Accessible name and keyboard",
"Focus",
"Announcement"
],
"w": "1fr 2fr 1.2fr 1.4fr",
"rows": [
[
"Language control",
"\"Language: French. Switch version\". Enter or Down opens a menu; arrows move; Enter selects; Escape closes",
"Into the list on open; back to the control on close",
"Polite: \"Editing French version\""
],
[
"Title input",
"Accessible name \"Title (French)\", no visible label (the language dropdown beside it names the version); aria-describedby points to the help or error text; invalid sets aria-invalid and the message is an alert",
"Stays in the field; on Try again, back to the field",
"Polite: \"Saving title\", \"Title saved\"; errors are announced"
],
[
"Save, Save and publish, Retry publish",
"Buttons named by their text including the language (\"Save and publish French\"); busy state sets aria-busy; Tab order: title, language, status, reference, Unpublish, Save, primary, More actions",
"After save, stays on the button; after a failed save, on Try saving again",
"Polite: \"French saved\"; \"Saved as a draft. Publishing failed. Try publishing again.\""
],
[
"More actions menu or sheet",
"Menu button \"More actions\"; items are menuitems; disabled items use aria-disabled and show the reason as text",
"First enabled item on open; back to More actions on close",
"None"
],
[
"Reference button",
"\"Show source reference\" or \"Hide source reference\", icon-only at 1024 with the same name",
"Moves into the pane on show; back to the button on hide",
"Polite: \"Showing English reference\""
],
[
"Conflict dialog and sheet",
"role dialog, modal; options are buttons in order Keep my changes, Download my version, Reload theirs, Save over theirs; Escape equals Keep my changes",
"First button (Keep my changes) on open; back to the title on close",
"Polite: \"Download started\""
],
[
"Move panel and dialogs",
"Site select is a listbox named \"Site\"; the note is the description of the confirm button",
"Into the list; back to the site select",
"Polite: \"Moved to Atelier\""
],
[
"Add translation dialog",
"Focus trapped; Escape cancels unless creating",
"First field; back to the control",
"Polite: \"French draft created\""
],
[
"Source segment (mobile)",
"Two-button radiogroup, arrows switch",
"Stays on the segment",
"Polite: \"Showing English source\""
],
[
"Coverage chips",
"aria-label per chip: \"French: draft, needs review\". The \"+3\" chip is named \"3 more languages: …\"",
"Row, not chips. Dashed chips are buttons on desktop only when the user can create",
"None"
],
[
"List unit label",
"aria-live polite: \"4 articles not in French\"",
"None",
"Polite on every filter change"
],
[
"Status without color",
"Publication badges are solid with an icon (Published: check, Draft: pencil). Freshness badges are outlined with a refresh icon and a word. Save status always has an icon and a word",
"None",
"None"
],
[
"Touch targets",
"On mobile every button, menu row, chip button and segment is at least 44 px high; the bottom bar keeps the primary visible",
"None",
"None"
],
[
"Reduced motion",
"No slide or fade transitions on popovers, sheets and the tab strip scroll when prefers-reduced-motion is set; the saving spinner becomes a static icon with the word \"Saving\"",
"None",
"None"
]
]
},
{
"title": "Acceptance scenarios",
"cols": [
"#",
"Given / When / Then"
],
"w": "40px 1fr",
"rows": [
[
"1",
"Given a single-language site, when creating an article, then no language control appears anywhere."
],
[
"2",
"Given French is enabled, when an editor adds a French translation with \"Copy from English\", then a draft is created, the editor opens it, and the badge reads Draft and Needs review."
],
[
"3",
"When French is published, then English publication is unchanged."
],
[
"4",
"When English changes title or body after review, then French shows Needs review; changing authors does not."
],
[
"5",
"Given dirty French, when choosing English, then a three-choice dialog appears and no work is lost."
],
[
"6",
"Given a writer who cannot see the German draft, then German appears nowhere in lists, counts, menus or alternates."
],
[
"7",
"When disabling German with 12 versions, then all 12 stay and 3 published stay online."
],
[
"8",
"When two editors create French at once, then the second sees \"French is no longer available\" with entries kept."
],
[
"9",
"When moving a story to a site missing a language, then the move is blocked and nothing splits."
],
[
"10",
"When the interface language is Arabic or French, the editing language indicator still shows the article language."
],
[
"11",
"Given French has unsaved edits, when the user chooses Save and publish French and the save fails, then nothing is published and the edits stay unsaved with Try saving again."
],
[
"12",
"Given French is missing and unpublished, when a client resolves the story with lang=fr and fallback=default and the default is published, then the response is the default language with requested_lang fr and fallback true; with the default also unpublished it is 404 language_not_available."
],
[
"13",
"Given a site locale pt-BR, when setup seeds the default language, then it reads Portuguese (Brazil) pt-BR and the owner confirms it before anything applies."
]
]
},
{
"title": "Existing surfaces audit and verification",
"cols": [
"Item",
"Note"
],
"w": "1fr 3fr",
"rows": [
[
"Home",
"Recent articles show the default-language title with a language code when not default. States loc.l12.home, loc.l12.homeFr"
],
[
"Activity",
"Events gain the language: \"Published French version of …\". State loc.l12.activity"
],
[
"Article menu",
"Publish and Delete labels name the language on multilingual sites. State loc.l12.menu"
],
[
"API URL copy",
"Copies the version URL with ?lang=. State loc.l12.apiCopy"
],
[
"SEO preview",
"Shows the localized address and canonical. State loc.l12.seoPreview"
],
[
"Media usage",
"Counts a shared image once; lists versions that use it. State loc.l12.media"
],
[
"Site text fallback",
"A site-level text (for example a site description) is a fallback only for articles in the same language. A French article can use the French site text; an Arabic article with no Arabic site text gets \"No fallback\" and the tag is left out. State loc.l12.siteFallback"
],
[
"Verification limit",
"Repository files were not accessible. Statements about them are assumptions marked verify in repo."
],
[
"Decided",
"Slug policy, hidden-version disclosure, Home counting articles, and review storing the source version only (no text diff) are decided in the other tables."
]
]
},
{
"title": "Visibility and anti-enumeration policy",
"cols": [
"Situation",
"User can read it",
"User cannot read it"
],
"w": "1.2fr 1.6fr 1.6fr",
"rows": [
[
"Duplicate translation",
"\"French already exists for this article.\" Action: Open French version",
"\"French can't be created for this article.\" No Open action, no title, no hint a version exists"
],
[
"Create conflict response",
"409 language_exists with article_id",
"409 language_unavailable; identical body for exists-unreadable, disabled and lost race"
],
[
"Article delete confirmation",
"Names readable versions: \"Delete English and French?\"",
"Unreadable versions are not named or counted. If any blocks deletion: \"You can't delete this article.\" plus Delete this version"
],
[
"Source unavailable",
"Deleted: \"The source was deleted.\"",
"\"The source isn't available to you.\""
],
[
"Missing translation tab",
"Row shown; Add French translation",
"Same row; a 409 shows the neutral copy"
],
[
"Counts and summaries",
"Readable versions only",
"Never included"
]
]
},
{
"title": "Public lookup and URLs",
"cols": [
"Topic",
"Decision"
],
"w": "1fr 3fr",
"rows": [
[
"CMS API vs website",
"CMS API URLs (/v1/...) are never shown as website URLs. Website URLs come from a proposed per-site public_url_pattern (owner), e.g. https://example.com/{lang}/{slug}. Existing convention unverified."
],
[
"Pattern not configured",
"\"Public URL not configured\" with a link to site settings. No URL is fabricated. API links by article ID remain."
],
[
"Exact lookup",
"GET /v1/public/site/{key}/lang/{lang}/article/{slug}. Matches site + language + slug only (slug is unique per site and language). Missing or unpublished: 404 article_not_found. It never falls back and never tries another language's slug"
],
[
"Resolution by story",
"GET /v1/public/site/{key}/story/{story_id}?lang=&fallback=default. story_id is the stable story identifier, returned on every public response and in alternates[]. No lang means the site default language. Fallback is decided only by story_id"
],
[
"Example: requested version published",
"story st_01, lang=fr, French published: 200, lang fr, fallback false"
],
[
"Example: requested missing or unpublished",
"lang=fr, no published French. Without fallback: 404 language_not_available. With fallback=default and a published default (en): 200, lang en, requested_lang fr, fallback true"
],
[
"Example: default also missing or unpublished",
"lang=fr, fallback=default, French and the default both unavailable: 404 language_not_available. No third language is tried"
],
[
"Example: default requested but unpublished",
"lang=en, English unpublished, fallback=default: 404 language_not_available"
],
[
"Compatibility",
"Article-ID endpoints (GET /v1/article/{id}) and existing public URLs keep working. Setup labels existing articles with the site default language"
],
[
"Response identity",
"Always lang (actual language). alternates[] lists only published, enabled-language versions"
],
[
"Canonical",
"The localized page itself, unless an explicit canonical override is set"
],
[
"Default-language change",
"Does not change what existing URLs return; absent or unpublished default gives 404 unless fallback is requested"
]
]
},
{
"title": "Locale semantics",
"cols": [
"Value",
"Meaning",
"Used for"
],
"w": "1fr 2fr 1.6fr",
"rows": [
[
"Site formatting locale",
"Existing Site.Locale",
"SPA dates and numbers; kept"
],
[
"Default content language",
"New per-site setting. Setup seeds it from the full Site.Locale when a supported language entry matches (en-GB, pt-BR). If only the language part is supported it seeds that and says so. No locale: the owner must choose. The owner confirms or changes it explicitly; nothing applies unconfirmed",
"Fallback, new-article default, setup"
],
[
"Article language",
"BCP 47 tag per version (en, fr, pt-BR, ar)",
"html lang, API lang, hreflang"
],
[
"Social metadata locale",
"Resolved in order: 1) explicit social locale on the site language (set by an owner, e.g. fr: fr_FR); 2) the language tag already has a region (pt-BR gives pt_BR); 3) Site.Locale has the same language part (site en-GB and article en give en_GB); 4) otherwise none: og:locale and og:locale:alternate are omitted for that version and the editor shows Not set. No region is invented for a bare language. og:locale:alternate lists other published versions whose locale resolves by the same rules; the rest are left out",
"og:locale, og:locale:alternate"
],
[
"SEO fallbacks",
"Empty French SEO fields fall back to French title and excerpt. English site defaults are never applied silently to a non-default language: editor shows \"Empty. English site default not used.\" and the tag is omitted",
"Editor labels: \"Fallback: French title\", \"No fallback\""
]
]
},
{
"title": "Article list modes and sample data",
"cols": [
"Mode",
"Units and count",
"Representative title",
"Row opens",
"Search",
"Sort"
],
"w": "1fr 1.2fr 1.4fr 1fr 1.4fr 1fr",
"rows": [
[
"All languages",
"Stories",
"Default-language version if readable, else first readable by language code",
"Representative version",
"Any readable title",
"Story updated: latest readable version"
],
[
"Language selected",
"Versions in that language",
"That version",
"That version",
"That language only",
"Version updated"
],
[
"Missing language",
"Stories lacking it or unreadable in it",
"As All languages",
"Representative version",
"Titles in any readable language",
"Story updated"
],
[
"Filters",
"Status, Author, SEO, Needs review: a story matches if any readable version matches (language mode: the version)",
"",
"",
"",
""
],
[
"Story Author, Updated",
"Union of readable authors; latest readable update",
"",
"",
"",
""
],
[
"Differing sites",
"Language filter lists the union; Missing excludes sites that do not enable the language",
"",
"",
"",
""
],
[
"Dataset (locale-fixtures.js)",
"See the Fixture index. Journal: 8 articles, 17 versions, page size 5 (page 1 shows 5, page 2 shows 3). Enabled en (default), fr, de, ar; disabled es, it. Versions: en 7 (6 published), fr 4 (2 published, 2 Needs review), de 4 (3 published), ar 1, es 1, it 0. 4 articles have no French version. Needs translation 7, Needs review 2. Every list, tab count, unit label and pager is computed from locale-fixtures.js.",
"",
"",
"",
""
]
]
},
{
"title": "Review source and revision semantics",
"cols": [
"Topic",
"Decision"
],
"w": "1fr 3fr",
"rows": [
[
"Record",
"{ article_id, source_article_id, source_revision, translation_revision, reviewed_by, reviewed_at }"
],
[
"Confirmation guard",
"The dialog carries the versions the user saw. If either moved, the dialog says \"The English source changed while you were reviewing. Review again.\" or \"French was changed by someone else while you were reviewing. Review again.\" Nothing is marked"
],
[
"Change tracked source",
"Explicit action Change tracked source. Result: Needs review. Earlier reviews are kept as history"
],
[
"Cycles",
"Prevented (422 source_cycle). Source must be in the same story"
],
[
"Access loss vs deletion",
"Access lost: \"The source isn't available to you right now.\" history kept, Mark reviewed disabled. Deleted: \"Source deleted\" with Choose a new source"
],
[
"Source replacement",
"Pick another readable version of the story; freshness becomes Needs review"
],
[
"What changed",
"Changed-field summary only, shown as \"Changed fields: Title, Body\", with the source version date and the translation version date. No text diff and no revision numbers in the UI"
],
[
"Concurrent edits",
"Mark reviewed applies only to the revisions the user saw"
]
]
},
{
"title": "Title, slug and concurrent edit flows",
"cols": [
"Case",
"Behavior"
],
"w": "1fr 3fr",
"rows": [
[
"Title editing",
"Inline underline input in heading style, on the same row as the language, badges and actions on desktop (no box, no visible label). States: pristine, focus, editing, unsaved, invalid (\"Enter a title\"), too short, too long, saving, failed (value kept). Bounds 3 to 200 characters. Accessible label \"Title (French)\". Editing the title never changes the slug"
],
[
"Slug",
"See Slug policy. Edit slug lives in Search and sharing and in the overflow menu; its note says \"Editing the title never changes the slug.\""
],
[
"Conflict",
"\"Someone else saved this version at <time>.\" Choices: Keep my changes (filled; nothing sent), Download my version, Reload theirs (confirm: \"This discards your unsaved changes.\"), Save over theirs (sends the version fetched now; a newer save gives a new conflict)"
],
[
"Overwrite scope",
"\"Replaces the title, body and search fields of this version only. Other languages are untouched.\""
],
[
"Session expiry",
"Draft kept locally per article (title, body, search fields) until saved or discarded. After login: \"Restored unsaved changes from <time>.\""
]
]
},
{
"title": "Language configuration lifecycle",
"cols": [
"Action",
"Rule"
],
"w": "1fr 3fr",
"rows": [
[
"Disable default",
"Blocked: \"Choose another default first.\""
],
[
"Disable last enabled",
"Blocked: \"At least one language must stay enabled.\""
],
[
"Disable with content",
"Allowed; versions remain; published stay online; publishing blocked for drafts"
],
[
"Re-enable",
"Row action Re-enable; no data change"
],
[
"Remove",
"Only at zero versions; never the default"
],
[
"Save error",
"Dialog keeps language, default and toggle values; error above the footer"
],
[
"Story moves",
"Disabled-language versions count toward destination requirements"
]
]
},
{
"title": "Mobile list and prototype scope",
"cols": [
"Item",
"Spec"
],
"w": "1fr 3fr",
"rows": [
[
"Filter sheet",
"Site, Language, Status, Author, SEO; Clear all and \"Show N results\"; active filters show as chips with \"Remove filter Status: Draft\" and Clear all"
],
[
"Tabs",
"Scrolls with edge fade and buttons \"Scroll tabs left/right\"; Not in French is always reachable"
],
[
"Pager",
"Same page size as desktop (5); \"Page 1 of 2\"; the URL keeps page and size"
],
[
"Row actions",
"Identical capability on every width: see Row actions. Mobile uses the row menu button (44 px), desktop shows the same button (28 px)"
],
[
"Prototype flows",
"Not part of this pass. The LocaleLive prototype was not touched"
]
]
},
{
"title": "Editor header priority",
"cols": [
"Order",
"Item",
"1440",
"1024",
"390"
],
"w": "50px 1.4fr 1.4fr 1.4fr 1.8fr",
"rows": [
[
"1",
"Title input (underline, heading style; own row on mobile, shares the header row on desktop)",
"Full width",
"Full width",
"Full width"
],
[
"2",
"Language dropdown",
"Visible",
"Visible",
"Visible, 44 px"
],
[
"3",
"Publication badge",
"Visible",
"Visible",
"Visible"
],
[
"4",
"Primary action (the only filled button)",
"Rightmost before More actions",
"Same",
"Fixed bottom bar, fills the width left by Save and More"
],
[
"5",
"Status badge (Unsaved changes, Saving, Saved, Save failed)",
"Visible",
"Visible",
"Visible"
],
[
"6",
"Save (secondary)",
"Visible",
"Visible",
"Bottom bar, next to the primary"
],
[
"7",
"Freshness badge",
"Icon and word",
"Collapses first: icon only, name in aria-label",
"Wraps under the language row"
],
[
"8",
"Read only tag",
"Visible",
"Visible",
"Visible"
],
[
"9",
"Reference button",
"Icon and label",
"Collapses second: icon only",
"More actions sheet"
],
[
"10",
"Unpublish (secondary)",
"Visible",
"Collapses third: More actions menu",
"More actions sheet"
],
[
"11",
"More actions",
"Edit slug, Move article, Delete versions",
"Same plus Unpublish",
"Same plus reference and Unpublish"
]
]
},
{
"title": "Action matrix",
"cols": [
"State",
"Permissions",
"Primary (filled)",
"Secondary",
"Notes"
],
"w": "1.4fr 1.2fr 1.4fr 1.4fr 2fr",
"rows": [
[
"Clean draft",
"edit + publish",
"Publish French",
"Save (off)",
"loc.l5.default"
],
[
"Clean draft",
"edit only",
"None",
"Save (off)",
"Hint: Publishing needs the publish permission"
],
[
"Dirty draft",
"edit only",
"Save",
"None",
"loc.l5.dirty; \"Saving keeps it as a draft.\""
],
[
"Dirty draft",
"edit + publish",
"Save and publish French",
"Save",
"loc.l5.dirtyPublish"
],
[
"Clean online",
"edit + publish",
"None",
"Unpublish French, Save (off)",
"loc.l5.cleanOnline; \"This version is online. Saving updates the live page right away.\""
],
[
"Dirty online",
"edit + publish",
"Save",
"Unpublish French",
"loc.l5.onlineDirty"
],
[
"Saving",
"any",
"Saving… (busy)",
"None",
"loc.l5.saving"
],
[
"Saved",
"any",
"Publish French if allowed",
"Save (off)",
"loc.l5.saved"
],
[
"Save failed",
"edit",
"Try saving again",
"None",
"loc.l5.saveFail; nothing published"
],
[
"Publish failed after save",
"edit + publish",
"Retry publish",
"Save (off)",
"loc.l5.pubFail; \"Saved as a draft. Publishing failed. Try publishing again.\""
],
[
"Unpublish with edits",
"publish",
"Unchanged",
"Unpublish French",
"loc.l8.unpublishDirty; saved version goes offline, local edits stay, nothing is saved"
],
[
"Read-only",
"read",
"None",
"None",
"Reference button remains"
],
[
"Publish without edit",
"publish",
"Publish French",
"None",
"Fields read-only"
]
]
},
{
"title": "Title field states",
"cols": [
"State",
"Look",
"Message",
"Save"
],
"w": "1fr 2fr 2fr 1fr",
"rows": [
[
"Pristine",
"Heading-size text, neutral border",
"None",
"Off"
],
[
"Focus",
"Primary 2px underline",
"\"Changing the title doesn't change the address.\"",
"Off"
],
[
"Editing",
"Primary border, counter n / 200",
"Same note",
"On"
],
[
"Unsaved",
"Neutral border, Unsaved changes badge",
"Same note",
"On"
],
[
"Invalid",
"Error border and icon",
"\"Enter a title\"",
"Blocked"
],
[
"Too short",
"Error border, counter",
"\"Use at least 3 characters\"",
"Blocked"
],
[
"Too long",
"Error border, counter 203 / 200",
"\"Use 200 characters or fewer\"",
"Blocked"
],
[
"Saving",
"Spinner",
"\"Saving title…\"",
"Busy"
],
[
"Save failed",
"Error border and icon",
"\"Couldn't save the title. Your text is kept.\" Try again",
"On"
]
]
},
{
"title": "Row actions (same on every width)",
"cols": [
"Action",
"Shown when",
"Desktop",
"Mobile"
],
"w": "1.6fr 2fr 1fr 1fr",
"rows": [
[
"Open <language>",
"Always, for the version the row opens",
"Row click and menu",
"Row tap and menu"
],
[
"Open another language…",
"More than one readable version",
"Menu",
"Menu"
],
[
"Add translation…",
"User can create and a language is missing",
"Menu (dashed chip button as a shortcut)",
"Menu"
],
[
"Publish / Unpublish <language>",
"User can publish",
"Menu",
"Menu"
],
[
"Copy API URL",
"Always",
"Menu",
"Menu"
],
[
"Delete <language> version",
"User can delete",
"Menu",
"Menu"
]
]
},
{
"title": "Coverage chips for many languages",
"cols": [
"Width",
"Chips shown",
"Overflow",
"Dashed missing chips"
],
"w": "1fr 1.4fr 2fr 2fr",
"rows": [
[
"1440",
"5",
"\"+3\" chip for 8 languages; named \"3 more languages: …\"",
"Last in order; buttons only if the user can create; hidden otherwise"
],
[
"1024",
"4 (narrower column)",
"\"+4\"",
"Same"
],
[
"390",
"4, under the title",
"\"+4\"",
"Not buttons; added from the row menu"
],
[
"Order",
"Default language, other existing versions, disabled-language versions, then missing",
"",
"Never shown for languages the user can't create"
]
]
},
{
"title": "URL state (article list)",
"cols": [
"Param",
"Values",
"Default"
],
"w": "1fr 3fr 1fr",
"rows": [
[
"site",
"Site key",
"First site"
],
[
"lang",
"Language code; absent means all languages",
"all"
],
[
"tab",
"all, missing, review (Published and Drafts are the status filter)",
"all"
],
[
"q",
"Search text",
"empty"
],
[
"status",
"online, draft",
"any"
],
[
"author",
"Author name",
"any"
],
[
"seo",
"attention",
"any"
],
[
"page",
"Page number, starting at 1; filter changes reset it",
"1"
],
[
"size",
"Rows per page",
"5 in the fixture"
]
]
},
{
"title": "Public URL pattern validation",
"cols": [
"Case",
"Example",
"Message"
],
"w": "1fr 2fr 2fr",
"rows": [
[
"Valid",
"https://journal.example.com/{lang}/{slug}",
"Preview: https://journal.example.com/fr/comment-nous-chiffrons-une-renovation"
],
[
"Unknown placeholder",
"https://journal.example.com/{language}/{slug}",
"Unknown placeholder {language}. Use {lang} and {slug}."
],
[
"Bad scheme",
"ftp://journal.example.com/{lang}/{slug}",
"Start with https:// or http://."
],
[
"Not encoded",
"https://journal.example.com/my pages/{lang}/{slug}",
"Remove spaces and characters that need encoding. Use letters, digits, - and _."
],
[
"Missing {slug}",
"https://journal.example.com/{lang}",
"Add {slug} so each article has its own address."
],
[
"Unset",
"",
"Public URL not configured, with a link to this setting"
]
]
},
{
"title": "Social locale per language",
"cols": [
"State",
"Label",
"Meaning"
],
"w": "1fr 1fr 3fr",
"rows": [
[
"Derived",
"Derived: pt_BR",
"The language tag has a region. Not editable except by setting a value"
],
[
"Set",
"Set: fr_FR",
"An owner typed it. Format language_REGION"
],
[
"Not set",
"Not set",
"No region available. og:locale and og:locale:alternate are left out"
],
[
"Invalid",
"fr-FR",
"Use letters and one underscore, for example fr_FR."
]
]
},
{
"title": "Move article",
"cols": [
"Case",
"Behavior"
],
"w": "1fr 3fr",
"rows": [
[
"Entry point",
"The site select in the editor details"
],
[
"Note",
"\"Moves all language versions of this article.\" Lists readable versions; a version the user can read but not edit is tagged Read only; unreadable versions are not listed or counted"
],
[
"Confirm",
"\"Move to Atelier?\" with the same list"
],
[
"Failures",
"Language missing in the destination (disabled languages count), address conflict (names only readable conflicting versions), no permission (neutral)"
]
]
},
{
"title": "Release 2 groups",
"cols": [
"Group",
"Release",
"Why"
],
"w": "1fr 1fr 3fr",
"rows": [
[
"L6 Source reference",
"Release 2",
"Drawn in full so the checker has frames"
],
[
"L9 Freshness and review",
"Release 2",
"Drawn in full so the checker has frames"
]
]
}
]
}
window.LOC.changes = {"added":["L1|sl|broader","L1|sl|defaultFail","L1|sl|stale","L1|sl|reenableDone","L1|sl|reenableFail","L1|sl|removeFail","L1|sl|social","L1|sl|socialEdit","L1|sl|socialInvalid","L1b|pu|set","L1b|pu|editing","L1b|pu|unknown","L1b|pu|scheme","L1b|pu|encoded","L1b|pu|noSlug","L1b|pu|saved","L1b|pu|unset","L1b|pu|viewOnly","L2|su|noLocale","L2|su|inProgress","L4|at|unreadable","L4|at|langOff","L5|ed|cleanOnline","L5|ed|pubFail","L5|ed|ovf","L5|ed|titleEdit","L5|ed|titleUnsaved","L5|ed|titleShort","L5|ed|titleLong","L5b|perm|ro","L5b|perm|roMenu","L5b|perm|p2","L5b|perm|p3","L5b|perm|p4","L5b|perm|p5","L5b|perm|p6","L5b|perm|noAdd","L5b|perm|withAdd","L6|src|arRef","L7|li|page2","L7|li|review","L7|li|author","L7|li|langFilter","L7|li|rowMenu","L8|pd|publishDisabled","L8|pd|unpublishDirty","L8|pd|deleteStoryOk","L9|li|frReview","L10|seo|editSlug","L11|pm|restored","L11|pm|conflictRestored","L11|pm|reloadConfirm","L11|pm|conflict2","L11|pm|download","L11b|pm|moveSelect","L11b|pm|moveSelectHidden","L11b|pm|moveConfirm","L11b|pm|moveBusy","L11b|pm|moveDone","L11b|pm|move","L11b|pm|moveSlug","L11b|pm|movePerm","L12|sf|home","L12|sf|homeFr","L12|sf|activity","L12|sf|media","L12|sf|menu","L12|sf|apiCopy","L12|sf|seoPreview","L12|sf|siteFallback"],"removed":["L5b|ed|ro (Read-only version)","L5b|ed|roMenu (Read-only: menu)","L5b|ed|p2 (Edit, no publish)","L5b|ed|p3 (Publish, no edit)","L5b|ed|p4 (Sibling readable, not editable)","L5b|ed|p5 (Sibling not readable)","L5b|ed|p6 (Can create, not modify all)","L11|pm|move (Move: language missing)","L11|pm|moveSlug (Move: address conflict)","L11|pm|movePerm (Move: no permission)"]}