Skip to content

fix: fall back to English per-key when locale is missing a msgid - #440

Open
jacalata wants to merge 5 commits into
developmentfrom
jac/localization-fallback
Open

fix: fall back to English per-key when locale is missing a msgid#440
jacalata wants to merge 5 commits into
developmentfrom
jac/localization-fallback

Conversation

@jacalata

@jacalata jacalata commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Fix per-key fallback in localize.py: chain en behind the preferred locale in gettext.translation(languages=[preferred, "en"]) so a msgid missing from the loaded catalog resolves to the English msgstr, instead of surfacing the raw key.
  • Add check-mo-freshness CI workflow: regenerates .po/.mo from committed sources on every PR that touches localization files, fails if the committed .mo files disagree. Uploads the regenerated .mo files as a workflow artifact on failure so they can be downloaded and committed straight back.
  • Docs: fix wrong doit task name (combine_property_files -> properties) in bin/i18n/README.md; update the "English fallback" claim to describe what the code actually does now; remove duplicate Localization section from contributing.md.
  • dodo.py: fix NameError in uniquify_file when no lines were discarded (uniques -> unique_lines).
  • .gitignore: ignore the doit intermediate combined.tmp and codestrings.tmp files.

Test plan

  • pytest tests/commands/test_localize.py -v - 8 passed, including two new tests:
    • test_fallback_to_english_when_key_missing_in_locale - fr load, en-only msgid returns the en msgstr
    • test_key_present_in_locale_uses_locale_translation - fr load, translated msgid returns the fr msgstr (fallback doesn't clobber)
  • The first CI run on this PR will exercise check-mo-freshness and will (deliberately) fail: the committed .mo files have drifted from their sources. I will download the regenerated files from the workflow artifact and commit them back to this branch, at which point the workflow should go green.

Known drift being fixed

A clean doit localize run against the current sources produces ~20 key differences from the committed .mo files across all 11 locales. Roughly:

  • Orphan translations for keys no longer in the code (tabcmd.options.addcalculations, tabcmd.status.job_completed, etc.)
  • Missing translations for keys currently in the code (tabcmd.debug.error_message, tabcmd.error.file_save_failed, etc.)

After this PR the committed .mo files will be back in sync, and the freshness workflow will keep them there.

Related followup: #441 (stop committing generated .mo/.po files entirely; generate at build time).

Previously, gettext.translation was called with a single-locale languages
list. If the loaded catalog was missing a msgid the raw key was surfaced
to the user instead of the English text. The chained locale_options list
in set_client_locale only handled catalog *load* failures, not per-key
lookups.

Chain "en" behind the preferred locale so per-key fallback works.

Also:
- Add CI workflow to detect drift between committed .mo files and the
  .properties sources they are compiled from; regenerates and diffs on
  every PR that touches localization files. Uploads the regenerated .mo
  files as an artifact on failure so they can be downloaded and committed.
- Fix wrong doit task name (combine_property_files -> properties) in
  bin/i18n/README.md, and update the English-fallback claim to match the
  new behavior.
- Remove duplicate Localization section from contributing.md.
- Fix NameError in dodo.py uniquify_file when no lines were discarded.
- Gitignore the doit intermediate combined.tmp and codestrings.tmp files.
@github-actions

github-actions Bot commented Jul 27, 2026

Copy link
Copy Markdown

Coverage

Coverage Report
FileStmtsMissCoverMissing
tabcmd
   __main__.py121212 0%
   _version.py111111 0%
   tabcmd.py151515 0%
   version.py955 44%
tabcmd/commands
   commands.py101010 0%
   constants.py771818 77%
   server.py1351818 87%
tabcmd/commands/auth
   session.py3945050 87%
tabcmd/commands/datasources_and_workbooks
   datasources_and_workbooks_command.py1571818 89%
   datasources_workbooks_views_url_parser.py14255 96%
   delete_command.py601616 73%
   export_command.py1202525 79%
   get_url_command.py1274747 63%
   publish_command.py1232828 77%
   runschedule_command.py2177 67%
tabcmd/commands/extracts
   create_extracts_command.py4288 81%
   decrypt_extracts_command.py2722 93%
   delete_extracts_command.py3766 84%
   encrypt_extracts_command.py2722 93%
   extracts.py2022 90%
   reencrypt_extracts_command.py2722 93%
   refresh_extracts_command.py481010 79%
tabcmd/commands/group
   create_group_command.py2955 83%
   delete_group_command.py2722 93%
tabcmd/commands/project
   create_project_command.py4688 83%
   delete_project_command.py3544 89%
   publish_samples_command.py3044 87%
tabcmd/commands/site
   create_site_command.py3455 85%
   delete_site_command.py2722 93%
   edit_site_command.py3822 95%
   list_command.py771212 84%
   list_sites_command.py2922 93%
tabcmd/commands/user
   add_users_command.py2955 83%
   create_site_users.py581111 81%
   create_users_command.py5999 85%
   delete_site_users_command.py4355 88%
   user_data.py2223131 86%
tabcmd/execution
   _version.py222 0%
   global_options.py12588 94%
   localize.py671111 84%
   logger_config.py6066 90%
   tabcmd_controller.py4277 83%
TOTAL287445884% 

jacalata and others added 4 commits July 27, 2026 17:32
The check-mo-freshness workflow only installs .[localize], which doesn't
pull setuptools_scm; the top-level import made 'doit properties/po/mo'
crash with ModuleNotFoundError. setuptools_scm is only used by
task_version, so import it lazily inside that task.
Two follow-on issues discovered when the workflow ran in a clean CI env:

1. prop2po.py imports click, which was not in the [localize] extra. Add
   click as a dep of [localize] so 'pip install .[localize]' is sufficient
   to run 'doit localize' from scratch.

2. task_properties invoked check_strings.py in build mode, which hard-fails
   on missing non-English keys. With the English fallback added in this
   branch, non-en gaps are handled at runtime, so blocking the build on
   them is counterproductive. Remove enforce_strings_present from the
   properties task; check-strings.yml already runs check_strings.py in dev
   mode on every PR as a separate check.
The committed .mo files were out of sync with their .properties sources:
~20 keys per locale were either orphan translations for removed code or
missing translations for currently-used code. Regenerated via the
check-mo-freshness workflow on Ubuntu and downloaded from the workflow
artifact so the bytes match what CI produces.
.po files are gitignored across the repo, so the workflow's
`git diff --exit-code -- '**/*.po'` was always a no-op and the
`.po` path filter never triggered a run. Trim both.

No behavior change; the .mo drift check is what has always been
doing the actual work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant