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
In 1,198 translations, the placeholders (%s, %(name)s) do not match the English source. At runtime they:
Effect
Count
Raise an exception (backend)
37
Show a raw template, such as the literal text %s SENHA
253
Drop a value: a name, a count, or an error detail
839
Show a wrong or blank value
69
Ask: I plan to fix this in three PRs, plus an optional fourth (see Proposed fix). PR 2 clears 1,190 fuzzy translations from 18 catalogs, so I would like a maintainer's view on that approach before I open it.
The most serious case: SQL Lab fails in Slovak. The Slovak catalog still has the old placeholder names that caused #38468 in French:
msgid "Running block %(block_num)s out of %(block_count)s"
msgstr "Spouští sa príkaz %(statement_num)s z %(statement_count)s"
superset/sql_lab.py builds this progress message for every query. I ran SELECT name, qty FROM fruit on a local SQLite database, on master. In Slovak the query fails with the error 'statement_num', labelled as a SQLite error. The same query in English returns its rows.
More cases I reproduced on a local master:
A 500 instead of a 400.GET /api/v1/chart/1/data/?filters_dashboard_id=424242 (a dashboard that does not exist) returns 400 {"message": "Dashboard 424242 not found"} in English. In pt_BR it returns 500 with the message 'id', because the translation is Gráfico %(id)s não encontrado.
A raw template in the UI. On the user info page in pt_BR, the Reset my password button reads %s SENHA.
Other examples
Lang
Source
Translation
Result
nl
Dataset %(table)s already exists
Dataset %(name)s bestaat al
KeyError: 'name'
pl
Not set
Jeszcze brak %s
UI shows Jeszcze brak %s
pt_BR
Created by: %s
Criado por
author name is dropped
ko
Could not load database driver for: %(engine)s
데이터베이스 드라이버를 로드할 수 없습니다
engine name is dropped
The problem affects 20 of the 29 translated languages. The most affected are ko (114), pl (100), nl (91), fa (89), ar (87) and pt_BR (87). It only reaches deployments that enable these languages (by default LANGUAGES is empty and BABEL_DEFAULT_LOCALE is en).
This bug class is not new: #38468 and #39566 (French) and #40165 (Czech) broke SQL Lab, and #33344 reported compile errors in zh_TW. Each was fixed by hand for the strings reported, with no check to stop the next one.
Why nothing catches it
Superset serves fuzzy translations on purpose (docs/developer_docs/contributing/howtos.md). The release Dockerfiles in RELEASING/ compile with flask fab babel-compile (pybabel compile -f), the main Dockerfile uses --use-fuzzy when BUILD_TRANSLATIONS=true, and po2json.sh uses --fuzzy. So a broken fuzzy entry is as live as a confirmed one. 1,190 of the 1,198 are fuzzy.
Compile errors are ignored. pybabel compile --use-fuzzy reports 1,090 errors and exits 1, but it still writes every .mo file. The Dockerfile ends the command with || true, and flask fab babel-compile does not check the exit status.
CI checks translation counts and msgids, not placeholders. The standard gettext check, msgfmt -c, does not help here: it skips fuzzy entries and entries without a python-format flag (like Not set above).
The same pattern, errors in the translation tools that nobody sees, is behind #44395: babel_update.sh has skipped its normalization step since July 2024 (#29782), because a failing command did not stop the script.
Where they came from: 829 have the same translation as a different source string, so they most likely come from Babel's old fuzzy matching (the same problem as #42728). 97 came from scripts/translations/backfill_po.py. The script tells the model to keep placeholders, but nothing checks the output yet; the CI check in PR 1 can do that. For 272 I could not find the source.
Proposed fix
A CI check that fails when a translation's placeholders cannot format correctly. It is small and has no new dependencies. On master it flags 1,227 entries, and 1,198 of those fail at runtime. Of the rest, 11 are one message whose singular and plural forms use different placeholder names (ParameterErrorMessage.tsx), which I would make consistent, and 17 fail only when a count is 0, which their call sites never pass. So PR 1 should land after PRs 2 and 3, with a short list of known exceptions, to keep CI green.
Clear the 1,190 broken fuzzy entries. They become untranslated, so users see the English text with its values. This keeps the "serve fuzzy on purpose" policy and only removes entries that cannot work. I tested it on a copy of master:
All 37 exceptions go away, and the only remaining cases are the confirmed ones in PR 3.
Compile errors drop from 1,090 to 154.
check_translation_regression.py reports no regressions. Confirmed translations do not change; only the fuzzy count drops.
Fix the 8 broken confirmed translations manually: 4 in mi, 2 in es, 1 in fr, 1 in nl. Native speakers are welcome to review.
Optional: fall back to English when a translation cannot be formatted. Today the backend raises and the frontend shows the raw template. With this change both show the English text with its values and log a warning. Errors in the English source still raise, so code bugs stay visible. I built and tested a prototype (a flask_babel.Domain subclass installed at startup, and a small change in Translator.ts):
Unit tests pass, and they fail without the change.
On a local master, Slovak SQL Lab runs its queries, the pt_BR API returns the proper 400, and the pt_BRDuplicate role dialog shows Duplicate role Admin instead of Duplicate role %(name)s.
It fixes 63 of the 1,198 cases: all 37 exceptions and 26 raw templates. The other 1,135 do not raise an error, so only PRs 1–3 fix them. This is a safety net for the future, including custom catalogs, not a replacement.
Traceback (most recent call last):
File "<string>", line 4, in <module>
KeyError: 'statement_num'
Reproduce in the app: add "sk": {"flag": "sk", "name": "Slovak"} to LANGUAGES, compile the catalogs as above, switch the language to Slovak, and run any query in SQL Lab.
How the numbers were measured, and the limits
A static check compares each translated form with the source form it stands for (the singular with msgid, plural forms with msgid_plural). It follows gettext's plural rule: a form used for only one number may write that number out (as 1 or as a word), so Arabic's zero, one and two forms are not flagged.
Each flagged entry was then rendered the way users see it. Frontend: the catalog converted with po2json --fuzzy, then formatted with jed 1.1.1, using the same calls and the same fallback as Translator in superset-core. Backend: the .mo compiled with --use-fuzzy, then formatted as flask_babel 4.0 does (s % variables).
The test arguments follow the source string, not each call site. The one message with linked arguments (%(suggestion)s instead of …) was re-run with the real arguments; it renders correctly and is not counted.
17 more entries fail only when the count is 0, which their call sites never pass. They are not counted and not cleared.
"Values present" does not mean "correct". Some entries that pass still say the wrong thing (on the same pt_BR page, First Name reads Nome do gráfico, "chart name"). A script cannot judge meaning, so those are not counted.
The 14 msgfmt -c errors in the committed fr, it, pt and sl files are mostly harmless: 8 are plural forms that write the number 1 literally (correct for it and pt), and 4 are the linked-arguments message above. Of the other 2 in fr, one is fixed in PR 3 and one fails only when the count is 0.
Checklist
I have searched Superset docs and Slack and didn't find a solution to my problem.
I have searched the GitHub issue tracker and didn't find a similar bug report.
I have checked Superset's logs for errors and if I found a relevant Python stacktrace, I included it here as text in the "additional context" section.
Bug description
In 1,198 translations, the placeholders (
%s,%(name)s) do not match the English source. At runtime they:%s SENHAAsk: I plan to fix this in three PRs, plus an optional fourth (see Proposed fix). PR 2 clears 1,190 fuzzy translations from 18 catalogs, so I would like a maintainer's view on that approach before I open it.
The most serious case: SQL Lab fails in Slovak. The Slovak catalog still has the old placeholder names that caused #38468 in French:
superset/sql_lab.pybuilds this progress message for every query. I ranSELECT name, qty FROM fruiton a local SQLite database, onmaster. In Slovak the query fails with the error'statement_num', labelled as a SQLite error. The same query in English returns its rows.More cases I reproduced on a local
master:GET /api/v1/chart/1/data/?filters_dashboard_id=424242(a dashboard that does not exist) returns400 {"message": "Dashboard 424242 not found"}in English. Inpt_BRit returns500with the message'id', because the translation isGráfico %(id)s não encontrado.pt_BR, the Reset my password button reads%s SENHA.Other examples
Dataset %(table)s already existsDataset %(name)s bestaat alKeyError: 'name'Not setJeszcze brak %sJeszcze brak %sCreated by: %sCriado porCould not load database driver for: %(engine)s데이터베이스 드라이버를 로드할 수 없습니다The problem affects 20 of the 29 translated languages. The most affected are
ko(114),pl(100),nl(91),fa(89),ar(87) andpt_BR(87). It only reaches deployments that enable these languages (by defaultLANGUAGESis empty andBABEL_DEFAULT_LOCALEisen).This bug class is not new: #38468 and #39566 (French) and #40165 (Czech) broke SQL Lab, and #33344 reported compile errors in
zh_TW. Each was fixed by hand for the strings reported, with no check to stop the next one.Why nothing catches it
docs/developer_docs/contributing/howtos.md). The release Dockerfiles inRELEASING/compile withflask fab babel-compile(pybabel compile -f), the mainDockerfileuses--use-fuzzywhenBUILD_TRANSLATIONS=true, andpo2json.shuses--fuzzy. So a broken fuzzy entry is as live as a confirmed one. 1,190 of the 1,198 are fuzzy.pybabel compile --use-fuzzyreports 1,090 errors and exits 1, but it still writes every.mofile. TheDockerfileends the command with|| true, andflask fab babel-compiledoes not check the exit status.msgfmt -c, does not help here: it skips fuzzy entries and entries without apython-formatflag (likeNot setabove).The same pattern, errors in the translation tools that nobody sees, is behind #44395:
babel_update.shhas skipped its normalization step since July 2024 (#29782), because a failing command did not stop the script.Where they came from: 829 have the same translation as a different source string, so they most likely come from Babel's old fuzzy matching (the same problem as #42728). 97 came from
scripts/translations/backfill_po.py. The script tells the model to keep placeholders, but nothing checks the output yet; the CI check in PR 1 can do that. For 272 I could not find the source.Proposed fix
A CI check that fails when a translation's placeholders cannot format correctly. It is small and has no new dependencies. On
masterit flags 1,227 entries, and 1,198 of those fail at runtime. Of the rest, 11 are one message whose singular and plural forms use different placeholder names (ParameterErrorMessage.tsx), which I would make consistent, and 17 fail only when a count is 0, which their call sites never pass. So PR 1 should land after PRs 2 and 3, with a short list of known exceptions, to keep CI green.Clear the 1,190 broken fuzzy entries. They become untranslated, so users see the English text with its values. This keeps the "serve fuzzy on purpose" policy and only removes entries that cannot work. I tested it on a copy of
master:check_translation_regression.pyreports no regressions. Confirmed translations do not change; only the fuzzy count drops.I can split it by language if smaller PRs are easier to review. chore(i18n): update pt/pt_BR translations and rebuild translation index #43022 also edits
pt_BR, so one of the two PRs will need a rebase.Fix the 8 broken confirmed translations manually: 4 in
mi, 2 ines, 1 infr, 1 innl. Native speakers are welcome to review.Optional: fall back to English when a translation cannot be formatted. Today the backend raises and the frontend shows the raw template. With this change both show the English text with its values and log a warning. Errors in the English source still raise, so code bugs stay visible. I built and tested a prototype (a
flask_babel.Domainsubclass installed at startup, and a small change inTranslator.ts):master, Slovak SQL Lab runs its queries, thept_BRAPI returns the proper 400, and thept_BRDuplicate role dialog showsDuplicate role Admininstead ofDuplicate role %(name)s.cc @rusackas @sadpandajoe (recent translation CI work in #44467, #44509)
Screenshots/recordings
Slovak vs. English, same query, same database, local
master:Superset version
master / latest-dev
Python version
3.11
Node version
18 or greater
Browser
Chrome
Additional context
Measured on
masterat269b9f99fe.Reproduce without a running app
Reproduce in the app: add
"sk": {"flag": "sk", "name": "Slovak"}toLANGUAGES, compile the catalogs as above, switch the language to Slovak, and run any query in SQL Lab.How the numbers were measured, and the limits
msgid, plural forms withmsgid_plural). It follows gettext's plural rule: a form used for only one number may write that number out (as1or as a word), so Arabic's zero, one and two forms are not flagged.po2json --fuzzy, then formatted withjed1.1.1, using the same calls and the same fallback asTranslatorinsuperset-core. Backend: the.mocompiled with--use-fuzzy, then formatted asflask_babel4.0 does (s % variables).%(suggestion)s instead of …) was re-run with the real arguments; it renders correctly and is not counted.pt_BRpage, First Name readsNome do gráfico, "chart name"). A script cannot judge meaning, so those are not counted.msgfmt -cerrors in the committedfr,it,ptandslfiles are mostly harmless: 8 are plural forms that write the number 1 literally (correct foritandpt), and 4 are the linked-arguments message above. Of the other 2 infr, one is fixed in PR 3 and one fails only when the count is 0.Checklist