Count with a plural rather than an apologetic "(s)" - #199
Merged
Merged
Conversation
Twenty-eight strings refused to choose a plural. Eighteen carried more than one
"(s)", and four carried three independent counts in a single sentence, each with
its own agreement — a key per form would have been eight variants of those alone.
The library for this cannot run here. @jsverse/transloco-messageformat compiles
every message with new Function, the CSP is script-src 'self', and the
application boots onto an error banner and seeds nothing. The unit suite passed
throughout: vitest runs under jsdom, where there is no policy to violate. Only
the e2e run said so, with 19 spec files out of 21 red.
So the transpiler is ours, sixty lines over Intl.PluralRules, reading the ICU the
strings were already written in. It knows plural, exact =N, the categories and #,
and nothing else — a parser that guesses at a grammar it does not implement is
worse than one that refuses it. Plain interpolation stays {{name}} and stays the
default transpiler's, which is what keeps {{app}} resolving against a sibling key.
Three more from the same ticket. The trash said "erased in 31 d" under a sentence
promising 30: the clock ticks every thirty seconds, so the now a card renders
against can be behind an instant Rust has just stamped, and rounding up made the
difference a whole day. Thirty-four icon buttons gained a tooltip, from the same
key as their label. And the one empty state that was a dead end now offers the
clearing that was already sitting there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The zero case is written out in words, and the scenario asserted the French ones. The e2e suite runs in whatever language the machine is set to: French here, English on both runners. It passed locally and could not pass on CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Closes #185.
The dependency was installed, and it cannot run here
You steered me to
@jsverse/transloco-messageformat, and the reasoning held: 18 of the 28 strings carry more than one(s), four of them three independent counts in a single sentence with three separate agreements. A key per form would have meant eight variants of those alone.It compiles each message into a function with
new Function.tauri.conf.jsonisscript-src 'self'. The application booted onto a CSP error banner, seeded no sample note, and 19 of 21 e2e spec files failed.What replaces it
plural-transpiler.ts, ~60 lines, reading exactly the ICU syntax already written — so the 28 converted strings are kept as they are. No dependency, noeval, no CSP change.Two choices that bound the risk of hand-rolling this:
plural, exact=N, the categoriesIntl.PluralRulesanswers, and#. It does not knowselect,selectordinal, or a plural inside a plural. Nothing here needs them, and a parser that guesses at a grammar it does not implement is worse than one that refuses it.{{name}}— the convention of the other 75 strings — soDefaultTranspilerkeeps handling it and this code never touches it. That includes the sibling-key resolution{{app}}depends on.Eleven tests, the two that matter most being:
Zero taking the singular in French is the thing no doubled key of mine got right, and the third line keeps the reason written down rather than in a commit message.
TranslocoServiceasks for the transpiler in its own constructor, so taking it by injection would be a cycle. The service is resolved on the first render instead.The rest
(s)left, 506 keys in parity. Two guards on the shipped files: no(s)may come back, and no brace may appear that is neither an interpolation nor a plural.shortcuts.unavailableis the only one that needed the call site changed: it agrees three times over and had no count at all, only a list.ClockServiceticks every 30 s, so thenowa card renders against can be behind an instant Rust has just stamped; the gap reads as 30 days and change, and rounding up made the change a whole day. The ticket proposedfloor, which says 29 for the same note — the same contradiction reversed. It rounds now, and the test reproduces the exact string from the screenshot.aria-label.noResultsis a dead end —clearFilters()was sitting there unoffered — and it is now conditioned onhasUserFilters, neverisFiltering, which is true inside a folder.🤖 Generated with Claude Code