chore: redesign article localization before implementation

This commit is contained in:
Florian Sylvain committed 2026-11-01 14:57:07 +01:00
1 parent 99a74dc3a2
commit 8c4a1825ab
31 files changed
+3365 -882

No files matched your search

@@ -0,0 +1,70 @@
# Localization: site languages, public URL and setup
Board: RenewCMS Screens Localization. Frame: LocaleFrame. Data: locale-fixtures.js (single fixture source), tables in locale-spec.js. UI copy says "article", never "story". No revision numbers, IDs or HTTP status names in user-facing text.
## L1 Site languages
Route: /settings/sites/:key/languages
API: proposed: GET/PUT /v1/site/{key}/languages
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.
| ID | State | Trigger | Selector | Sizes | Fixture |
|---|---|---|---|---|---|
| loc.l1.single | Single language | Owner opens Languages on Archive (one language, pt-BR kept) | LocaleFrame{s:{v:"sl",x:"single"}} | 1440, 390 | Archive |
| loc.l1.multi | Multilingual | Journal: four enabled, two disabled (Spanish, Italian) | LocaleFrame{s:{v:"sl",x:"multi"}} | 1440, 1024, 390 | Journal |
| loc.l1.loading | Loading | Request in flight | LocaleFrame{s:{v:"sl",x:"loading"}} | 1440, 390 | Journal |
| loc.l1.error | Load failed | Network or server error | LocaleFrame{s:{v:"sl",x:"error"}} | 1440, 390 | Journal |
| loc.l1.add | Add a language | Add language | LocaleFrame{s:{v:"sl",x:"add"}} | 1440, 390 | Journal |
| loc.l1.saveFail | Add failed | Server error, selection kept | LocaleFrame{s:{v:"sl",x:"saveFail"}} | 1440, 390 | Journal |
| loc.l1.adddup | Duplicate language | Already enabled | LocaleFrame{s:{v:"sl",x:"adddup"}} | 1440, 390 | Journal |
| loc.l1.broader | Only a broader tag supported | Add Portuguese (Portugal): substitution explained, confirmation required | LocaleFrame{s:{v:"sl",x:"broader"}} | 1440, 390 | Journal |
| loc.l1.default | Make default | Row menu | LocaleFrame{s:{v:"sl",x:"default"}} | 1440, 390 | Journal |
| loc.l1.defaultFail | Set default failed | Server error, nothing changed | LocaleFrame{s:{v:"sl",x:"defaultFail"}} | 1440, 390 | Journal |
| loc.l1.disable | Disable with content | Row menu → Disable German | LocaleFrame{s:{v:"sl",x:"disable"}} | 1440, 390 | Journal |
| loc.l1.stale | Languages changed | Another owner changed them: "Languages changed. Reload." | LocaleFrame{s:{v:"sl",x:"stale"}} | 1440, 390 | Journal |
| loc.lang.disableDefault | Disable default blocked | Disable English | LocaleFrame{s:{v:"sl",x:"disableDefault"}} | 1440, 390 | Journal |
| loc.lang.disableLast | Disable last blocked | Only one enabled | LocaleFrame{s:{v:"sl",x:"disableLast"}} | 1440, 390 | Journal |
| loc.l1.reenable | Re-enable | Spanish row → Re-enable | LocaleFrame{s:{v:"sl",x:"reenable"}} | 1440, 390 | Journal |
| loc.l1.reenableDone | Re-enabled | Toast after success | LocaleFrame{s:{v:"sl",x:"reenableDone"}} | 1440, 390 | Journal |
| loc.l1.reenableFail | Re-enable failed | Dialog error, still disabled | LocaleFrame{s:{v:"sl",x:"reenableFail"}} | 1440, 390 | Journal |
| loc.l1.remove | Remove (no content) | Italian (0 versions) | LocaleFrame{s:{v:"sl",x:"remove"}} | 1440, 390 | Journal |
| loc.l1.removeFail | Remove failed | Dialog error, still listed | LocaleFrame{s:{v:"sl",x:"removeFail"}} | 1440, 390 | Journal |
| loc.l1.removeBlocked | Remove blocked | Spanish has versions | LocaleFrame{s:{v:"sl",x:"removeBlocked"}} | 1440, 390 | Journal |
| loc.l1.readonly | Read-only view | Non-owner: list and social locale readable; no controls | LocaleFrame{s:{v:"sl",x:"readonly"}} | 1440, 390 | Journal |
| loc.l1.saved | Saved | After Add language | LocaleFrame{s:{v:"sl",x:"saved"}} | 1440, 390 | Journal |
| loc.l1.social | Social locale: three states | Atelier: Derived en_GB and pt_BR, Set fr_FR, Not set | LocaleFrame{s:{v:"sl",x:"social"}} | 1440, 1024, 390 | Atelier |
| loc.l1.socialEdit | Social locale: edit | Edit on French (Set: fr_FR) | LocaleFrame{s:{v:"sl",x:"socialEdit"}} | 1440, 390 | Atelier |
| loc.l1.socialInvalid | Social locale: invalid | fr-FR typed with a hyphen | LocaleFrame{s:{v:"sl",x:"socialInvalid"}} | 1440, 390 | Atelier |
## L1b Public URL pattern (owner setting)
Route: /settings/sites/:key/public-url
API: proposed: GET/PUT /v1/site/{key} { public_url_pattern }
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.
| ID | State | Trigger | Selector | Sizes | Fixture |
|---|---|---|---|---|---|
| loc.l1b.set | Set | Pattern saved, preview shown | LocaleFrame{s:{v:"pu",x:"set"}} | 1440, 390 | Journal |
| loc.l1b.editing | Editing | Live preview follows the field | LocaleFrame{s:{v:"pu",x:"editing"}} | 1440, 390 | Journal |
| loc.l1b.unknown | Unknown placeholder | {language} typed | LocaleFrame{s:{v:"pu",x:"unknown"}} | 1440, 390 | Journal |
| loc.l1b.scheme | Bad scheme | ftp:// typed | LocaleFrame{s:{v:"pu",x:"scheme"}} | 1440, 390 | Journal |
| loc.l1b.encoded | Not encoded | A space in the path | LocaleFrame{s:{v:"pu",x:"encoded"}} | 1440, 390 | Journal |
| loc.l1b.noSlug | Missing {slug} | Pattern without {slug} | LocaleFrame{s:{v:"pu",x:"noSlug"}} | 1440, 390 | Journal |
| loc.l1b.saved | Saved | Toast | LocaleFrame{s:{v:"pu",x:"saved"}} | 1440, 390 | Journal |
| loc.l1b.unset | Unset | No pattern; opened from "Public URL not configured" | LocaleFrame{s:{v:"pu",x:"unset"}} | 1440, 390 | Journal |
| loc.l1b.viewOnly | Read-only view | Non-owner: pattern readable, no controls | LocaleFrame{s:{v:"pu",x:"viewOnly"}} | 1440, 390 | Journal |
## L2 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)
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.
| ID | State | Trigger | Selector | Sizes | Fixture |
|---|---|---|---|---|---|
| loc.l2.ready | Ready to assign | Journal (en), Notes (no locale, must choose), Archive (pt-BR kept) | LocaleFrame{s:{v:"su",x:"ready"}} | 1440, 390 | Journal, Notes, Archive |
| loc.l2.noLocale | No-locale site | Notes only: the owner must choose | LocaleFrame{s:{v:"su",x:"noLocale"}} | 1440, 390 | Notes |
| loc.l2.empty | Site with no articles | Gallery has no articles; en-GB kept, confirm and Save | LocaleFrame{s:{v:"su",x:"empty"}} | 1440, 390 | Gallery |
| loc.l2.applying | Applying | Request in flight | LocaleFrame{s:{v:"su",x:"applying"}} | 1440, 390 | Journal, Notes, Archive |
| loc.l2.inProgress | Setup already running | Another owner started it; starting again is safe | LocaleFrame{s:{v:"su",x:"inProgress"}} | 1440, 390 | Journal, Notes, Archive |
| loc.l2.done | Done | 16 articles labelled | LocaleFrame{s:{v:"su",x:"done"}} | 1440, 390 | Journal, Notes, Archive |
| loc.l2.fail | Failed | Nothing changed; choices kept; retry is safe | LocaleFrame{s:{v:"su",x:"fail"}} | 1440, 390 | Journal, Notes, Archive |