From 2853ae167b4aa09f0dadf5e35abb2dfcc34dda0f Mon Sep 17 00:00:00 2001 From: Tyler Vigario Date: Tue, 16 Jun 2026 15:01:04 -0700 Subject: [PATCH 1/2] fix(ro-RO): insert "de" before the currency unit for 20+ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Romanian requires "de" before a counted noun when the number's last two digits are 00 or 20-99 ("douăzeci de lei", "o sută de lei", but "o sută unu lei"). toCurrency applied this to the bani (cents) path but not to the lei: it spelled "douăzeci lei" / "o sută lei". The lei path now mirrors the bani path and the scale-word handling already present in buildScalePhrase. The fixtures had encoded the de-less output, so the suite stayed green on the bug; the seven affected lei cases (21, 42, 100, 1000, and the lei+bani combinations) are corrected to the proper forms. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ro-RO.js | 8 +++++++- test/fixtures/ro-RO.js | 14 +++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ro-RO.js b/src/ro-RO.js index 953ae86f..80a5e0d9 100644 --- a/src/ro-RO.js +++ b/src/ro-RO.js @@ -452,7 +452,13 @@ function toCurrency(value) { } else { const leuWord = integerToWords(dollars, 'masculine') - parts.push(leuWord + ' ' + LEU_PLURAL) + // Romanian inserts "de" before the noun when the count's last two digits + // are 00 or 20-99 (the CLDR `other` category): "douăzeci de lei", "o sută + // de lei", but "o sută unu lei". Mirrors the bani path below and the + // scale-word handling in buildScalePhrase. + const m = dollars % 100n + const needsDe = dollars >= 20n && (m === 0n || m >= 20n) + parts.push(leuWord + (needsDe ? ' de ' : ' ') + LEU_PLURAL) } } diff --git a/test/fixtures/ro-RO.js b/test/fixtures/ro-RO.js index 0dc48ae0..13079ea4 100644 --- a/test/fixtures/ro-RO.js +++ b/test/fixtures/ro-RO.js @@ -212,10 +212,10 @@ export const currency = [ [2, 'doi lei'], [5, 'cinci lei'], [10, 'zece lei'], - [21, 'douăzeci și unu lei'], - [42, 'patruzeci și doi lei'], - [100, 'o sută lei'], - [1000, 'o mie lei'], + [21, 'douăzeci și unu de lei'], + [42, 'patruzeci și doi de lei'], + [100, 'o sută de lei'], + [1000, 'o mie de lei'], // Bani only [0.01, 'un ban'], @@ -229,12 +229,12 @@ export const currency = [ [1.01, 'un leu un ban'], [1.50, 'un leu cincizeci de bani'], [2.02, 'doi lei doi bani'], - [42.50, 'patruzeci și doi lei cincizeci de bani'], - [100.99, 'o sută lei nouăzeci și nouă de bani'], + [42.50, 'patruzeci și doi de lei cincizeci de bani'], + [100.99, 'o sută de lei nouăzeci și nouă de bani'], // Negative amounts [-1, 'minus un leu'], - [-42.50, 'minus patruzeci și doi lei cincizeci de bani'], + [-42.50, 'minus patruzeci și doi de lei cincizeci de bani'], // Edge cases ['5.00', 'cinci lei'], From 583854a543fd3efb0f7f39f20412b5970bfe662d Mon Sep 17 00:00:00 2001 From: Tyler Vigario Date: Tue, 16 Jun 2026 15:11:54 -0700 Subject: [PATCH 2/2] test(ro-RO): pin the "de" boundary at 19/20 and clarify the rule comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add explicit 19 ("nouăsprezece lei", no "de") and 20 ("douăzeci de lei") fixtures so the >= 20 boundary is pinned two-sided — a > 20 vs >= 20 off-by-one would now fail. Reword the rule comment to make the >= 20 gate explicit (it excludes 0-19, so 0 doesn't take "de") and to stop claiming it mirrors buildScalePhrase, which uses a related but per-segment predicate. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ro-RO.js | 9 +++++---- test/fixtures/ro-RO.js | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/ro-RO.js b/src/ro-RO.js index 80a5e0d9..89981f02 100644 --- a/src/ro-RO.js +++ b/src/ro-RO.js @@ -452,10 +452,11 @@ function toCurrency(value) { } else { const leuWord = integerToWords(dollars, 'masculine') - // Romanian inserts "de" before the noun when the count's last two digits - // are 00 or 20-99 (the CLDR `other` category): "douăzeci de lei", "o sută - // de lei", but "o sută unu lei". Mirrors the bani path below and the - // scale-word handling in buildScalePhrase. + // Romanian inserts "de" before the noun for the CLDR `other` category: + // count >= 20 whose last two digits are 00 or 20-99 — "douăzeci de lei", + // "o sută de lei", but "o sută unu lei" (101) and no "de" below 20. The + // bani path below applies the same rule; buildScalePhrase uses a related + // per-segment predicate for scale words. const m = dollars % 100n const needsDe = dollars >= 20n && (m === 0n || m >= 20n) parts.push(leuWord + (needsDe ? ' de ' : ' ') + LEU_PLURAL) diff --git a/test/fixtures/ro-RO.js b/test/fixtures/ro-RO.js index 13079ea4..33b8b9ea 100644 --- a/test/fixtures/ro-RO.js +++ b/test/fixtures/ro-RO.js @@ -212,6 +212,8 @@ export const currency = [ [2, 'doi lei'], [5, 'cinci lei'], [10, 'zece lei'], + [19, 'nouăsprezece lei'], + [20, 'douăzeci de lei'], [21, 'douăzeci și unu de lei'], [42, 'patruzeci și doi de lei'], [100, 'o sută de lei'],