Two issues, two maintainer fixes: how NocoDB repaired its broken translations
NocoDB's locale files had translated placeholder names and 182 plurals whose separator had been turned into <unk>. Two issues, two maintainer PRs, and what the fixes say about machine-translation pipelines.
Key takeaway
NocoDB's locale files had translated placeholder names and 182 plurals whose separator had been turned into <unk>. Two issues, two maintainer PRs, and what the fixes say about machine-translation pipelines.
NocoDB is an open-source Airtable alternative with about 65,000 GitHub
stars. Its interface ships in 39 languages, synced from Crowdin into packages/nc-gui/lang/*.json and
rendered with vue-i18n.
Between September 12 and October 1 I filed two issues against those files. Both were fixed by NocoDB's own maintainers: the second one 89 minutes after I opened it.
The first issue: placeholder names that got translated
vue-i18n only substitutes the exact token the source string uses. If the English says {search}, the French
has to say {search} too. In #14573 the token itself had
been translated:
| locale | value |
|---|---|
| en | Search {search} field |
| fr | Recherche {recherche} colonne |
| it | Cerca {Cerca} colonna |
| pt | Pesquisar {pesquisar} Coluna |
French, Italian and Portuguese users saw the literal {recherche} instead of the field name. Another string
dropped all three of its variables ({activePlan}, {limit}, {plan}), so French users were told they were
on the free plan whatever plan they actually had.
Because the files come from Crowdin, I filed an issue rather than a PR. A direct edit would be overwritten on the next sync. A NocoDB maintainer opened #14581 two days later. It touched all 39 locale files (300 lines) and merged on September 15.
The second issue: 182 plurals with the separator replaced
vue-i18n plurals are singular | plural. In #14717 the | had
been replaced in 182 strings across 13 locales, by <unk>, ●, ・, +, ├, £, -, ww, a full-width
|, or a plain space. With the separator gone, vue-i18n sees one form and prints both sentences together:
- Swedish, 3 records: "3 filter
3 filter" - Portuguese: "Você excluiu registros 3 + Você excluiu registros 3"
<unk> is what a machine-translation model emits for a token it cannot represent. The pipe was going through
MT as ordinary text.
Three minutes after I filed it, a NocoDB team member confirmed it
in their enterprise tree too, with their own scan. They also found something I had missed: two English
strings, 'Log In | NocoDB' and 'Join | NocoDB', use a literal pipe. vue-i18n splits on it anyway, so the
English page title renders as Log In and drops | NocoDB. My checker had skipped those keys because they
have no variable. That was the right call for the translations and the wrong one for the source.
The fix, #14718, was opened 33 minutes after the issue and merged at 10:47 UTC, 89 minutes after filing. All 13 locales were corrected.
What held, and what didn't
- Held: on 2026-10-11, after four "sync translations with en.json" commits (Oct 2, 5, 7 and 9), a check
of
developfinds 0 placeholder or plural errors in the corrected locales, and no<unk>insv.json. - Still open: the English
title.headLogin/title.headJoinpipes are unchanged. That is a source-string fix, separate from the translation one, and it's NocoDB's call. - No CI check yet. I suggested two ways to catch a recurrence after each Crowdin sync. One was a rule in
their own check: same number of
|as English when the English has a variable. The other was an off-the-shelf command. Neither has been adopted, and that's fine. The fix is theirs either way.
What this says about MT pipelines
Both bugs have one cause. Markup that means something to the runtime ({variable}, |) went through
translation as if it were prose. The model translated the variable name in one case and mangled the
separator in the other. Neither breaks JSON, and neither changes the key set, so a key-diff check passes both.
The defence is cheap: protect those tokens before MT (Crowdin's placeholder settings), and check after it. The check compares each translation's variables and plural separators with the source string's.
Timeline
| date (UTC) | event |
|---|---|
| Sep 12 | #14573 filed: translated and dropped variables (fr/it/pt/es) |
| Sep 14 | maintainer opens #14581 (39 files) |
| Sep 15 | #14581 merged |
| Oct 1 09:18 | #14717 filed: 182 plural separators, 13 locales |
| Oct 1 09:21 | maintainer confirms, finds the English title bug |
| Oct 1 09:51 | maintainer opens #14718 |
| Oct 1 10:47 | #14718 merged |
| Oct 2–9 | four translation-sync commits to the locale files |
| Oct 11 | re-checked on develop: still clean |
I maintain Shipi18n, the open-source checker that found these. Every
finding was verified by hand against develop before filing. The full list of repos and strings is at
shipi18n.com/oss.