🤖 Filed by @dokterbob's agent (Claude Code) on @dokterbob's behalf.
Describe the bug
Six translation keys present in en-US.json are missing from other locale files in backend/chainlit/translations/. Users on those locales fall through to a missing-key render rather than translated text.
Separately — and arguably the more important half — nothing currently catches this, which is why the gaps accumulated. See "Why this wasn't caught" below.
The gaps
Comparing every file in backend/chainlit/translations/ against en-US.json (the ground truth, per lint_translations() in backend/chainlit/config.py):
| Locale |
Missing key |
en-US value |
ar-SA |
chat.favorites.remove |
"Remove favorite" |
da-DK |
chat.favorites.remove |
"Remove favorite" |
de-DE |
components.DatePickerInput |
{ "placeholder": { "single": "Pick a date", "range": "Pick a date range" } } |
it |
components.DatePickerInput |
(as above) |
ko |
components.DatePickerInput |
(as above) |
ja |
chat.fileUpload.browse |
"Browse Files" |
The other 17 locales are at full parity.
Origin of each key:
Why this wasn't caught
chainlit lint-translations does not check the translation files in this repo. lint_translations() (backend/chainlit/config.py:692) loads en-US.json from the package as ground truth, then iterates config_translation_dir — the consuming app's .chainlit/translations/ directory. Run inside a checkout, it lints whatever stale copy happens to sit in .chainlit/ (which is gitignored), not backend/chainlit/translations/.
backend/tests/test_translations.py unit-tests compare_json_structures / lint_translation_json thoroughly, but only against synthetic in-test dictionaries — it never loads the real locale files. And no CI workflow references translations at all.
So a PR that adds a key to en-US.json and forgets the other 22 files goes green. That's exactly what happened three times above.
To Reproduce
import json, os
from chainlit.translations import compare_json_structures
d = "backend/chainlit/translations"
truth = json.load(open(os.path.join(d, "en-US.json"), encoding="utf-8"))
for f in sorted(os.listdir(d)):
if f.endswith(".json"):
for e in compare_json_structures(truth, json.load(open(os.path.join(d, f), encoding="utf-8"))):
print(f, e)
Expected behavior
- All locale files carry every key in
en-US.json.
- A test in
backend/tests/test_translations.py walks the real backend/chainlit/translations/ directory and asserts parity against en-US.json, so this fails in CI instead of accumulating silently.
Item 2 is the durable fix; item 1 without it just resets the counter.
Notes for whoever picks this up
The six translations themselves are a nice good first issue for native speakers of ar-SA, da-DK, de-DE, it, ko, ja.
The parity test is the part worth doing carefully — it should fail loudly and name the locale and key, and it needs to tolerate the fact that en-US.json is both a locale file and the ground truth (comparing it to itself is trivially clean).
Describe the bug
Six translation keys present in
en-US.jsonare missing from other locale files inbackend/chainlit/translations/. Users on those locales fall through to a missing-key render rather than translated text.Separately — and arguably the more important half — nothing currently catches this, which is why the gaps accumulated. See "Why this wasn't caught" below.
The gaps
Comparing every file in
backend/chainlit/translations/againsten-US.json(the ground truth, perlint_translations()inbackend/chainlit/config.py):ar-SAchat.favorites.remove"Remove favorite"da-DKchat.favorites.remove"Remove favorite"de-DEcomponents.DatePickerInput{ "placeholder": { "single": "Pick a date", "range": "Pick a date range" } }itcomponents.DatePickerInputkocomponents.DatePickerInputjachat.fileUpload.browse"Browse Files"The other 17 locales are at full parity.
Origin of each key:
components.DatePickerInput— added in Date Picker Input Widget #2654 (Date Picker Input Widget)chat.favorites.remove— added in feat: allow easy deletion of favorites #2792 (allow easy deletion of favorites)chat.fileUpload.browse— predates bothWhy this wasn't caught
chainlit lint-translationsdoes not check the translation files in this repo.lint_translations()(backend/chainlit/config.py:692) loadsen-US.jsonfrom the package as ground truth, then iteratesconfig_translation_dir— the consuming app's.chainlit/translations/directory. Run inside a checkout, it lints whatever stale copy happens to sit in.chainlit/(which is gitignored), notbackend/chainlit/translations/.backend/tests/test_translations.pyunit-testscompare_json_structures/lint_translation_jsonthoroughly, but only against synthetic in-test dictionaries — it never loads the real locale files. And no CI workflow references translations at all.So a PR that adds a key to
en-US.jsonand forgets the other 22 files goes green. That's exactly what happened three times above.To Reproduce
Expected behavior
en-US.json.backend/tests/test_translations.pywalks the realbackend/chainlit/translations/directory and asserts parity againsten-US.json, so this fails in CI instead of accumulating silently.Item 2 is the durable fix; item 1 without it just resets the counter.
Notes for whoever picks this up
The six translations themselves are a nice good first issue for native speakers of ar-SA, da-DK, de-DE, it, ko, ja.
The parity test is the part worth doing carefully — it should fail loudly and name the locale and key, and it needs to tolerate the fact that
en-US.jsonis both a locale file and the ground truth (comparing it to itself is trivially clean).