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

Motivation

If a locale catalog was missing a msgid, gettext surfaced the raw key
(e.g. tabcmd.user.warning.local_auth_remapped) instead of the English
translation. The fix is a one-liner in localize.py -- chain en
behind the preferred locale in gettext.translation(languages=[..., "en"]). Bundled with a CI workflow that catches .mo drift on PRs so
this class of gap stops accumulating silently.

Behavior change

For users: untranslated msgids now resolve to English rather than
surfacing the raw key. No behavior change for anyone on a fully-covered
locale.

For contributors: .github/workflows/check-mo-freshness.yml on
every PR touching localization sources regenerates .mo/.po from
committed sources and fails if the committed binaries drift. Failed
runs upload the regenerated .mo files as an artifact so contributors
can commit them back without re-running doit locally.

Also bundled:

  • Fixed NameError in dodo.py's uniquify_file when no lines were
    discarded (uniques -> unique_lines).
  • Ignored combined.tmp and codestrings.tmp doit intermediates.
  • Docs: fixed wrong doit task name (combine_property_files ->
    properties) in bin/i18n/README.md; removed a duplicate
    Localization section from contributing.md.

Test plan

  • pytest tests/commands/test_localize.py -v — 8 passed, including
    two new tests: fr load with en-only msgid returns en; fr load with
    translated msgid still returns fr
  • First CI run on this PR will exercise check-mo-freshness and
    will (deliberately) fail: committed .mo files have drifted. Will
    download regenerated files from workflow artifact and commit back.

Related followup: #441 (stop committing generated .mo/.po at all).

🤖 Generated with Claude Code

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