037 - Gettext Catalogs with LibreTranslate Autotranslation¶
User Story¶
As a maintainer of Infinito.Nexus, I want every user-facing string that core ships extracted into gettext catalogs and machine-translated by LibreTranslate through make, so that the API, the documentation and every consumer app serve all ISO 639-1 languages from one source of truth in this repository.
Scope¶
This requirement covers the catalogs, their extraction, their machine translation and their validation. Serving the catalogs is 038 - web-svc-api; consuming them inside core roles is 039 - Core i18n Consumers. The consumer repositories (infinito-nexus-gui, meta-infinite-graph, portfolio) keep their own interface strings in their own gettext catalogs and switch only after 037 to 039 are green.
Findings That Constrain the Design¶
Core has no gettext today. The only catalogue is logout_i18n.yml: 30 languages keyed by language code, named keys,
{placeholder}slots and adirvalue, inlined as JSON by logout-panel.js.j2.The translatable core strings are
galaxy_info.descriptionin everyroles/*/meta/main.yml(266 roles),titleanddescriptionof every node in categories.yml, the key anddescriptionof every entry in menu_categories.yml, and the logout panel strings. Role titles are product names and MUST NOT be translated.Babel 2.18 is already installed in the development interpreter as a Sphinx dependency; Sphinx compiles
.pocatalogs through it.The Argos package index behind LibreTranslate offers 49 languages paired with English. 47 of them are ISO 639-1 codes;
pb(Brazilian Portuguese) andzt(Traditional Chinese) are not and are out of scope. The remaining ISO languages have no model.web-svc-libretranslate pins
libretranslate/libretranslatev1.9.6withload_only: [en, de],cpus: 0.2andmem_limit: 256m. Bulk translation MUST NOT depend on that deployment;makeruns its own short-lived container of the same image and version.The documentation sources hold about 300,000 words (role READMEs and
docs/). Translating them into 47 languages takes days of CPU, so the documentation domain runs per language and in the background.make i18n-translatereaches its engines through the gateway of 040 - Translation Gateway rather than one engine directly, and brings up that gateway’s bundle the way it used to bring up the lone engine. The boundary the two requirements first drew between build time and request time is recorded as re-opened in 040, with the catalog measurement that overturned it.
Design¶
meta/languages.ymllists the 184 ISO 639-1 languages with English name, native name and text direction. It is the single source for every language list in core.Catalogs use the standard layout
locale/<code>/LC_MESSAGES/<domain>.po, without committed.pottemplates.Domain
coreexists for every language except the source languageen. Every message carries amsgctxtnaming its source:role:<role>:description,category:<dotted path>:title|description,menu:<key>:title|description,logout:<key>.Domain
docsexists for the 47 languages LibreTranslate supports. Its messages come from the Sphinxgettextbuilder run over the same generated sources the documentation build uses, compacted into the single domaindocs.Machine translations carry the translator comment
libretranslate. A human translation never carries it and is never overwritten.
Acceptance Criteria¶
[x]
meta/languages.ymllists exactly the 184 ISO 639-1 languages, each with English name, native name and directionltrorrtl.[x]
make i18n-extractwriteslocale/<code>/LC_MESSAGES/core.pofor all 183 non-English languages, holding every role description, category title and description, dashboard menu label and logout panel string under themsgctxtof its source.[x]
make i18n-extractwriteslocale/<code>/LC_MESSAGES/docs.pofor every language LibreTranslate supports, holding the messages of the Sphinxgettextbuild of the documentation.[x] Running
make i18n-extracttwice in a row leaves the working tree unchanged.[x] After a source string changes,
make i18n-extractkeeps the previous translation as a fuzzy entry, and after a source string is removed its entry is gone.[x]
make i18n-translate domain=corestarts a LibreTranslate container with the image and version ofroles/web-svc-libretranslate/meta/services.yml, and no container of it remains after the target ends, on success and on failure.[ ] After
make i18n-translate domain=core, nomsgstris empty or fuzzy incore.poof any language LibreTranslate supports, and everycore.poof the other languages is left untouched.[x] Every machine translation written by
make i18n-translatecarries the translator commentlibretranslate, and an entry with a human translation is byte-identical before and after the run.[x] A machine translation that drops or alters a
{name},%(name)sor%splaceholder, an inline literal or a URL of its source is discarded, left empty and reported, never written.[x]
make i18n-translate domain=<domain> languages=<codes>translates only the listed languages.[ ]
make i18n-translate domain=docs languages=defills everymsgstrof the Germandocs.po.[x] A lint test fails when a catalog is out of date with the extracted sources, when a language of
meta/languages.ymlhas nocore.po, or when a translated entry breaks a placeholder of its source.[x] Unit tests exercise extraction, the merge rules and the translator against a fake LibreTranslate server.