France Number Format
Try It — Live Preview
Format Rules
France formatting implementation notes
For France, format numbers with the explicit locale fr-FR; the reference value 1234567.89 renders as 1 234 567,89.
France's reference format uses "," as the decimal mark and a narrow non-breaking space (U+202F) for grouping. The integer uses repeating groups of three digits. Pass "fr-FR" directly to Intl.NumberFormat instead of replacing punctuation in a preformatted string.
EUR is shown with € after the amount with a separating space; the same reference value renders as 1 234 567,89 €. The percent sign appears after the amount with a separating space. Use the ISO currency code for storage and interchange because a visible symbol can be shared by unrelated currencies.
Western 0–9 digits are the page's reference digit system, and the negative pattern is -n. Treat fr-FR as the reference locale for this page, but still respect the user's explicitly selected language and numbering-system preferences. Validate output with formatToParts() when spacing, signs, or bidirectional literals affect the interface.
- Locale: fr-FR
- Decimal and grouping: 1 234 567,89
- Currency: EUR → 1 234 567,89 €
- Digits: Western 0–9 digits
Reviewed 19 July 2026. Sources: Unicode locale number formats · Intl.NumberFormat reference
What's different here
- Decimal mark is a comma (,) (en-US uses a period (.)).
- Thousands are grouped with a narrow no-break space (U+202F) (en-US uses a comma (,)).
- The currency symbol goes after the amount with a space (en-US: immediately before the amount).
- The percent sign goes after the number with a space (en-US: immediately after the number).
- Uses the same number pattern (decimal mark, grouping, symbol and percent placement) as Belgium, Portugal, Finland, Czech Republic, Hungary and Slovakia in Europe, plus 2 pages in other regions.
- Nearby differences: Luxembourg (fr-LU) groups with a period (.) instead of a narrow no-break space (U+202F); Germany (de-DE) groups with a period (.) instead of a narrow no-break space (U+202F); Sweden (sv-SE) groups with a no-break space (U+00A0) instead of a narrow no-break space (U+202F); Norway (nb-NO) groups with a no-break space (U+00A0) instead of a narrow no-break space (U+202F).
- Currency: EUR has ISO minor unit 2.
- The grouping character is a narrow no-break space (U+202F), not a regular space. Normalise it before parsing user input or comparing strings.
- Postal address order: name → building → street number + street → postcode + city → country. See address formats.
French number format
French (fr-FR) writes 1234567.89 as 1 234 567,89 €, and there are two different invisible characters in it. The spaces between digit groups are U+202F NARROW NO-BREAK SPACE. The space before € (and before %) is U+00A0 NO-BREAK SPACE. Neither is the U+0020 you type.
Three spaces that look the same
| Character | Name | Where fr-FR Intl uses it |
|---|---|---|
| U+0020 | SPACE | nowhere in formatted numbers |
| U+00A0 | NO-BREAK SPACE | before € and before % |
| U+202F | NARROW NO-BREAK SPACE | between digit groups |
Both no-break spaces keep a number on one line. Swap in U+0020 and a long amount can wrap mid-number. Keep them, and a plain string comparison fails:
const s = new Intl.NumberFormat('fr-FR').format(1234567.89);
s === "1 234 567,89"; // → false
s.includes("\u202F"); // → true
const norm = t => t.replace(/[\u00A0\u202F]/g, ' ');
norm(s) === "1 234 567,89"; // → trueformatToParts() shows which character sits where:
new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' })
.formatToParts(1234.5)
.filter(p => ['group', 'decimal', 'literal'].includes(p.type))
.map(p => `${p.type} U+${p.value.codePointAt(0).toString(16).toUpperCase().padStart(4, '0')}`)
.join(', ');
// → "group U+202F, decimal U+002C, literal U+00A0"In tests, compare formatToParts() results or normalise both sides. When parsing, accept all three spaces as group separators, because users type U+0020.
Francophone variants
Values are CLDR 48.2 / Intl output for 1234567.89:
| Locale | Grouping | Plain number | Currency | Percent (0.13) |
|---|---|---|---|---|
| fr-FR | U+202F | 1 234 567,89 | 1 234 567,89 € | 13 % |
| fr-BE | U+202F | 1 234 567,89 | 1 234 567,89 € | 13 % |
| fr-CA | U+00A0 | 1 234 567,89 | 1 234 567,89 $ | 13 % |
| fr-CH | apostrophe U+0027 (CLDR 48.2+); U+202F on older runtimes | 1'234'567,89 | 1'234'567.89 CHF | 13% |
fr-CA uses the wider no-break space. fr-CH now uses the Swiss apostrophe, switches to a period decimal inside CHF amounts, and puts no space before %.
Q&A
Why won't my French-formatted number match my test string?
Your test string has U+0020. Intl uses U+202F between groups and U+00A0 before € or %. Normalise both sides, or compare formatToParts() output.
Is there a space before % in French?
In fr-FR, fr-BE and fr-CA, yes: 13 %, and it's a no-break space. fr-CH has none: 13%.
Can I use a regular space as the thousands separator?
Accept it in input. In output, keep what Intl produces, so the number can't wrap.
Do Canada, Switzerland and Belgium format like France?
Belgium (fr-BE) matches France. Canada (fr-CA) groups with U+00A0 instead of U+202F. Switzerland (fr-CH) groups with an apostrophe and writes CHF amounts with a period decimal (1'234'567.89 CHF), which is CLDR data, not a typo.
Code Examples
// Basic number
new Intl.NumberFormat('fr-FR').format(1234567.89)
// → "1 234 567,89"
// Currency
new Intl.NumberFormat('fr-FR', {
style: 'currency',
currency: 'EUR'
}).format(1234567.89)
// → "1 234 567,89 €"
// Percentage
new Intl.NumberFormat('fr-FR', {
style: 'percent',
minimumFractionDigits: 1
}).format(0.125)
// Compact notation
new Intl.NumberFormat('fr-FR', {
notation: 'compact'
}).format(1234567)Negative Number Formats
new Intl.NumberFormat('fr-FR').format(-1234567.89)new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR', currencySign: 'accounting' }).format(-1234567.89)Edge Cases & Notes
Thousands separator is U+202F NARROW NO-BREAK SPACE, not a regular space. This catches many developers off-guard.
Continue your localization implementation
Connect this reference to the format, validation, and testing guidance used elsewhere on the site.
Formatting implementation
Numbers, currencies, dates, time zones, numbering systems, and production tests.
Failure patterns
Hardcoded copy, broken layouts, missing context, bidi, and cultural adaptation.
Standards reference
ISO country, currency, and language codes; BCP 47; E.164; and UPU S42.
Validation coverage
Provider evidence, country test cases, geocoding, deliverability, and phone checks.
Locale-aware numbers
Separators, grouping, currency precision, and Intl.NumberFormat patterns.
Localization testing
Pseudolocalization, test matrices, native review, and regional release gates.
Address and phone standards
UPU S42, E.164, adaptive forms, storage, parsing, and validation.
I18n architecture
Messages, Unicode, RTL, locale services, fallbacks, and delivery workflow.
Currency reference
ISO 4217 codes, symbols, minor units, and implementation examples.
Address formats
Country templates, field order, and accessible international forms.
Phone formats
E.164 storage, regional display, parsing, and validation.
Compare locales
Inspect the same numeric value across several country formats.
Interactive tools
Try number, address, and phone formatting in your browser.
