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]

Reply via email to