Bug
The shipped `lint-po` recipe (`just lint-po` → `uvx lint-po locales//LC_MESSAGES/.po`) rejects any `.po` file that uses GNU gettext plural forms.
Reproducer
A perfectly valid Babel-extracted plural entry:
```po
#: templates/frontend/directory.html.jinja:22
#, python-format
msgid "One podcast on the air."
msgid_plural "%(count)s podcasts on the air."
msgstr[0] "Un podcast emetent."
msgstr[1] "%(count)s podcasts emetent."
```
Generated by:
```jinja
{% trans count=entries | length %}
One podcast on the air.
{% pluralize %}
{{ count }} podcasts on the air.
{% endtrans %}
```
`lint-po` walks the file line-by-line with a small state machine that knows about `msgid` and `msgstr` but not `msgid_plural`, `msgstr[0]`, `msgstr[1]`, etc. Each unknown line triggers `warnings.warn("(state) Unexpected input: ...")` and bumps an internal `errors = True`. `main` then returns `1`, so `just lint-po` fails. `just lint` (which depends on `lint-po`) fails too.
Why it matters
Plural forms aren't optional polish — they're how GNU gettext expects you to localize "1 X / N X" patterns. Several target locales need the distinction (German, Catalan, Spanish, etc.). Without plural-form support in the linter, projects either:
- Skip pluralization entirely and write awkward fallbacks like `{% if n == 1 %}…{% else %}…{% endif %}` with two separate `msgid`s. (Workaround we just did in radio — losing N-form variants in languages that need them.)
- Disable the lint step.
- Switch to a different linter.
This is a footgun: scaffolded projects start clean, hit it the first time someone uses `{% trans count=… %}{% pluralize %}…{% endtrans %}`, and have to choose between losing pluralization or losing the linter.
Fix options
A. Replace `lint-po` with something plural-aware. Options:
- `msgfmt --check` (from gettext-tools) — battle-tested, ships with most distros, supports plural forms, but adds a system dependency outside uv/uvx.
- `polib`-based linter (Python). `polib.pofile().percent_translated()` etc., plus a small wrapper. Stays in the uv toolchain.
- `dennis-cmd` (https://github.com/willkg/dennis) — plural-aware, Python-based, packaged.
(B is probably the cleanest for vibetuner: stay in the uv ecosystem, support plurals, add it as a vibetuner-shipped CLI command.)
B. Drop the lint-po recipe. PO file syntax is mechanically validated by `pybabel compile` already; `lint-po` adds little beyond that. `just compile-locales` already fails on malformed files. Removing the duplicate check means one fewer scaffolded gotcha.
C. Patch `lint-po` upstream to handle plurals. Doable but the project (https://github.com/zaufi/lint-po) hasn't seen activity in a while. Might or might not get accepted.
We did A-via-workaround in radio (alltuner/radio#410) by avoiding plural forms entirely. Not a great precedent.
Pointers
- Scaffolded recipe: `.justfiles/linting.justfile` → `lint-po:` target.
- Failing input source: any Babel `{% trans count=... %}{% pluralize %}{% endtrans %}` block.
- `lint-po` source: https://github.com/zaufi/lint-po/blob/master/lint_po/main.py (the state machine that doesn't know about `msgid_plural` / `msgstr[N]`).
Filed by Claude Code.
Bug
The shipped `lint-po` recipe (`just lint-po` → `uvx lint-po locales//LC_MESSAGES/.po`) rejects any `.po` file that uses GNU gettext plural forms.
Reproducer
A perfectly valid Babel-extracted plural entry:
```po
#: templates/frontend/directory.html.jinja:22
#, python-format
msgid "One podcast on the air."
msgid_plural "%(count)s podcasts on the air."
msgstr[0] "Un podcast emetent."
msgstr[1] "%(count)s podcasts emetent."
```
Generated by:
```jinja
{% trans count=entries | length %}
One podcast on the air.
{% pluralize %}
{{ count }} podcasts on the air.
{% endtrans %}
```
`lint-po` walks the file line-by-line with a small state machine that knows about `msgid` and `msgstr` but not `msgid_plural`, `msgstr[0]`, `msgstr[1]`, etc. Each unknown line triggers `warnings.warn("(state) Unexpected input: ...")` and bumps an internal `errors = True`. `main` then returns `1`, so `just lint-po` fails. `just lint` (which depends on `lint-po`) fails too.
Why it matters
Plural forms aren't optional polish — they're how GNU gettext expects you to localize "1 X / N X" patterns. Several target locales need the distinction (German, Catalan, Spanish, etc.). Without plural-form support in the linter, projects either:
This is a footgun: scaffolded projects start clean, hit it the first time someone uses `{% trans count=… %}{% pluralize %}…{% endtrans %}`, and have to choose between losing pluralization or losing the linter.
Fix options
A. Replace `lint-po` with something plural-aware. Options:
(B is probably the cleanest for vibetuner: stay in the uv ecosystem, support plurals, add it as a vibetuner-shipped CLI command.)
B. Drop the lint-po recipe. PO file syntax is mechanically validated by `pybabel compile` already; `lint-po` adds little beyond that. `just compile-locales` already fails on malformed files. Removing the duplicate check means one fewer scaffolded gotcha.
C. Patch `lint-po` upstream to handle plurals. Doable but the project (https://github.com/zaufi/lint-po) hasn't seen activity in a while. Might or might not get accepted.
We did A-via-workaround in radio (alltuner/radio#410) by avoiding plural forms entirely. Not a great precedent.
Pointers
Filed by Claude Code.