Repository navigation
Document the l10n conventions (imports, %n, TRANSLATORS placement, composed strings) #96
Description
There's no l10n guidance anywhere in this repo — no CONTRIBUTING.md, nothing in AGENTS.md, and README.md mentions @nextcloud/l10n only in passing. The conventions are all discoverable, but only if you already know where to look, and we've now hit three separate l10n problems in the same file because of it.
TL;DR — add a short l10n section (in AGENTS.md, CONTRIBUTING.md, or docs/) covering the import aliases, %n for counts, TRANSLATORS comment placement, and the rule against interpolating nouns into phrases.
Evidence: three issues in OfficeOverview.vue alone
- fix: make string plural-compatible for non-English langs + add translator hints #90 — used
n(…)without importingtranslatePlural as n, so the render function threw and 14 tests failed.@nextcloud/l10n's README gives the exact import line and explains why thet/naliases specifically are required, but nothing in this repo points at it. - fix: make string plural-compatible for non-English langs + add translator hints #90 again — identical English singular and plural forms, and
{count}rather than the conventional%n, with the count passed twice (as the count argument and invars). - 'Neueste Textdokument' is not valid German #45 — user-visible broken German (
Neueste Textdokument). To be clear, this one is not a translation-convention mistake: the root cause is thatgetTemplates()fetches the genericapps/files/api/v1/templatesendpoint unfiltered, so the Text app's markdown creator (German labelTextdokument) appears in the Office overview and falls throughcategoryName()tocreator.label. It's listed here only because a composed string is what turned an unrelated data-scoping bug into a grammar error in front of a user — which is the argument for writing the composed-string guidance down.
None of these are unreasonable mistakes. They're "didn't know the convention" mistakes, which is what documentation is for.
Suggested content
Imports. Always import { translate as t, translatePlural as n } from '@nextcloud/l10n'. The aliases are not cosmetic — the extraction scripts look for t( and n(, so renaming them breaks string extraction. There is no global t/n in this app: main.ts installs no plugin and @nextcloud/vue is consumed via per-component deep imports, so nothing is provided on the component context.
Counts. Use %n for the number, not a {count} placeholder, and don't also pass it in vars:
n('office', '%n file found in {category}', '%n files found in {category}',
files.length, { category: activeCategoryName })Give singular and plural distinct English text. Identical forms are legal but leave translators with no signal about which slot is which, which defeats the purpose for languages with more than two plural forms.
Translator hints. <!-- TRANSLATORS: … --> works inside a Vue <template>, and // TRANSLATORS: … in script. It must sit on the line immediately above the call — the extractor only checks the current and preceding line.
Be careful interpolating nouns into phrases. Recent {category} composes correctly in German with the app's own category names (Documents → Dokumente gives Neueste Dokumente), but only as long as every possible value of {category} is a plural noun that fits the surrounding phrase. categoryName() falls back to a server-supplied creator.label when no mimetype matches, and that value isn't under this app's control or translated by this app — so an unrelated bug upstream surfaces as broken grammar. Either constrain what can be interpolated, or use complete per-category strings so each language translates a whole phrase. (#45 is what this looks like in practice, though its actual fix is scoping the creator list, not the string.)
Converting an existing string to plural has a cost. It orphans the existing translations, and translationtool.php drops a string from the shipped bundle unless every plural form for that language is filled — so affected languages fall back to English until Transifex round-trips. Worth calling out in the PR description when you do it.
Related
- 'Neueste Textdokument' is not valid German #45 — surfaces as a
Recent {category}German bug, but the fix belongs in creator-list scoping rather than in l10n; still open - fix: make string plural-compatible for non-English langs + add translator hints #90 — the missing
translatePluralimport - Add a tsc/vue-tsc typecheck gate (8 pre-existing type errors in OfficeOverview.vue) #49 —
vue-tsctypecheck gate; would have caught fix: make string plural-compatible for non-English langs + add translator hints #90's bug statically, sinceeslintstructurally cannot (novue/no-undef-properties, not in@nextcloud's config) - App name is translated #17 — app name translation
Docs alone won't catch mistakes, so #49 is the complementary half of this: guidance for the conventions a linter can't check, a typecheck gate for the ones it can.
Activity