i18n: rename the LARP domain concept from "Setting" to "World" across 36 locales - #290
Conversation
…hable (#286) `src/views/EventRoster.vue` was registered in `src/registry.js` and named by nothing in `src/manifest.json`. Registration makes a component RESOLVABLE, not REACHABLE — a `kind: "section"` entry only renders if a manifest page names it. The openspec task "T6: Add the roster / check-in surface to src/manifest.json" was ticked while that edit was never made, so game masters had no way to record check-in / no-show through the product at all. Adds the Check-in tab to EventDetail's sidebar, alongside the existing audit tab. `component: "EventRoster"` is the documented resolution path: CnObjectSidebar.resolveTabComponent() looks the string up in the v2 registry (`effectiveRegistry[tab.component].component`) before falling back to the legacy customComponents map, and hands the tab `sharedTabProps` — objectId / objectType / register / schema / apiBase — which is where EventRoster.vue's `objectId` prop comes from. `AccountCheck` is the attendance schema's own icon and is in vue-material-design-icons. Why nothing caught this: - EventRoster.vue has no mount test. The tests named in the issue (EventsControllerTest, EventsControllerAttendanceTest) are PHP; the backend was wired and green the whole time. - A separate BODY widget with the near-identical id `event-roster` exists on the same page — a read-only `object-list` of participating characters with no check-in controls — so any grep for "roster" in the manifest hit. - The only visible symptom was an orphan `Check-in` key in l10n/en.json with no call site. tests/vitest/manifestRegistry.spec.js pins all three of those. It asserts on the TAB OBJECT — its component binding, id, label and icon — never on the `tabs` array existing, which was already true of the broken tree because the audit tab was there. It also asserts the body object-list and the sidebar tab are distinct surfaces, and that EventRoster's own t() strings are still in the catalogue. Can-fail proof: deleting the checkin tab from the manifest turns 3 of the 5 tests red — "no EventDetail sidebar tab renders the EventRoster component: expected undefined to be defined" — and restoring it returns them to green. Deliberately not asserted: that `tab.label` is in l10n/en.json. CnObjectSidebar renders sidebar tabs as `:name="tab.label"`, raw, with no translate() call — unlike CnAppNav, which routes menu labels through resolveLabel() -> effectiveTranslate(). Asserting it would fail on the 10 pre-existing "History" tab labels and would imply a translation the library does not perform. Reported separately. Closes #286
… 36 locales
In LARP a *setting* is the game world — the campaign that scopes characters,
events and mechanics. In a Nextcloud UI "Setting" means a preference. One
English source string cannot carry both meanings, and the catalogues show
exactly that split:
- the PHRASE keys were already translated with the correct domain meaning
in every locale — "Alle Spielwelten", "Tous les univers", "Todas las
ambientaciones", "Все игровые миры";
- the BARE keys "Setting"/"Settings", used as the page titles and nav
label for the game-world entity, were read as preferences by all 35
non-Dutch locales — Einstellung, Paramètre, Configuración, Ustawienie.
Retranslating the bare keys cannot work: they are also the correct source
strings for actual preferences, and the ambiguity would recur for every new
locale and translator. So the ENGLISH WORD changes instead, and the
"Setting"/"Settings" nav keys are left exactly as they are — they now
unambiguously mean preferences and every existing translation of them is
correct. No 35-locale rewrite.
"World" is not invented. The schema's own summary already read "A LARP world
/ campaign", its icon was already Earth, and the menu label was already the
tell-tale "Settings (Worlds)" — someone had reached for the word and could
only bolt it on in parentheses.
LABELS ONLY. The persisted OpenRegister property is still `setting`, the
schema slug is still `setting`, the page ids are still Settings /
SettingDetail and the routes are still /settings and /settings/:id — those are
deep-link surface and stored data, and renaming them would be a migration.
Renamed: manifest menu label + icon (CogOutline -> Earth, which was the
preferences signal in the navigation), the Settings/SettingDetail page titles,
the four SettingDetail widget titles and empty texts, SkillTree.vue's three
t() strings, and the register's schema title / summary / descriptions.
Translations for the two new bare keys are DERIVED FROM EACH LOCALE'S OWN
existing domain rendering rather than invented — de Spielwelt(en) from "Alle
Spielwelten", fr Univers from "Tous les univers", pt Cenário(s) from "Todos
os cenários", is Söguheimur/-ar from "Allir söguheimar", and so on for all
36. The two renamed phrase keys carry their existing translation over
verbatim: the target text was already right, only the English source moved.
Lower confidence, flagged rather than guessed silently: ga (Saolta cluiche),
mt (Dinjiet tal-logħba) and rm (Munds da gieu) are morphological plurals
derived from the locale's own singular, not verified by a native speaker.
Can-fail proof for test:l10n, both arms:
- dropping "World" from l10n/en.json -> l10n-check FAIL, exit 1,
'translation key(s) used in source but MISSING from l10n/en.json: World'
- dropping "Worlds" from l10n/de.json only -> l10n-parity FAIL, exit 1,
'backend (.json) de: 1 missing key(s) ... missing: "Worlds"'
- both restored -> exit 0.
Not changed, and worth a follow-up: the menu GROUP label is also "World"
(it holds Items / Events / XP Awards), so the nav now reads World > … beside
a Worlds entry. That is a pre-existing nav-structure question, not an
ambiguity, and redesigning the grouping was out of scope here.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 101/101 | |||
| npm | ✅ | ✅ 537/537 | |||
| PHPUnit | ✅ | ||||
| Newman | ❌ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 11:18 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 101/101 | |||
| npm | ✅ | ✅ 537/537 | |||
| PHPUnit | ✅ | ||||
| Newman | ❌ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 12:00 UTC
Download the full PDF report from the workflow artifacts.
Quality Report — ConductionNL/larpingapp @
|
| Check | PHP | Vue | Security | License | Tests |
|---|---|---|---|---|---|
| lint | ✅ | ||||
| phpcs | ✅ | ||||
| phpmd | ✅ | ||||
| psalm | ✅ | ||||
| phpstan | ✅ | ||||
| phpmetrics | ✅ | ||||
| eslint | ✅ | ||||
| stylelint | ✅ | ||||
| build | ✅ | ||||
| check-specs | ✅ | ||||
| test-l10n | ✅ | ||||
| composer | ✅ | ✅ 101/101 | |||
| npm | ✅ | ✅ 537/537 | |||
| PHPUnit | ✅ | ||||
| Newman | ❌ | ||||
| Playwright | ✅ | ||||
| Hydra gates | ❌ |
Quality workflow — 2026-08-08 14:35 UTC
Download the full PDF report from the workflow artifacts.
|
Merging. Strict-subset evaluation, by name
This PR: Hydra Gates: zero failing gates on both sidesWorth spelling out, because sharing a job name is not the same as sharing a defect. Neither this PR nor
The l10n change is the rename, not a retranslationConfirmed mechanically, since this is the part that would be easy to get wrong: Not one of the 35 existing The English source word changed; Can-fail proof for test:l10n, both arms
|
Stacked on #288 (shares
src/manifest.json). Rebase/merge that first.The defect
In LARP a setting is the game world. In a Nextcloud UI "Setting" means a preference. One English source string cannot carry both meanings, and the catalogues show exactly that split:
All settings→ de Alle Spielwelten, fr Tous les univers, es Todas las ambientaciones, ru Все игровые мирыnllocalesSetting→ de Einstellung, fr Paramètre, es Ajuste, pl UstawienieThe bare keys were the page titles and nav label for the game-world entity. Every translator read them as preferences — correctly, given no context.
Why renaming beats retranslating
Retranslating cannot work:
Setting/Settingsare also the correct source strings for actual preferences, and the ambiguity would recur for every future locale and translator. So the English word changes and theSetting/Settingsnav keys are left exactly as they are — they now unambiguously mean preferences and every existing translation of them is correct. No 35-locale rewrite."World" is not invented
EarthSettings (Worlds)— someone had reached for the word and could only bolt it on in parenthesesLabels only — no migration
Unchanged: the persisted OpenRegister property
setting, the schema slugsetting, the page idsSettings/SettingDetail, the routes/settingsand/settings/:id(deep-link surface).SkillTree.vuestill filters onskill.setting.Changed: menu label + icon (
CogOutline→Earth— the cog was the preferences signal in the nav), the two page titles, fourSettingDetailwidget titles/empty texts,SkillTree.vue's threet()strings, and the register schema title / summary / descriptions.Translations derived, not invented
Each locale's
World/Worldscomes from its own existing domain rendering of the phrase keys — deSpielwelt/Spielweltenfrom "Alle Spielwelten", frUnivers(invariable) from "Tous les univers", ptCenário/Cenários, isSöguheimur/Söguheimar, huJátékvilág/Játékvilágok, and so on for all 36. The two renamed phrase keys carry their existing translation over verbatim — the target text was already right, only the English source moved.Flagged rather than guessed silently:
ga(Saolta cluiche),mt(Dinjiet tal-logħba) andrm(Munds da gieu) are morphological plurals derived from the locale's own singular, not native-verified.Can-fail proof for
test:l10n— both armsWorldfroml10n/en.jsonl10n-check: FAIL, exit 1 — translation key(s) used in source but MISSING from l10n/en.json: "World"Worldsfroml10n/de.jsononlyl10n-parity: FAIL, exit 1 — backend (.json) de: 1 missing key(s) … missing: "Worlds"(Exit codes read directly, not through a pipe.)
The 37 catalogue files round-trip byte-identically at 4-space indent + trailing newline — verified before writing — so the diff is exactly the four keys touched per file.
Left for a follow-up
The menu group label is also
World(it holds Items / Events / XP Awards), so the nav now readsWorld > …beside aWorldsentry. Redundant, but not ambiguous, and regrouping the nav was out of scope. Separately:Mechanics,World,XP Awards,XP Award,Game SettingsandFeatures & roadmapare menu labels / page titles missing froml10n/en.jsonentirely, so they render untranslated in all 36 locales. Pre-existing, unrelated to this change, reported not fixed.Verification
check:specs,lint,stylelint,test:unit,test:l10nall exit 0. Full-tree hydra gates unchanged vsdevelopment(4 failing, same names).