Document the l10n conventions (imports, %n, TRANSLATORS placement, composed strings)
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 6
- Forks
- 1
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 10
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
- #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. - #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). - #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
- #45 — surfaces as a
Recent {category}German bug, but the fix belongs in creator-list scoping rather than in l10n; still open - #90 — the missing
translatePluralimport - #49 —
vue-tsctypecheck gate; would have caught #90's bug statically, sinceeslintstructurally cannot (novue/no-undef-properties, not in@nextcloud's config) - #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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Review README.md and the @nextcloud/l10n README first, then choose AGENTS.md, CONTRIBUTING.md, or docs/ for the new section. Document the import aliases, %n plural-count convention, TRANSLATORS placement, and composed-string risks, including the stated examples and translation cost; done means all four conventions are easy to find in repository documentation.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- documentation, internationalization
- Issue type
- Documentation
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100