glaterza opened a new issue, #44551:
URL: https://github.com/apache/superset/issues/44551
### Bug description
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.
<!-- SCREENSHOT: live-evidence/sk-sqllab-keyerror.png -->
<!-- SCREENSHOT: live-evidence/en-sqllab-control-ok.png -->
**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`.
<!-- SCREENSHOT: live-evidence/ptbr-reset-password-raw-template.png -->
**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**
1. 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.
2. 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.
3. 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**
1. **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.
2. **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.
I can split it by language if smaller PRs are easier to review. #43022
also edits `pt_BR`, so one of the two PRs will need a rebase.
3. **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.
4. **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_BR` *Duplicate 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.
cc @rusackas @sadpandajoe (recent translation CI work in #44467, #44509)
### Screenshots/recordings
Slovak vs. English, same query, same database, local `master`:
<!-- SCREENSHOT: live-evidence/sqllab_slovak_keyerror_vs_english.gif -->
### Superset version
master / latest-dev
### Python version
3.11
### Node version
18 or greater
### Browser
Chrome
### Additional context
Measured on `master` at `269b9f99fe`.
**Reproduce without a running app**
```bash
cp -r superset/translations /tmp/mo
pybabel compile --use-fuzzy -d /tmp/mo > /tmp/compile.log 2>&1; echo
"exit=$?" # exit=1
grep -c '^error:' /tmp/compile.log
# 1090
ls /tmp/mo/*/LC_MESSAGES/messages.mo | wc -l
# 30
python -c '
from babel.support import Translations
t = Translations.load("/tmp/mo", ["sk"], "messages")
print(t.ugettext("Running block %(block_num)s out of %(block_count)s") %
{"block_num": 1, "block_count": 1})
'
```
```
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.
<details><summary>How the numbers were measured, and the limits</summary>
- 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.
</details>
### Checklist
- [ ] I have searched Superset docs and Slack and didn't find a solution to
my problem.
- [x] I have searched the GitHub issue tracker and didn't find a similar bug
report.
- [x] 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.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]