Files
58b104cac3 The rules a new translation is held to (#39299)
* Report a catalog that cannot be read or is not an object, rather than crash

A locale catalog whose top level was null, a number or a string made
check_icu.mjs throw, and an array was checked as if it were an empty
catalog; a catalog it could not read threw too. Either way the run
ended at that file, so the catalogs after it went unchecked. Each is
now an error against its file, and the rest are checked.

en.json is held to the same shape: an array or a number left nothing
to check, and every catalog passed.

mmgotool i18n verify already reports each of these and carries on.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Keep a copy of go-i18n's server plural forms, pinned by a Go test

The translation rules check a server plural map in Node, before the
catalog is written, so they need go-i18n's plural categories for each
locale. i18n/scripts/lib/server_plurals.json holds them, and a Go test
fails if it disagrees with go-i18n for any server catalog, or lists a
locale with none. The server translation job runs the test, so a PR that
adds a server locale has to add it to the table.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Hold a new translation to the structural rules, and test them in CI

i18n/scripts/lib/rules.mjs checks one translation, before it is written
to a catalog. Webapp messages go through icu_rules.mjs, the code
check_icu.mjs runs, so the two cannot disagree; what CI tolerates as a
warning in the catalogs as they stand is an error for new work, except
an arrow in a right-to-left locale, which is soft. Server messages get
the per-string checks mmgotool i18n verify makes, with plural forms
held to go-i18n's table rather than CLDR.

icu_rules.mjs gains plural-unused-category, a plural arm the locale
never selects. check_icu.mjs does not report it: it is dead text, not
something a user sees, so it is left to the pipeline.

A new job runs the pipeline's tests, and the workflow now runs for a
change under i18n/.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Flag a translation that leaves out a name the glossary keeps in English

A doNotTranslate term (Mattermost, GitLab, Markdown) dropped from a
translation is a soft finding: usually a mistake, sometimes a sentence
that is better leaving the brand implicit, so it lands only with a
stated reason.

Channels, Playbooks, Boards and Calls are product names that are also
ordinary English words. The English reads the same whether a string
names the product or starts a sentence about channels, so they are
marked commonWord and not flagged; the translator decides from the
description.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Catch English left untranslated

A translation identical to its English is an error once the English has
three ordinary lowercase words, a sentence nobody translated, and soft
below that, where it may be a name or a word the language shares with
English. In a language not written in Latin script, lowercase English
words kept from the source are soft too.

Only words a translator would translate count: ICU arguments and tags,
template actions, URLs, email addresses, glossary names, quoted values
and anything shaped like an identifier are left out. en-* locales are
not checked.

icu_rules.mjs exports its parser and options, so the rules read a
message's literal text the way check_icu.mjs parses it.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Flag the wrong form of address

i18n/style.json records, for each locale where the choice matters, how
its catalogs address the user, formally or informally, and the pronouns
that would mean the other. A translation that uses one is soft: a
pronoun can be quoted text or mean someone else, as German "Sie" can at
the start of a sentence, so a stated reason lets it land.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Check the glossary and style files in CI

The translation rules read i18n/glossary/ and i18n/style.json, so a
malformed entry would quietly change what a translation is held to.
check_glossary.mjs holds them to their shape: canonical JSON, the known
fields only, one owner per term or alias, a glossary file for exactly
the locales with catalogs, terms in en.json order, a doNotTranslate
term's target the term itself, and no pronoun both used and avoided.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Run every translation check through make i18n-verify-all

CI ran the i18n checks as three jobs of inline commands: the webapp
checker, make i18n-verify plus the plural-table test in the build
container, and the pipeline's tests and glossary check. Nobody could
run that set locally without copying it out of the workflow.

make i18n-verify-all in server/ now runs all of them, and CI runs only
that, in one job on the runner. Go comes from setup-go at mmgotool's
go.mod version, as Tools CI does, so the build container, and with it
the Docker Hub secrets and id-token permission it needed, are gone. The
workflow also runs when server/Makefile changes.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Run make i18n-verify-all in the build environment again

The previous commit swapped the build environment for setup-go, for no
reason that holds up: the image has the Node .nvmrc pins as well as
the Go mmgotool needs, it reuses the Go caches Server CI warms, and it
mounts the checkout, so it sees the node_modules webapp-setup installs.
Server CI builds and checks in it too.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Drop comments that only restate the code

Each said what the line under it already says: a path list, a setup
step, a target its help text describes, a function its name and body
describe, and how to run tests the Makefile now runs. Comments that
give a reason the code cannot show stay.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

* Fail on a glossary or style file that is not a JSON object

readGlossary and readStyle read an array or a number as an empty
glossary or no style, which turned the do-not-translate and form-of-
address rules off without a word, and threw an unrelated TypeError on
null. check_glossary.mjs rejects these shapes in CI, but the rules read
the files on their own. Each is now an error naming the file; a file
that does not exist still means none.

Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jesse Hallam <lieut-data@users.noreply.github.com>
2026-10-22 16:20:27 -03:00
..