Conversation
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.
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>
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Regenerate and commit the stale .mo bundles, and update the outdated dodo.py documentation.
Get a fresh assessment by requesting another Copilot review.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Adds per-key English fallback for incomplete locale catalogs and CI checks for stale localization bundles.
Changes:
- Implements English fallback and tests it.
- Adds
.mofreshness validation workflow. - Fixes localization tooling, documentation, and ignored intermediates.
| File | Summary |
|---|---|
tests/commands/test_localize.py |
Adds fallback behavior tests. |
tabcmd/execution/localize.py |
Implements English fallback. |
pyproject.toml |
Adds localization dependency. |
dodo.py |
Fixes tooling and a variable reference. |
contributing.md |
Removes duplicate documentation. |
bin/i18n/README.md |
Updates localization instructions. |
.gitignore |
Ignores localization intermediates. |
.github/workflows/check-mo-freshness.yml |
Validates generated .mo files. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| echo "The committed .mo files are out of date with the .properties sources." | ||
| echo "Run 'python -m doit localize' locally and commit the regenerated files." | ||
| echo "The freshly-generated files are attached as a workflow artifact." | ||
| exit 1 |
There was a problem hiding this comment.
The .mo bundles were regenerated in commit d94f91f7b9 ("chore: regenerate .mo bundles from committed .properties sources") and check-mo-freshness is now passing on the current head (46b741068d). Likely to get stale again if other PRs are merged before this one; will regenerate as necessary when ready to merge.

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 Englishtranslation. The fix is a one-liner in
localize.py-- chainenbehind the preferred locale in
gettext.translation(languages=[..., "en"]). Bundled with a CI workflow that catches.modrift on PRs sothis 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.ymlonevery PR touching localization sources regenerates
.mo/.pofromcommitted sources and fails if the committed binaries drift. Failed
runs upload the regenerated
.mofiles as an artifact so contributorscan commit them back without re-running doit locally.
Also bundled:
NameErrorindodo.py'suniquify_filewhen no lines werediscarded (
uniques->unique_lines).combined.tmpandcodestrings.tmpdoit intermediates.combine_property_files->properties) inbin/i18n/README.md; removed a duplicateLocalization section from
contributing.md.Test plan
pytest tests/commands/test_localize.py -v— 8 passed, includingtwo new tests: fr load with en-only msgid returns en; fr load with
translated msgid still returns fr
check-mo-freshnessandwill (deliberately) fail: committed
.mofiles have drifted. Willdownload regenerated files from workflow artifact and commit back.
Related followup: #441 (stop committing generated
.mo/.poat all).🤖 Generated with Claude Code