Skip to content

Fall back to English for messages with damaged format placeholders - #14666

Open
shashb27 wants to merge 2 commits into
sphinx-doc:masterfrom
shashb27:fix/14664-safe-format-translations
Open

Fall back to English for messages with damaged format placeholders#14666
shashb27 wants to merge 2 commits into
sphinx-doc:masterfrom
shashb27:fix/14664-safe-format-translations

Conversation

@shashb27

@shashb27 shashb27 commented Sep 2, 2026

Copy link
Copy Markdown

Refs #14664.

Problem

As detailed in #14664, some translated catalogues (e.g. el, fa) contain msgstrs with damaged str.format placeholders:

>>> import gettext
>>> t = gettext.translation('sphinx', 'sphinx/locale', languages=['el'])
>>> t.gettext("The config value `{name}' has type `{current.__name__}', "
...           "defaults to `{default.__name__}'.").format(name='x', current=int, default=str)
KeyError: 'current__name__'

Six call sites format translated text eagerly, so a single damaged placeholder in any locale kills the build with a KeyError that surfaces as an ExtensionError blaming an extension (sphinx/config.py x3, sphinx/_cli/__init__.py x2, sphinx/transforms/i18n.py).

Approach

Add sphinx.locale.safe_format: pass the untranslated message and the format arguments; it formats the translation and falls back to the original English message when the translation is damaged.

Damage detection compares the set of str.format field names between the msgid and its translation, plus a try/except around the format call:

  • Renamed or added placeholders (e.g. {current.__name__} -> {current__name__}) and dropped placeholders are both caught. A plain try/except alone cannot catch dropped placeholders, because str.format silently ignores surplus arguments and would emit an incomplete message.
  • Comparing sets (not sequences) keeps legitimate translator reordering ({1} ... {0}) working.
  • When no translation is active, behaviour is identical to plain .format (the untranslated message formats itself).

Affected messages degrade to an English warning instead of a dead build. The catalogue data itself (the Transifex half of #14664) is not touched here — that needs someone with Transifex access, per the discussion in the issue.

Tests

  • tests/test_locale.py: unit tests for the helper (healthy translation, renamed keyword, dropped positional, mixed corruption, reordered placeholders, other namespaces).
  • tests/test_config/test_config.py: regression test driving a corrupted catalogue through check_confval_types, asserting the English fallback message.
  • The full tests/test_intl/test_intl.py suite passes (61 tests) — the compare_references warnings now format through the helper.

Some translated catalogues contain msgstrs with damaged str.format placeholders. Six call sites formatted translated text eagerly, so a single damaged placeholder in any locale crashed the build with a KeyError.

Add sphinx.locale.safe_format, which formats the translation and falls back to the untranslated message when the placeholder sets do not match or formatting raises, and route the eager call sites through it.

Refs sphinx-doc#14664
@shashb27

shashb27 commented Sep 2, 2026

Copy link
Copy Markdown
Author

Heads-up on the red test matrix: the failing set here (test_viewcode*, test_default_highlight, test_html_code_role, the two test_additional_targets_* intl tests, plus the wrongenc.inc checkout encode errors) matches what every currently-open PR hits — the same jobs on #14654 and #14662 fail with the identical test list, and the last green main.yml run on master is from Aug 11. So this looks like dependency drift in the CI environment rather than something from this branch.

For what it's worth, the suites covering the code this branch touches pass in CI (test_config shows green in the matrix) and locally: tests/test_locale.py, tests/test_config/test_config.py, tests/test_command_line.py, and the full tests/test_intl/test_intl.py (61/61).

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