You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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 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.
'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 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 (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.
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.
There's no l10n guidance anywhere in this repo — no
CONTRIBUTING.md, nothing inAGENTS.md, andREADME.mdmentions@nextcloud/l10nonly 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, ordocs/) covering the import aliases,%nfor counts,TRANSLATORScomment placement, and the rule against interpolating nouns into phrases.Evidence: three issues in
OfficeOverview.vuealonen(…)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.{count}rather than the conventional%n, with the count passed twice (as the count argument and invars).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 fort(andn(, so renaming them breaks string extraction. There is no globalt/nin this app:main.tsinstalls no plugin and@nextcloud/vueis consumed via per-component deep imports, so nothing is provided on the component context.Counts. Use
%nfor the number, not a{count}placeholder, and don't also pass it invars: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→DokumentegivesNeueste 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-suppliedcreator.labelwhen 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.phpdrops 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
Recent {category}German bug, but the fix belongs in creator-list scoping rather than in l10n; still opentranslatePluralimportvue-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)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.