nextcloud / nextcloud/office

Document the l10n conventions (imports, %n, TRANSLATORS placement, composed strings)

Open Beginner friendly
#96 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

0. Needs triage documentation
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
  1. #90 — used n(…) without importing translatePlural as n, so the render function threw and 14 tests failed. @nextcloud/l10n's README gives the exact import line and explains why the t/n aliases specifically are required, but nothing in this repo points at it.
  2. #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 in vars).
  3. #45 — user-visible broken German (Neueste Textdokument). To be clear, this one is not a translation-convention mistake: the root cause is that getTemplates() fetches the generic apps/files/api/v1/templates endpoint unfiltered, so the Text app's markdown creator (German label Textdokument) appears in the Office overview and falls through categoryName() to creator.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 (DocumentsDokumente 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 translatePlural import
  • #49 — vue-tsc typecheck gate; would have caught #90's bug statically, since eslint structurally cannot (no vue/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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.