Untranslated strings people read: the Browser and Email notifier names, a career end-date error, the unreadable-secrets message #566

Closed
opened 2026-10-01 10:47:41 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, looking for literal messages that reach a page without passing through gettext.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The code is now at plugins/views.py:249-250 (the connection form) and plugins/views.py:401-402 (the result of Test); the other places are where they were, and the re-extracted catalogues still lack the three msgids.

Four strings that people read on ordinary pages are English literals, so a reader in any other language meets English in the middle of translated sentences. None of them is in the catalogues (msgid "Browser" and msgid "This is before the start date." are absent from locale/fr_FR/LC_MESSAGES/django.po; "Email" is there only because another string uses it).

  • Where:
    • plugins/browser/__init__.py:52: label="Browser" in the shipped(...) manifest of BrowserNotifier, while its description beside it goes through _lazy.
    • plugins/email/__init__.py:87: label="Email" in the manifest of EmailNotifier, the same pattern. The other shipped plugins whose label is a common word wrap it (own_mail _lazy("Your own email"), localstore _("This instance"), gdpr, identifiers and so on); only brand names such as SMTP, Webhook, Europass and schema.org stay literal, which is right for them.
    • resume/forms.py:94: ExperienceForm.clean adds forms.ValidationError("This is before the start date.") although the module imports gettext_lazy as _.
    • plugins/secrets.py:68-72: decrypt raises SecretsUnreadable("The connection's secrets cannot be read: ...") as a plain string. The text is shown as is by plugins/views.py:248 (messages.error(request, str(error)) on the connection form), by plugins/views.py:400 (the result of Test), stored as last_error by plugins/syncing.py:81-82 and shown on Connections, and returned by documents/archiving.py:138-139 as a copy's failure.
  • What happens: a person using Postulo in French opens Settings → Connections → Add: the picker lists "Browser" and "Email", and the connection form's heading reads "Connecter Browser" and "Ce dont Email a besoin" (templates/connections/form.html:7 and :25 put plugin.label inside translated sentences). Saving a career entry whose end date precedes its start shows one English sentence under the end date. After the instance's key changes, every connection page and every test or sync of that connection reports the secrets problem in English.
  • Why it matters: the project promises that every user-facing string is translatable, and these are among the few messages a person meets on the settings and CV pages that break it; translators cannot fix them because they never reach Weblate.
  • Fix: label=_lazy("Browser") and label=_lazy("Email"); forms.ValidationError(_("This is before the start date.")) in ExperienceForm.clean; wrap the SecretsUnreadable message in gettext at the raise in secrets.decrypt (with POSTULO_FIELD_KEY and SECRET_KEY as placeholders or left literal inside the sentence, so translators do not rename them). Then run the extraction so the three new msgids reach the catalogues.

Done when

  • A test in tests/test_notifications.py activates a language whose catalogue translates "Browser" and asserts registry.find_plugin("notifier", "browser").label is not the English word (or, independent of any catalogue, asserts both labels are lazy translation proxies).
  • A test in the résumé form tests submits an ExperienceForm with the end before the start under translation.override("fr") and asserts the error is not the English sentence.
  • A test in tests/test_connections*.py (or the secrets tests) triggers SecretsUnreadable under a non-English language and asserts the message shown is translated.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, looking for literal messages that reach a page without passing through `gettext`. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The code is now at `plugins/views.py:249-250` (the connection form) and `plugins/views.py:401-402` (the result of Test); the other places are where they were, and the re-extracted catalogues still lack the three msgids. Four strings that people read on ordinary pages are English literals, so a reader in any other language meets English in the middle of translated sentences. None of them is in the catalogues (`msgid "Browser"` and `msgid "This is before the start date."` are absent from `locale/fr_FR/LC_MESSAGES/django.po`; `"Email"` is there only because another string uses it). - **Where:** - `plugins/browser/__init__.py:52`: `label="Browser"` in the `shipped(...)` manifest of `BrowserNotifier`, while its `description` beside it goes through `_lazy`. - `plugins/email/__init__.py:87`: `label="Email"` in the manifest of `EmailNotifier`, the same pattern. The other shipped plugins whose label is a common word wrap it (`own_mail` `_lazy("Your own email")`, `localstore` `_("This instance")`, `gdpr`, `identifiers` and so on); only brand names such as `SMTP`, `Webhook`, `Europass` and `schema.org` stay literal, which is right for them. - `resume/forms.py:94`: `ExperienceForm.clean` adds `forms.ValidationError("This is before the start date.")` although the module imports `gettext_lazy as _`. - `plugins/secrets.py:68-72`: `decrypt` raises `SecretsUnreadable("The connection's secrets cannot be read: ...")` as a plain string. The text is shown as is by `plugins/views.py:248` (`messages.error(request, str(error))` on the connection form), by `plugins/views.py:400` (the result of Test), stored as `last_error` by `plugins/syncing.py:81-82` and shown on Connections, and returned by `documents/archiving.py:138-139` as a copy's failure. - **What happens:** a person using Postulo in French opens Settings → Connections → Add: the picker lists "Browser" and "Email", and the connection form's heading reads "Connecter Browser" and "Ce dont Email a besoin" (`templates/connections/form.html:7` and `:25` put `plugin.label` inside translated sentences). Saving a career entry whose end date precedes its start shows one English sentence under the end date. After the instance's key changes, every connection page and every test or sync of that connection reports the secrets problem in English. - **Why it matters:** the project promises that every user-facing string is translatable, and these are among the few messages a person meets on the settings and CV pages that break it; translators cannot fix them because they never reach Weblate. - **Fix:** `label=_lazy("Browser")` and `label=_lazy("Email")`; `forms.ValidationError(_("This is before the start date."))` in `ExperienceForm.clean`; wrap the `SecretsUnreadable` message in `gettext` at the raise in `secrets.decrypt` (with `POSTULO_FIELD_KEY` and `SECRET_KEY` as placeholders or left literal inside the sentence, so translators do not rename them). Then run the extraction so the three new msgids reach the catalogues. ## Done when - A test in `tests/test_notifications.py` activates a language whose catalogue translates "Browser" and asserts `registry.find_plugin("notifier", "browser").label` is not the English word (or, independent of any catalogue, asserts both labels are lazy translation proxies). - A test in the résumé form tests submits an `ExperienceForm` with the end before the start under `translation.override("fr")` and asserts the error is not the English sentence. - A test in `tests/test_connections*.py` (or the secrets tests) triggers `SecretsUnreadable` under a non-English language and asserts the message shown is translated.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:41 +00:00
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
Postulo/postulo#566
No description provided.