[qt] Add Qt ID-based TS formatter with plural support#28
Merged
Conversation
Adds a first-class `qt` formatter that emits Qt Linguist TS files (consumed by lrelease → QM bundles, looked up at runtime via qtTrId). Output shape: ID-based <message id="..."> messages wrapped in a single empty <context><name></name> block (required by qtTrId). Definitions' comments are emitted as <extracomment> for translator context in Qt Linguist. Sequential %@/%d → %1, %2 placeholder conversion; numbered %N$x preserved as %N; any leading/trailing whitespace on each line (space, tab, NBSP, …) is escaped as a numeric entity so it survives lrelease. Plural (numerus) support: maps Twine's CLDR-named plural categories (one/few/many/zero/two/other) to Qt's positional <numerusform> entries via a table derived from Qt's qttools/src/linguist/shared/numerus.cpp. The first count-eligible placeholder in each form becomes %n; extras stay as %1, %2. Per-form fallback chain when a category is missing for the target language: translated[cat] → translated['other'] → developer[cat] → developer['other'] → fail. Languages absent from the Qt plural table fail generation (no silent default), with region tags falling back to base language (es-MX → es). pt-BR and zh-Hans/zh-Hant get explicit entries. Read support covers non-plural messages; numerus forms are skipped on read since the positional-to-CLDR mapping is lossy and no current pipeline feeds Qt TS back into Twine. Asturian (ast) is not in Qt's numerusTable but follows CLDR english-style 2-form rule; added explicitly. Also fixes a pre-existing crash in OutputProcessor.process(): a plural definition whose target language had only a non-plural translation (or no fallback language with plural data) used to raise KeyError/TypeError. The processor now leaves the plural slot absent in those cases and the formatter falls through to the non-plural path. Tested against OM's strings.txt: all 63 language files compile cleanly via lrelease, including the three real plural keys (bookmarks_places, tracks, bookmarks_detect_message) which now emit correct numerus messages instead of being silently rendered as their 'other' form. New pytest suites cover the formatter (singular + plural, fallbacks, escaping, extracomment) and an lrelease compilation check as a skip-if-missing integration test. Signed-off-by: Alexander Borsuk <me@alex.bio>
strump
approved these changes
May 21, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a first-class
qtformatter that emits Qt Linguist TS files (consumed bylrelease→ QM bundles, looked up at runtime viaqtTrId).<message id="...">in a single empty<context><name></name>(required byqtTrId); definition comments emitted as<extracomment>; sequential%@/%d→%1,%2placeholder conversion with numbered%N$xpreserved; leading/trailing whitespace on each line (space, tab, NBSP, …) escaped as numeric entities so it surviveslrelease.<numerusform>via a table derived fromqttools/src/linguist/shared/numerus.cpp. Per-form fallback chaintranslated[cat] → translated[other] → developer[cat] → developer[other]. Region tags fall back to base language;pt-BR,zh-Hans/zh-Hant,astget explicit entries.OutputProcessor.process()where a plural definition whose target language had only a non-plural translation (or no fallback language with plural data) raisedKeyError/TypeError.Test plan
pytest python_twine/tests— 132 passing (was 119; +13 new tests covering formatter singular/plural paths, fallback chain, escaping, extracomment,OutputProcessorregression, and lrelease integration).data/strings/strings.txt; every file compiles cleanly throughlreleasewith no warnings or errors.bookmarks_places,tracks,bookmarks_detect_message) — now emit correct positional<numerusform>per language instead of being silently rendered as theirotherform.<extracomment>placement matches Qt Linguist's expectations (between<message ...>and<source>).LLM tooling was used to draft and refine portions of this change.
CC @Osyotr