Problem
toLocaleOrdinal (src/libs/LocaleDigitUtils.ts) renders a number as a localized ordinal (1st, 2nd, …). It is broken in every locale except English, for three independent reasons:
- The selection logic hardcodes English's ordinal rule and applies it to all locales.
- The translation data is wrong — most locales have weekday or month abbreviations sitting in their ordinal keys.
- The output model is suffix-only (
${number}${suffix}), which cannot express locales where the ordinal is a prefix.
Non-English users currently see strings such as 4Do., 4czw., 4gio, 4juil. and 1第第.
Where it is used
src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx — day-of-month picker
src/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx — reporting frequency
src/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx — approver position
src/components/ApprovalWorkflowSection.tsx — approver position
src/libs/ReportActionsUtils.ts — auto-reporting offset in report actions
Cause 1 — the selection logic is English's rule, hand-rolled
const lastDigit = number % 10;
const lastTwoDigits = number % 100;
if (lastDigit === 1 && lastTwoDigits !== 11) {
suffixKey = 'workflowsPage.frequencies.ordinals.one';
} else if (lastDigit === 2 && lastTwoDigits !== 12) {
suffixKey = 'workflowsPage.frequencies.ordinals.two';
} else if (lastDigit === 3 && lastTwoDigits !== 13) {
suffixKey = 'workflowsPage.frequencies.ordinals.few';
}
This is a transcription of English's CLDR ordinal rule, and it runs for every locale. So German, Polish, Japanese and the rest all get one for 1, two for 2, few for 3 — categories their grammar does not define.
Intl.PluralRules(locale, {type: 'ordinal'}) already encodes the correct rule for each locale and should be used instead. Verified: for English it produces an identical category for every number from 1 to 120, so replacing the hand-rolled logic is behaviour-preserving for English.
Ordinal categories actually defined per locale
locale 1 2 3 4 8 11 21 22 31
en one two few other other other one two few
fr one other other other other other other other other
it other other other other many many other other other
es other other other other other other other other other
de other other other other other other other other other
el other other other other other other other other other
ja other other other other other other other other other
nl other other other other other other other other other
pl other other other other other other other other other
pt-BR other other other other other other other other other
zh-hans other other other other other other other other other
| Locale |
Ordinal categories |
en |
one, two, few, other |
it |
many, other |
fr |
one, other |
es, de, el, ja, nl, pl, pt-BR, zh-hans |
other only |
Eight of eleven locales need a single form. Their one / two / few entries are unreachable.
Cause 2 — the translation data is wrong
Current values of workflowsPage.frequencies.ordinals:
en one: 'st' two: 'nd' few: 'rd' other: 'th'
es one: 'º' two: 'º' few: 'º' other: 'º'
fr one: 'er' two: 'eᵉ' few: 'ᵉ' other: 'juil.'
de one: 'st' two: 'nd' few: 'rd.' other: 'Do.'
el one: 'στ' two: '2η' few: 'ημ.' other: 'ημ.'
it one: 'st' two: 'nd' few: 'rd' other: 'gio'
ja one: 'st' two: '番目' few: 'rd' other: '第'
nl one: 'ste' two: 'nd' few: 'rd' other: 'doe'
pl one: 'śr' two: 'nd' few: 'rd' other: 'czw.'
pt-BR one: 'º' two: 'nd' few: 'rd' other: 'º'
zh-hans one: '第第' two: '第几位' few: '第 rd' other: '第'
Several of these are not ordinal suffixes at all — they are weekday and month abbreviations:
| Locale |
Value |
What it actually means |
de |
Do. |
Donnerstag — Thursday |
pl |
czw. |
czwartek — Thursday |
pl |
śr |
środa — Wednesday |
it |
gio |
giovedì — Thursday |
fr |
juil. |
juillet — July |
nl |
doe |
the verb "doe" |
el |
ημ. |
ημέρα — day |
Others leak raw English (de, it, nl, pl, ja all have 'st' / 'nd' / 'rd' in some slot), and two contain doubled text that produces visible corruption:
el two: '2η' → the number 22 renders as 222η
zh-hans one: '第第' → the number 1 renders as 1第第
Cause 3 — the suffix model cannot express prefix locales
The function returns `${number}${suffix}`. Japanese and Chinese ordinals are formed with a prefix: 第1, 第22. No suffix value can produce that, so ja and zh-hans cannot be made correct without changing the model — currently they render 1第 instead of 第1.
Similarly, German and Polish ordinals are just the number followed by a period (1., 22.), which a suffix can express, but only if the value is corrected.
Comparison: current output vs a corrected implementation
Using Intl.PluralRules(locale, {type: 'ordinal'}) for selection, and per-locale data that returns the full string rather than a suffix:
n = 1 2 3 4 8 11 21 22 31
en current 1st 2nd 3rd 4th 8th 11th 21st 22nd 31st
corrected 1st 2nd 3rd 4th 8th 11th 21st 22nd 31st <- unchanged
fr current 1er 2eᵉ 3ᵉ 4juil. 8juil. 11juil. 21er 22eᵉ 31er
corrected 1er 2e 3e 4e 8e 11e 21e 22e 31e
de current 1st 2nd 3rd. 4Do. 8Do. 11Do. 21st 22nd 31st
corrected 1. 2. 3. 4. 8. 11. 21. 22. 31.
el current 1στ 22η 3ημ. 4ημ. 8ημ. 11ημ. 21στ 222η 31στ
corrected 1η 2η 3η 4η 8η 11η 21η 22η 31η
it current 1st 2nd 3rd 4gio 8gio 11gio 21st 22nd 31st
corrected 1º 2º 3º 4º 8º 11º 21º 22º 31º
ja current 1st 2番目 3rd 4第 8第 11第 21st 22番目 31st
corrected 第1 第2 第3 第4 第8 第11 第21 第22 第31
nl current 1ste 2nd 3rd 4doe 8doe 11doe 21ste 22nd 31ste
corrected 1e 2e 3e 4e 8e 11e 21e 22e 31e
pl current 1śr 2nd 3rd 4czw. 8czw. 11czw. 21śr 22nd 31śr
corrected 1. 2. 3. 4. 8. 11. 21. 22. 31.
zh current 1第第 2第几位 3第 rd 4第 8第 11第 21第第 22第几位 31第第
corrected 第1 第2 第3 第4 第8 第11 第21 第22 第31
The corrected values above are proposals and need native-speaker review before use. Two in particular are context-dependent:
- Greek ordinals inflect for gender.
η is feminine, which suits ημέρα (day) in the day-of-month usage, but would be wrong for a masculine or neuter noun.
- Dutch uses both
1e and 1ste conventions; the existing file mixes them.
Spanish and Brazilian Portuguese already produce correct output for most numbers (º), so they need the least work.
Suggested fix
Use Intl.PluralRules(locale, {type: 'ordinal'}) instead of hand written rules for each locale. This will make the solution scalable for other locales.
The writtenOrdinals path (numeric keys '1'–'10' returning "First", "Second", …) is a separate lookup and is not affected by any of this.
Verification
// selection logic is unchanged for English
for (let n = 1; n <= 120; n++) {
new Intl.PluralRules('en', {type: 'ordinal'}).select(n); // compare against the current rule
}
// categories each locale actually defines
new Intl.PluralRules('it', {type: 'ordinal'}).resolvedOptions().pluralCategories; // ['many', 'other']
Issue Owner
Current Issue Owner: @shubham1206agra
Problem
toLocaleOrdinal(src/libs/LocaleDigitUtils.ts) renders a number as a localized ordinal (1st,2nd, …). It is broken in every locale except English, for three independent reasons:${number}${suffix}), which cannot express locales where the ordinal is a prefix.Non-English users currently see strings such as
4Do.,4czw.,4gio,4juil.and1第第.Where it is used
src/pages/workspace/workflows/WorkspaceAutoReportingMonthlyOffsetPage.tsx— day-of-month pickersrc/pages/workspace/workflows/WorkspaceAutoReportingFrequencyPage.tsx— reporting frequencysrc/pages/workspace/workflows/approvals/ApprovalWorkflowEditor.tsx— approver positionsrc/components/ApprovalWorkflowSection.tsx— approver positionsrc/libs/ReportActionsUtils.ts— auto-reporting offset in report actionsCause 1 — the selection logic is English's rule, hand-rolled
This is a transcription of English's CLDR ordinal rule, and it runs for every locale. So German, Polish, Japanese and the rest all get
onefor 1,twofor 2,fewfor 3 — categories their grammar does not define.Intl.PluralRules(locale, {type: 'ordinal'})already encodes the correct rule for each locale and should be used instead. Verified: for English it produces an identical category for every number from 1 to 120, so replacing the hand-rolled logic is behaviour-preserving for English.Ordinal categories actually defined per locale
enone,two,few,otheritmany,otherfrone,otheres,de,el,ja,nl,pl,pt-BR,zh-hansotheronlyEight of eleven locales need a single form. Their
one/two/fewentries are unreachable.Cause 2 — the translation data is wrong
Current values of
workflowsPage.frequencies.ordinals:Several of these are not ordinal suffixes at all — they are weekday and month abbreviations:
deDo.plczw.plśritgiofrjuil.nldoeelημ.Others leak raw English (
de,it,nl,pl,jaall have'st'/'nd'/'rd'in some slot), and two contain doubled text that produces visible corruption:eltwo: '2η'→ the number 22 renders as222ηzh-hansone: '第第'→ the number 1 renders as1第第Cause 3 — the suffix model cannot express prefix locales
The function returns
`${number}${suffix}`. Japanese and Chinese ordinals are formed with a prefix: 第1, 第22. No suffix value can produce that, sojaandzh-hanscannot be made correct without changing the model — currently they render1第instead of第1.Similarly, German and Polish ordinals are just the number followed by a period (
1.,22.), which a suffix can express, but only if the value is corrected.Comparison: current output vs a corrected implementation
Using
Intl.PluralRules(locale, {type: 'ordinal'})for selection, and per-locale data that returns the full string rather than a suffix:The
correctedvalues above are proposals and need native-speaker review before use. Two in particular are context-dependent:ηis feminine, which suits ημέρα (day) in the day-of-month usage, but would be wrong for a masculine or neuter noun.1eand1steconventions; the existing file mixes them.Spanish and Brazilian Portuguese already produce correct output for most numbers (
º), so they need the least work.Suggested fix
Use
Intl.PluralRules(locale, {type: 'ordinal'})instead of hand written rules for each locale. This will make the solution scalable for other locales.The
writtenOrdinalspath (numeric keys'1'–'10'returning "First", "Second", …) is a separate lookup and is not affected by any of this.Verification
Issue Owner
Current Issue Owner: @shubham1206agra