[Bug] Interpolated Dutch strings in lib/services reach the UI and can never be translated #576

Closed
opened 2026-07-22 16:15:21 +00:00 by brenno · 0 comments
Owner

Found in the pre-publication review, then measured and narrowed in the main loop. This is the genuine remainder of the localisation story, and it is structural rather than a list of oversights.

The mechanism: the translation layer is keyed on literal Dutch source text. An interpolated string can therefore never be looked up — there is no literal to key on. git grep over lib/services/ and lib/state/ finds 36 interpolated Dutch strings. Some are log/debug output that never reaches a user (DiskTraces: map niet verwijderd), but several are shown verbatim.

Confirmed reaching the UI: lib/services/classification_enforcement_policy.dart:56-80 builds three refusal texts as bare Dutch literals — "Export geblokkeerd door classificatiebeleid: dit deck is $actual, lager dan het vereiste minimum ${floor.label}." Traced to the screen at lib/widgets/dialogs/export_dialog.dart:545, lib/widgets/shell/shell_actions_export.dart:29, and lib/widgets/panels/slide_list_panel_clipboard.dart:36. Others in the same class: "Deze presentatie is te groot voor de webversie ($size bytes)", "Certificaat niet vertrouwd: $e", "Antwoord van $forgeName is geen geldige JSON", "Directive $key wordt niet ondersteund en genegeerd."

Separately, two bare literals that are not interpolated but still bypass the gate: lib/state/tabs_provider_tab_info.dart:77 and :88 return 'Nieuw' as the tab label — the first word in the top-left corner of the window, in every one of the 32 languages. Also lib/widgets/editors/asset_overview_editor.dart:309.

Why the gate does not catch this: tool/check_hardcoded_text.dart scans for visible strings in widget code. These literals live in a service or provider and travel to the UI as data. The tool documents this blind spot itself.

Why this must be fixed before publication: a Polish user who hits the classification policy gets a Dutch sentence; every user in every language sees a tab called "Nieuw". Both are trivially screenshotted next to the 32-language claim.

Proposal: move the refusal texts to a typed reason enum that the UI localises at the point of display (the policy should return why, not prose); route the tab label through d(). Then extend the gate to flag interpolated Dutch literals in lib/services/ and lib/state/ that flow into a UI sink, with the current 36 as an explicit baseline so the number can only fall.

Found in the pre-publication review, then measured and narrowed in the main loop. This is the genuine remainder of the localisation story, and it is structural rather than a list of oversights. **The mechanism:** the translation layer is keyed on literal Dutch source text. An *interpolated* string can therefore never be looked up — there is no literal to key on. `git grep` over `lib/services/` and `lib/state/` finds **36 interpolated Dutch strings**. Some are log/debug output that never reaches a user (`DiskTraces: map niet verwijderd`), but several are shown verbatim. **Confirmed reaching the UI:** `lib/services/classification_enforcement_policy.dart:56-80` builds three refusal texts as bare Dutch literals — "Export geblokkeerd door classificatiebeleid: dit deck is $actual, lager dan het vereiste minimum ${floor.label}." Traced to the screen at `lib/widgets/dialogs/export_dialog.dart:545`, `lib/widgets/shell/shell_actions_export.dart:29`, and `lib/widgets/panels/slide_list_panel_clipboard.dart:36`. Others in the same class: "Deze presentatie is te groot voor de webversie ($size bytes)", "Certificaat niet vertrouwd: $e", "Antwoord van $forgeName is geen geldige JSON", "Directive `$key` wordt niet ondersteund en genegeerd." **Separately, two bare literals that are not interpolated but still bypass the gate:** `lib/state/tabs_provider_tab_info.dart:77` and `:88` return `'Nieuw'` as the tab label — the first word in the top-left corner of the window, in every one of the 32 languages. Also `lib/widgets/editors/asset_overview_editor.dart:309`. **Why the gate does not catch this:** `tool/check_hardcoded_text.dart` scans for visible strings in widget code. These literals live in a *service* or *provider* and travel to the UI as data. The tool documents this blind spot itself. **Why this must be fixed before publication:** a Polish user who hits the classification policy gets a Dutch sentence; every user in every language sees a tab called "Nieuw". Both are trivially screenshotted next to the 32-language claim. **Proposal:** move the refusal texts to a typed reason enum that the UI localises at the point of display (the policy should return *why*, not *prose*); route the tab label through `d()`. Then extend the gate to flag interpolated Dutch literals in `lib/services/` and `lib/state/` that flow into a UI sink, with the current 36 as an explicit baseline so the number can only fall.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#576
No description provided.