Skip to content

i18n: rename the LARP domain concept from "Setting" to "World" across 36 locales - #290

Merged
rubenvdlinde merged 4 commits into
developmentfrom
i18n/rename-setting-to-world
Aug 8, 2026
Merged

i18n: rename the LARP domain concept from "Setting" to "World" across 36 locales#290
rubenvdlinde merged 4 commits into
developmentfrom
i18n/rename-setting-to-world

Conversation

@rubenvdlinde

Copy link
Copy Markdown
Contributor

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:

rendering
Phrase keys — already correct everywhere All settings → de Alle Spielwelten, fr Tous les univers, es Todas las ambientaciones, ru Все игровые миры
Bare keys — wrong in all 35 non-nl locales Setting → de Einstellung, fr Paramètre, es Ajuste, pl Ustawienie

The 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/Settings are 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 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 summary already read "A LARP world / campaign"
  • the schema icon was already Earth
  • the menu label was already Settings (Worlds) — someone had reached for the word and could only bolt it on in parentheses

Labels only — no migration

Unchanged: the persisted OpenRegister property setting, the schema slug setting, the page ids Settings/SettingDetail, the routes /settings and /settings/:id (deep-link surface). SkillTree.vue still filters on skill.setting.

Changed: menu label + icon (CogOutlineEarth — the cog was the preferences signal in the nav), the two page titles, four SettingDetail widget titles/empty texts, SkillTree.vue's three t() strings, and the register schema title / summary / descriptions.

Translations derived, not invented

Each locale's World/Worlds comes from its own existing domain rendering of the phrase keys — de Spielwelt/Spielwelten from "Alle Spielwelten", fr Univers (invariable) from "Tous les univers", pt Cenário/Cenários, is Söguheimur/Söguheimar, hu Já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) and rm (Munds da gieu) are morphological plurals derived from the locale's own singular, not native-verified.

Can-fail proof for test:l10n — both arms

mutation result
drop World from l10n/en.json l10n-check: FAIL, exit 1translation key(s) used in source but MISSING from l10n/en.json: "World"
drop Worlds from l10n/de.json only l10n-parity: FAIL, exit 1backend (.json) de: 1 missing key(s) … missing: "Worlds"
both restored exit 0

(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 reads World > … beside a Worlds entry. Redundant, but not ambiguous, and regrouping the nav was out of scope. Separately: Mechanics, World, XP Awards, XP Award, Game Settings and Features & roadmap are menu labels / page titles missing from l10n/en.json entirely, 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:l10n all exit 0. Full-tree hydra gates unchanged vs development (4 failing, same names).

…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.
@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/larpingapp @ 9894be4

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.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/larpingapp @ 92624c4

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.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Quality Report — ConductionNL/larpingapp @ 31c0df6

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.

@rubenvdlinde

Copy link
Copy Markdown
Contributor Author

Merging.

Strict-subset evaluation, by name

development @ 621205be (run completed — checked, an in-flight run reports zero failures and reads like green):

quality / Integration Tests (Newman)
quality / Hydra Gates
quality / Quality Report        (pure aggregator — not debt)

This PR: Quality Report, Hydra Gates, Integration Tests (Newman). Strict subset ✅

Hydra Gates: zero failing gates on both sides

Worth spelling out, because sharing a job name is not the same as sharing a defect. Neither this PR nor development has a single FAIL line — both are red purely from coverage enforcement over structural skips, which is the upstream empty-scope fix working as designed (an empty diff no longer prints a vacuous PASS):

this PR development
gates FAILing none none
SKIPPED (structural) 19, 25, 60 19, 25, 62, 63
coverage 55 of 58 applicable 54 of 58 applicable

gate-60 icon-vocabulary: SKIPPED (wiring) — vue-material-design-icons is not installed is the honest form of what I reported as ConductionNL/.github#233, where the same condition produced 43 confident FAILs claiming Calendar, Trophy and Earth "do not exist". Fixed upstream in the meantime.

gate-54 relation-dialect passes here — the nested-property false positive I hit while measuring is closed by ConductionNL/.github#255.

The l10n change is the rename, not a retranslation

Confirmed mechanically, since this is the part that would be easy to get wrong:

$ git diff origin/development...HEAD -- l10n/ | grep -E Settings?:
(no output — zero + or - lines for either key)

Not one of the 35 existing Setting / Settings nav translations was touched. They are correct because the domain concept now has its own word:

de  Setting -> Einstellung    World  -> Spielwelt
de  Settings-> Einstellungen  Worlds -> Spielwelten
fr  Setting -> Paramètre      World  -> Univers
fr  Settings-> Paramètres     Worlds -> Univers

The English source word changed; World / Worlds are new keys whose translations are derived from each locale's own existing domain rendering of the phrase keys ("Alle Spielwelten"Spielwelt(en), "Tous les univers"Univers).

Can-fail proof for test:l10n, both arms

mutation result
drop World from l10n/en.json l10n-check: FAIL, exit 1used in source but MISSING
drop Worlds from l10n/de.json only l10n-parity: FAIL, exit 1de: 1 missing key(s)
both restored exit 0

test:l10n, check:specs, lint, stylelint all exit 0.

@rubenvdlinde
rubenvdlinde merged commit 516bd63 into development Aug 8, 2026
29 of 32 checks passed
@rubenvdlinde
rubenvdlinde deleted the i18n/rename-setting-to-world branch August 8, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant