An official plugin can compile catalogues but cannot make them, so it has two languages and core has sixty-eight #187

Closed
opened 2026-09-12 09:39:39 +00:00 by tiagoagueda · 2 comments
Owner

The rule, stated once for both

The rule is the same on both sides of the plugin boundary, and #182 just set the core half:

  • English (GB) is the source (languages.SOURCE = "en-gb") and has no catalogue.
  • While working: fr_FR and pt_PT. Enough to see a change in three languages and catch
    what only shows up in one.
  • At a release: the full set, with the twenty-four European Union catalogues hard-gated.

An official plugin follows it exactly. The full translation duty has not gone anywhere; it
has moved to the release.

Why plugins have two languages and it is not laziness

Core never translates a plugin's strings — that is the plugin-locale rule and it is right.
But the tooling never came with the duty:

Repository scripts/ Languages committed
postulo-paperless compile_messages.py fr_FR, pt_PT
postulo-imap compile_messages.py fr_FR, pt_PT
postulo-apprise compile_messages.py fr_FR, pt_PT
postulo-dav compile_messages.py fr_FR, pt_PT
postulo-helloworld compile_messages.py fr_FR, pt_PT

Every one of those five scripts is byte-identical — same MD5, copy-pasted five times —
and not one of them can extract. They turn a .po into a .mo and nothing creates or
updates the .po in the first place. Core has scripts/messages.py with extract,
extract --check, check and catalogue-set discovery; a plugin repository has none of it.

So the two catalogues each plugin carries were made by hand, and the other sixty-six were
never made at all. "In par with core" is not a matter of filling in translations — there
is nothing to fill in.
That is the thing to fix first.

Where the tool should live

Each plugin already depends on core in its dev group:

[dependency-groups]
dev = ["postulo @ git+https://source.tiagoagueda.com/postulo/postulo.git", ...]

Runtime dependencies are just httpx and friends — core is a development dependency only,
which is exactly the right shape for a translation tool. So core can ship one command a
plugin repository runs against itself, and the five identical compile_messages.py files
collapse into it rather than becoming six.

What it has to do, because a plugin is not a Django project:

  • Find the plugin's package and its locale/ without a settings module.
  • Create the full set of catalogues from core's language list, so a plugin has the same
    sixty-eight slots core does and a release sweep has somewhere to write.
  • extract, extract --check and check, behaving as core's do, so the habit transfers.
  • Keep compiling, since that is the one thing that already works and the reason the script
    exists (no GNU gettext needed).

Each plugin needs its own release gate

Core's uv run pytest -m release cannot see a plugin's catalogues — the plugin-locale
rule guarantees it. Without the same marker and the same checklist line in each plugin
repository, "full translation at release" quietly means plugins are never fully translated.
So each official plugin gets:

  • the release marker in its pyproject.toml, deselected by default;
  • a test asserting its own catalogues are complete;
  • uv run pytest -m release as step one of its own release checklist.

Two repositories that are not like the others

postulo-chromium and postulo-firefox use WebExtension _locales/<lang>/messages.json,
not gettext. Chromium already carries all thirty-nine; Firefox commits none and builds them
from the vendored Chromium source. The rule applies to both — three while working, the full
set at release — but none of the tooling above does, and their release gate has to be written
against messages.json.

postulo-mcp has no catalogue at all, and this is the one place the rule should probably
not simply be applied. Its user-facing strings are tool descriptions and prompts read by a
model, not a person; a French tool description reaching an agent whose user writes English
is worse than an English one. Its README and command-line messages are a different question.
Decide which of the two it has, and if the answer is "no catalogue, deliberately", write that
in the repository so nobody adds one for consistency.

  • #182 set the core half of this rule and is where the release marker comes from.
  • #186 puts the official plugins' versions in step with core; this is the same exercise for
    their catalogues, and both land per repository at each plugin's first release.
## The rule, stated once for both The rule is the same on both sides of the plugin boundary, and #182 just set the core half: - **English (GB) is the source** (`languages.SOURCE = "en-gb"`) and has no catalogue. - **While working: `fr_FR` and `pt_PT`.** Enough to see a change in three languages and catch what only shows up in one. - **At a release: the full set**, with the twenty-four European Union catalogues hard-gated. An official plugin follows it exactly. The full translation duty has not gone anywhere; it has moved to the release. ## Why plugins have two languages and it is not laziness Core never translates a plugin's strings — that is the plugin-locale rule and it is right. But the tooling never came with the duty: | Repository | `scripts/` | Languages committed | | --- | --- | --- | | postulo-paperless | `compile_messages.py` | `fr_FR`, `pt_PT` | | postulo-imap | `compile_messages.py` | `fr_FR`, `pt_PT` | | postulo-apprise | `compile_messages.py` | `fr_FR`, `pt_PT` | | postulo-dav | `compile_messages.py` | `fr_FR`, `pt_PT` | | postulo-helloworld | `compile_messages.py` | `fr_FR`, `pt_PT` | **Every one of those five scripts is byte-identical** — same MD5, copy-pasted five times — and **not one of them can extract.** They turn a `.po` into a `.mo` and nothing creates or updates the `.po` in the first place. Core has `scripts/messages.py` with `extract`, `extract --check`, `check` and catalogue-set discovery; a plugin repository has none of it. So the two catalogues each plugin carries were made by hand, and the other sixty-six were never made at all. "In par with core" is not a matter of filling in translations — **there is nothing to fill in.** That is the thing to fix first. ## Where the tool should live Each plugin already depends on core in its dev group: [dependency-groups] dev = ["postulo @ git+https://source.tiagoagueda.com/postulo/postulo.git", ...] Runtime dependencies are just `httpx` and friends — core is a development dependency only, which is exactly the right shape for a translation tool. So core can ship one command a plugin repository runs against itself, and the five identical `compile_messages.py` files collapse into it rather than becoming six. What it has to do, because a plugin is not a Django project: - Find the plugin's package and its `locale/` without a settings module. - Create the full set of catalogues from core's language list, so a plugin has the same sixty-eight slots core does and a release sweep has somewhere to write. - `extract`, `extract --check` and `check`, behaving as core's do, so the habit transfers. - Keep compiling, since that is the one thing that already works and the reason the script exists (no GNU gettext needed). ## Each plugin needs its own release gate Core's `uv run pytest -m release` **cannot see a plugin's catalogues** — the plugin-locale rule guarantees it. Without the same marker and the same checklist line in each plugin repository, "full translation at release" quietly means plugins are never fully translated. So each official plugin gets: - the `release` marker in its `pyproject.toml`, deselected by default; - a test asserting its own catalogues are complete; - `uv run pytest -m release` as step one of its own release checklist. ## Two repositories that are not like the others **postulo-chromium and postulo-firefox** use WebExtension `_locales/<lang>/messages.json`, not gettext. Chromium already carries all thirty-nine; Firefox commits none and builds them from the vendored Chromium source. The *rule* applies to both — three while working, the full set at release — but none of the tooling above does, and their release gate has to be written against `messages.json`. **postulo-mcp has no catalogue at all**, and this is the one place the rule should probably not simply be applied. Its user-facing strings are tool descriptions and prompts read by a **model**, not a person; a French tool description reaching an agent whose user writes English is worse than an English one. Its README and command-line messages are a different question. Decide which of the two it has, and if the answer is "no catalogue, deliberately", write that in the repository so nobody adds one for consistency. ## Related - #182 set the core half of this rule and is where the `release` marker comes from. - #186 puts the official plugins' versions in step with core; this is the same exercise for their catalogues, and both land per repository at each plugin's first release.
Author
Owner

Settled for postulo-mcp: English only, no catalogue — the one official plugin exempt from
this issue's rule, deliberately.

Its strings are tool descriptions, resource descriptions and prompts read by a model, and a
French tool description reaching an agent whose user writes English is worse than the English
one. Recorded with the reasoning, and with what would reopen it, at postulo/postulo-mcp#1.

Every other official plugin follows the rule in full. The exemption is about the audience of
these particular strings, not about translation mattering less.

**Settled for postulo-mcp: English only, no catalogue** — the one official plugin exempt from this issue's rule, deliberately. Its strings are tool descriptions, resource descriptions and prompts read by a *model*, and a French tool description reaching an agent whose user writes English is worse than the English one. Recorded with the reasoning, and with what would reopen it, at postulo/postulo-mcp#1. Every other official plugin follows the rule in full. The exemption is about the audience of these particular strings, not about translation mattering less.
Author
Owner

Landed across the repositories.

Core — f4263a2dc on main: scripts/messages.py is postulo.core.messages_tool now, pointed at a repository, installed as the console script postulo-messages wherever Postulo is a dependency. It reads pyproject.toml for the project's name, expects the package under src/, and runs extract, extract --check, check and compile against that package's locale/, creating the same sixty-eight slots core has. The wiki's Writing a plugin tells a plugin to use it.

Every Python plugin — helloworld 9397274, imap 23cef46, apprise 57a5e91, dav b15b164, paperless 9c05133: the copied compile_messages.py is gone, extract wrote the sixty-eight catalogues with French and European Portuguese kept and Brazilian Portuguese seeded from the European (draft), and tests/test_catalogues.py is the gate — catalogues current, placeholders agreeing, the two working languages complete on every commit, the twenty-four European Union languages complete behind uv run pytest -m release, registered as a marker and deselected by default. The full sweep for each plugin happens at its first release, as the rule says.

postulo-mcp is exempt, as decided on its #1; the two browser extensions keep their messages.json and are not covered by this tool.

Landed across the repositories. **Core** — `f4263a2dc` on `main`: `scripts/messages.py` is `postulo.core.messages_tool` now, pointed at a repository, installed as the console script `postulo-messages` wherever Postulo is a dependency. It reads `pyproject.toml` for the project's name, expects the package under `src/`, and runs `extract`, `extract --check`, `check` and `compile` against that package's `locale/`, creating the same sixty-eight slots core has. The wiki's *Writing a plugin* tells a plugin to use it. **Every Python plugin** — helloworld `9397274`, imap `23cef46`, apprise `57a5e91`, dav `b15b164`, paperless `9c05133`: the copied `compile_messages.py` is gone, `extract` wrote the sixty-eight catalogues with French and European Portuguese kept and Brazilian Portuguese seeded from the European (draft), and `tests/test_catalogues.py` is the gate — catalogues current, placeholders agreeing, the two working languages complete on every commit, the twenty-four European Union languages complete behind `uv run pytest -m release`, registered as a marker and deselected by default. The full sweep for each plugin happens at its first release, as the rule says. postulo-mcp is exempt, as decided on its #1; the two browser extensions keep their `messages.json` and are not covered by this tool.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#187
No description provided.