pl: speak the unit after "na" in the accusative, not the nominative - #745
Merged
Conversation
Mechanical change: 2435 text keys raised from t/ot/ct to T/OT/CT in the two unicode files. No text is touched. The Polish rules were written before the lowercase/uppercase convention was in use, so entries that have been translated all along still carried the "needs review" key. The audit tool therefore reported them as untranslated, which made the real gaps impossible to see: audit-translations pl, untranslated text: 3162 -> 727 The remaining 727 are in the rule files, where the entries need reading rather than a mechanical pass, so they are deliberately left alone. Every raised entry was checked to differ from the English source, i.e. it really is translated. Entries whose text legitimately equals English (Roman numerals, ligatures, proper names, whitespace) were handled separately in the previous PR; entries still holding English text are NOT raised, since that would assert a translation that does not exist. Verification: cargo test --test languages Languages::pl 609 passed, 0 failed diff: 2436 insertions, 2436 deletions, zero changes outside key case The last point is checked by normalising the key case in the diff and confirming no unpaired lines remain (without normalising, 4710 remain, so the check does discriminate).
The previous commit raised the key on entries written on a single line
(`- "±": [t: "..."]`), but not on entries whose body is a block:
- "°":
- test:
if: "parent::*[name(.)='msup' ...]"
then: [t: "stopni"]
else: [t: "stopień"]
Those are exactly the entries carrying inflection or a TTS condition, so
312 of the most deliberate translations in the file were left looking
unreviewed. This raises them: 64 in unicode.yaml, 247 in unicode-full.yaml
and one single-line entry (U+23E6) missed earlier.
One entry is deliberately left with a lower-case key: `t: ""` for U+00A0,
the else-branch of the `@data-empty-in-2D` test. That is silence rather
than text, and an upper-case key means "a translator checked this
wording" - there is no wording to check. English keeps a lower-case key
there too.
No content changes; only key case. `cargo test --test languages
Languages::pl` gives 609 passed, 0 failed, which is the same result as
before this commit, as expected for a change the engine treats as
equivalent (src/speech.rs matches "t"|"T" in one match arm).
Two loose ends in the Polish localization.
**Rule-file keys.** 248 entries in the rule files still used a lower-case
key while carrying translated Polish text. Content is untouched - every
one of the 248 changed lines differs from its predecessor only in the
case of the key name, verified line by line after normalizing the key.
17 entries are deliberately left lower-case: five are the letters C, N,
Q, R, Z in `dimension-number-sets` (the letter itself is the wording, as
in English); `ct: "s"`/`ct: "-"` in the unit rule are the plural suffix
and prefix separator; `navigate.yaml:176` is a Polish euphony variant
("ze" before "st-"); the remaining seven are XPath expressions rather
than speech.
**Tests for the number-set rules.** These rules had no Polish test at
all, in a file where two silent defects were already found. New
`tests/Languages/pl/number_sets.rs` covers the bare symbols, the
dimension form, both sign variants, and ClearSpeak agreeing with
SimpleSpeak.
The `number_set_with_variable_exponent` case is the negative control:
`dimension-number-sets` fires on `count(*)=2`, so a non-numeric exponent
must fall through to the full phrase ("liczby całkowite do potęgi n")
rather than being read as a dimension. Without it, a rule that swallowed
every superscript would still pass.
Polish keeps the letter with a dimension ("R 3"), matching en, de, fr,
nb, sv, fi and es; Hungarian is the only language that translates in that
position too. Measured with the SAPI voice NVDA actually uses: "R 3" is
byte-identical to a phonetic "er 3" and "N 4" to "en 4", so the letters
are pronounced as their Polish names.
cargo test --test languages Languages::pl 614 passed, 0 failed
The `unit` rule appended `ct: "s"` whenever a unit had no entry in
`PluralForms`, and the Polish `PluralForms` contained no SI base unit at
all. Measured output of the engine before this change:
1 metr, 2 metrs, 5 metrs, 21 metrs, 2 grams, 2 sekundas,
2 ampers, 2 mols, 2 kilo-metrs
So a Polish NVDA user heard an English plural ending on essentially every
quantity. Hungarian avoids this with `CT: ""` (it appends nothing);
French and German legitimately use "s"; Russian, whose plurals work like
Polish, does not rely on a suffix at all and fills in `PluralForms`
instead. Polish was in the wrong group.
Polish does not have one plural form. The noun follows the numeral:
1 metr | 2, 3, 4 metry (nominative plural) | 5 metrów (genitive plural)
and the teens are the exception that breaks the naive rule: 12, 13, 14
metrów, likewise 112-114, while 22 and 102 take the nominative again.
One extra plural column is therefore not enough, so this adds a second
definition section, `PluralGenitive`, next to `PluralForms` (now the
nominative plural). No Rust change is needed: `build_values` accepts any
definition name as a hashmap.
The `unit` rule (both the bare and the prefixed branch) and the
`currency` rule now read the value of the numeral and pick the section
accordingly. When the numeral cannot be determined - a variable
coefficient, as in "x ms" - `$Coefficient` is 0 and the genitive is
used, which is also what Polish uses for unknown quantities ("x
mili-sekund").
Where no inflected form is known the singular is spoken rather than a
guessed ending: a wrong ending is worse for a screen reader user than a
missing one.
Both sections cover all 131 unit and currency names that the Polish
`SIUnits`, `UnitsWithoutPrefixes`, `EnglishUnits` and `CurrencySymbols`
can produce. The forms are written out because Polish inflection cannot
be derived from the ending: metr/metry but litr/litry, om/omy,
dzień/dni, tydzień/tygodnie, rok/lata. Multi-word units inflect the head
noun and keep the dependent word's case ("5 lat świetlnych", "5 minut
łuku"). They were checked against hunspell pl_PL; the only words it does
not know are the binary-prefix neologisms (kibibajt, gibibit, ...) whose
singular forms already existed in the file.
Three base forms were themselves wrong - plurals used as singulars, so
the engine said "1 herce" and "1 konie mechaniczne":
Hz herce -> herc
hp konie mechaniczne -> koń mechaniczny
℧ M h o -> mho
Every other language has a singular here (en hertz, ru герц, fi hertsi,
es hercio; hp: ru "лошадиная сила", hu "lóerő", nb "hestekraft"; ℧:
de/fr "mho").
Why this survived: the existing Polish expectations in
tests/Languages/pl/units.rs recorded the broken output, so 609 tests
passed while the speech was wrong ("2 dzieńs", "2 tydzieńs",
"kwetta-grams"). Expectations harvested from real engine output are a
good regression net but prove nothing about correctness unless they are
read as a user would hear them. 21 test expectations are updated here,
each difference reviewed word by word.
Verification:
cargo test --test languages Languages::pl 615 passed, 0 failed
reverting general.yaml alone -> 6 of 6 new tests fail
The new tests in tests/Languages/pl/unit_plurals.rs cover one class of
phenomenon each: the three numeral classes including the teens
exception, SI base units, the prefixed branch of the rule, irregular
nouns, the corrected base forms, and multi-word units.
The `per-fraction` rule speaks the denominator of a unit fraction after
the preposition "na", which in Polish governs the accusative case. The
unit was inserted in the nominative, so the engine said:
62 mile na godzina (should be "na godzinę")
5 metrów na sekunda (should be "na sekundę")
3 galony na mila (should be "na milę")
3 metry na sekunda do kwadratu
Only feminine nouns change form here. For masculine inanimate and neuter
names the accusative already equals the nominative, so "3 gramy na litr"
and "1 dżul na mol" were correct and stay untouched. That narrows the
work to 32 of the 131 unit and currency names, which is why
`AccusativeCase` deliberately holds only those - a missing entry means
"the form does not change".
Why this lives in the `unit` rule rather than in `per-fraction`: Russian
has the same problem and solves it inside `per-fraction` by testing the
unit symbol and writing the inflected word inline (`милю`), which works
because only one word is affected. Polish would need 32 such cases in
the rule. Instead the `unit` rule asks whether it is being spoken as a
fraction denominator, so the choice is made once, in one place, for every
name.
The denominator can also be raised to a power, as in m/s², and then the
unit's parent is the `power` element rather than the fraction itself, so
`IsFractionDenominator` tests both shapes. That second case was found
only after the first fix: the existing expectation "3 metry na sekunda do
kwadratu" was still passing green, because the list of failing tests
shows only the cases whose output actually changed. Searching every
Polish expectation for a nominative after "na" is what surfaced it.
An adjective agrees with its noun ("na stopę kwadratową") while a
dependent noun keeps its own case ("na minutę łuku", "na uncję płynu"),
so the forms are written out rather than derived.
Verification:
cargo test --test languages Languages::pl 620 passed, 0 failed
New tests in tests/Languages/pl/preposition_case.rs include two negative
controls: masculine and neuter denominators must NOT change, and a unit
standing on its own must stay in the nominative, so the accusative cannot
leak outside the fraction denominator.
michaldziwisz
force-pushed
the
pl-preposition-case
branch
from
September 2, 2026 21:00
c5f8648 to
5b625ad
Compare
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.
The
per-fractionrule speaks the denominator of a unit fraction after the preposition "na", which in Polish governs the accusative case. The unit was inserted in the nominative, so the engine said:Scope is narrower than it looks
Only feminine nouns change form here. For masculine inanimate and neuter names the accusative already equals the nominative, so
3 gramy na litrand1 dżul na molwere already correct and are untouched. That narrows the work to 32 of the 131 unit and currency names, which is why the newAccusativeCasesection deliberately holds only those - a missing entry means "the form does not change".Why this is in the
unitrule, not inper-fractionRussian has exactly this problem and solves it inside
per-fractionby testing the unit symbol and writing the inflected word inline (милю), which works because only one word is affected there. Polish would need 32 such cases in the rule body. Instead theunitrule asks whether it is currently being spoken as a fraction denominator, so the choice is made once, in one place, for every name.The denominator can also be raised to a power, as in m/s², and then the unit's parent is the
powerelement rather than the fraction itself, soIsFractionDenominatortests both shapes.That second shape is worth a note, because it is a measurement lesson rather than a coding one: after the first fix the existing expectation
"3 metry na sekunda do kwadratu"was still passing green. The list of failing tests only shows the cases whose output actually changed, so a case the fix never reached stays silent. Searching every Polish expectation for a nominative after "na" is what surfaced it - not the test run.An adjective agrees with its noun (
na stopę kwadratową) while a dependent noun keeps its own case (na minutę łuku,na uncję płynu), so the forms are written out rather than derived from the ending.Verification
The two controls that stay green under a reverted rule are the point: they assert that masculine and neuter denominators must not change, and that a unit standing on its own stays in the nominative, so the accusative cannot leak outside the fraction denominator.
Based on #744
This branch sits on top of #744, which in turn sits on #741 — merge order is #741, #744, then this one. The stack is deliberate: all three touch adjacent lines of
Rules/Languages/pl/definitions.yamland each adds amodline at the same place intests/Languages/pl.rs, so building them independently would hand you three trivial conflicts. Already rebased once when #741 grew; I will do it again if anything moves. Test count (625) includes the tests added by both PRs below.