From be2e205965eb2afbee4c7457c2f8a84d2356177f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Falc=C3=B3n?= Date: Tue, 3 Mar 2026 14:50:41 +0000 Subject: [PATCH] fix(i18n): force thousands separator for 4-digit amounts in es-ES locale (#193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Modern ICU/CLDR sets `useGrouping: 'auto'` for `es-ES`, which suppresses the thousands separator for numbers between 1,000 and 9,999 (e.g. `1560,07 €` instead of `1.560,07 €`) - Added `useGrouping: 'always'` to `Intl.NumberFormat` options in `formatCurrency` to force the separator at 1,000+ for all locales - Added test cases covering `es-ES` + `EUR` for 4-digit amounts (1,000–9,999) --- resources/js/utils/currency.test.ts | 34 +++++++++++++++++++++++++++-- resources/js/utils/currency.ts | 1 + 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/resources/js/utils/currency.test.ts b/resources/js/utils/currency.test.ts index 4f4dc3de..216a3aa3 100644 --- a/resources/js/utils/currency.test.ts +++ b/resources/js/utils/currency.test.ts @@ -30,7 +30,10 @@ describe('formatCurrency', () => { const formatted = formatCurrency(cents, 'USD', 'en-US'); // The formatted string must contain a thousands separator for values >= $1,000 if (cents >= 100000) { - expect(formatted, `Expected thousands separator for ${cents} cents`).toContain(','); + expect( + formatted, + `Expected thousands separator for ${cents} cents`, + ).toContain(','); } // The formatted value must exactly match formatting the true decimal amount const expected = new Intl.NumberFormat('en-US', { @@ -39,7 +42,9 @@ describe('formatCurrency', () => { minimumFractionDigits: 2, maximumFractionDigits: 2, }).format(cents / 100); - expect(formatted.replace(/\u202F/g, ' ')).toBe(expected.replace(/\s/g, ' ')); + expect(formatted.replace(/\u202F/g, ' ')).toBe( + expected.replace(/\s/g, ' '), + ); } }); @@ -58,4 +63,29 @@ describe('formatCurrency', () => { it('respects custom fraction digits', () => { expect(formatCurrency(100000, 'USD', 'en-US', 0, 0)).toBe('$1,000'); }); + + it('formats es-ES EUR 4-digit amounts with thousands separator', () => { + // In some ICU/CLDR versions, es-ES uses useGrouping:'auto' which omits the thousands + // separator for 4-digit numbers (1,000–9,999). We force useGrouping:'always' so that + // 1.560,07 € is shown instead of 1560,07 €. + const formatted = formatCurrency(156007, 'EUR', 'es-ES'); + expect(formatted).toContain('1.560'); + }); + + it('formats es-ES EUR amounts between 1,000 and 9,999 with thousands separator', () => { + const cases = [ + { cents: 156007, expectedInteger: '1.560' }, + { cents: 100001, expectedInteger: '1.000' }, + { cents: 999999, expectedInteger: '9.999' }, + { cents: 150000, expectedInteger: '1.500' }, + ]; + + for (const { cents, expectedInteger } of cases) { + const formatted = formatCurrency(cents, 'EUR', 'es-ES'); + expect( + formatted, + `Expected thousands separator for ${cents} cents in es-ES`, + ).toContain(expectedInteger); + } + }); }); diff --git a/resources/js/utils/currency.ts b/resources/js/utils/currency.ts index 998ea100..5d535ebf 100644 --- a/resources/js/utils/currency.ts +++ b/resources/js/utils/currency.ts @@ -11,6 +11,7 @@ export function formatCurrency( currency: currencyCode, minimumFractionDigits, maximumFractionDigits, + useGrouping: 'always', }) .format(amount) .replace(/\s/g, '\u202F');