|
| 1 | +## Guide to the ECMAScript Internationalization API |
| 2 | + |
| 3 | +For years, developers relied on JavaScript libraries, string manipulation, or server-side logic to ensure that users around the world see dates, numbers, and text formatted in a way that is natural and correct for them. These solutions, while functional, often added significant weight to web pages and created maintenance challenges. |
| 4 | + |
| 5 | +Fortunately, modern browsers now have a built-in, standardized solution: the **ECMAScript Internationalization API**, available globally in JavaScript via the `Intl` object. This API provides a native way to handle locale- and culture-sensitive data and operations, ensuring your application speaks your user's language correctly and efficiently. |
| 6 | + |
| 7 | +This article will serve as a practical overview of the most essential parts of the `Intl` API, providing actionable examples you can use to internationalize your web applications today. |
| 8 | + |
| 9 | +### The Core Concept: Locales and Options |
| 10 | + |
| 11 | +Before diving into specific formatters, it's important to understand the two fundamental arguments that nearly every `Intl` constructor takes: |
| 12 | + |
| 13 | +1. **`locales`**: A string representing a language tag (following the BCP 47 standard), such as `'en-US'` (American English), `'fr-FR'` (French in France), or simply `'ja'` (Japanese). You can also provide an array of locales, like `['fr-CA', 'fr-FR']`, and the browser will use the first one it supports. If omitted, the browser's default locale will be used. |
| 14 | +2. **`options`**: An object that allows you to customize the formatting behavior. This is where the real power of the API lies, enabling you to specify everything from currency symbols to date styles. |
| 15 | + |
| 16 | +### 1. Formatting Dates and Times with `Intl.DateTimeFormat` |
| 17 | + |
| 18 | +One of the most common i18n tasks is displaying dates and times. A date like "10/12/2025" can mean October 12th in the US but December 10th in much of Europe. `Intl.DateTimeFormat` solves this ambiguity effortlessly. |
| 19 | + |
| 20 | +The basic usage is simple. You create a formatter instance and then call its `.format()` method. |
| 21 | + |
| 22 | +```javascript |
| 23 | +const eventDate = new Date(); |
| 24 | + |
| 25 | +// For a user in the United States |
| 26 | +const usFormatter = new Intl.DateTimeFormat('en-US'); |
| 27 | +console.log(usFormatter.format(eventDate)); |
| 28 | +// Output: 10/26/2025 |
| 29 | + |
| 30 | +// For a user in Germany |
| 31 | +const deFormatter = new Intl.DateTimeFormat('de-DE'); |
| 32 | +console.log(deFormatter.format(eventDate)); |
| 33 | +// Output: 26.10.2025 |
| 34 | +``` |
| 35 | + |
| 36 | +#### Fine-Grained Control with Options |
| 37 | + |
| 38 | +You can achieve much more detailed and readable formats using the `options` object. The modern approach uses `dateStyle` and `timeStyle`. |
| 39 | + |
| 40 | +```javascript |
| 41 | +const options = { |
| 42 | + dateStyle: 'full', |
| 43 | + timeStyle: 'long', |
| 44 | +}; |
| 45 | + |
| 46 | +const formatter = new Intl.DateTimeFormat('fr-FR', options); |
| 47 | +console.log(formatter.format(eventDate)); |
| 48 | +// Output: jeudi 26 octobre 2025 à 10:30:00 UTC |
| 49 | +``` |
| 50 | + |
| 51 | +You can also specify time zones, a critical feature for global applications. |
| 52 | + |
| 53 | +```javascript |
| 54 | +const japanFormatter = new Intl.DateTimeFormat('ja-JP', { |
| 55 | + year: 'numeric', |
| 56 | + month: 'long', |
| 57 | + day: 'numeric', |
| 58 | + timeZone: 'Asia/Tokyo', |
| 59 | +}); |
| 60 | + |
| 61 | +console.log(japanFormatter.format(eventDate)); |
| 62 | +// Sample Output: 2025年10月27日 (Note the date may change due to timezone) |
| 63 | +``` |
| 64 | + |
| 65 | +### 2. Handling Numbers, Currencies, and Units with `Intl.NumberFormat` |
| 66 | + |
| 67 | +Numbers are formatted differently across the world. For example, the decimal separator can be a period or a comma. `Intl.NumberFormat` handles this seamlessly. |
| 68 | + |
| 69 | +```javascript |
| 70 | +const largeNumber = 1234567.89; |
| 71 | + |
| 72 | +// United States |
| 73 | +console.log(new Intl.NumberFormat('en-US').format(largeNumber)); |
| 74 | +// Output: 1,234,567.89 |
| 75 | + |
| 76 | +// Germany |
| 77 | +console.log(new Intl.NumberFormat('de-DE').format(largeNumber)); |
| 78 | +// Output: 1.234.567,89 |
| 79 | +``` |
| 80 | + |
| 81 | +#### Currency Formatting |
| 82 | + |
| 83 | +Formatting currency correctly is crucial for e-commerce. It involves more than just a symbol; the position of the symbol and spacing are locale-dependent. `Intl.NumberFormat` requires the `style` to be `'currency'` and an ISO 4217 currency code. |
| 84 | + |
| 85 | +```javascript |
| 86 | +const price = 99.95; |
| 87 | + |
| 88 | +// US Dollars |
| 89 | +console.log(new Intl.NumberFormat('en-US', { |
| 90 | + style: 'currency', |
| 91 | + currency: 'USD' |
| 92 | +}).format(price)); // Output: $99.95 |
| 93 | + |
| 94 | +// Euros for a German customer |
| 95 | +console.log(new Intl.NumberFormat('de-DE', { |
| 96 | + style: 'currency', |
| 97 | + currency: 'EUR' |
| 98 | +}).format(price)); // Output: 99,95 € |
| 99 | + |
| 100 | +// Euros for an Irish customer |
| 101 | +console.log(new Intl.NumberFormat('en-IE', { |
| 102 | + style: 'currency', |
| 103 | + currency: 'EUR' |
| 104 | +}).format(price)); // Output: €99.95 |
| 105 | +``` |
| 106 | + |
| 107 | +#### Unit and Compact Formatting |
| 108 | + |
| 109 | +The API also supports unit formatting and compact notation for large numbers. |
| 110 | + |
| 111 | +```javascript |
| 112 | +// Unit formatting |
| 113 | +console.log(new Intl.NumberFormat('en-GB', { |
| 114 | + style: 'unit', |
| 115 | + unit: 'kilometer-per-hour' |
| 116 | +}).format(100)); // Output: 100 km/h |
| 117 | + |
| 118 | +// Compact notation |
| 119 | +console.log(new Intl.NumberFormat('en-US', { |
| 120 | + notation: 'compact', |
| 121 | + compactDisplay: 'short' |
| 122 | +}).format(2500000)); // Output: 2.5M |
| 123 | +``` |
| 124 | + |
| 125 | +### 3. Locale-Aware Sorting with `Intl.Collator` |
| 126 | + |
| 127 | +If you've ever tried to sort an array of strings in a language with accents, you know that JavaScript's default `Array.prototype.sort()` can fail. It sorts based on [code points](https://www.w3.org/TR/i18n-glossary/#dfn-code-point), which often leads to incorrect alphabetical order. |
| 128 | + |
| 129 | +`Intl.Collator` provides a locale-sensitive string comparison function. |
| 130 | + |
| 131 | +```javascript |
| 132 | +const names = ['Émilie', 'Zoe', 'Elodie', 'Stéphane']; |
| 133 | + |
| 134 | +// Default sort (incorrect for French) |
| 135 | +console.log([...names].sort()); |
| 136 | +// Output: [ 'Elodie', 'Stéphane', 'Zoe', 'Émilie' ] |
| 137 | + |
| 138 | +// Using Intl.Collator for French |
| 139 | +const collator = new Intl.Collator('fr'); |
| 140 | +console.log(names.sort(collator.compare)); |
| 141 | +// Output: [ 'Elodie', 'Émilie', 'Stéphane', 'Zoe' ] |
| 142 | +``` |
| 143 | + |
| 144 | +You can even use options for case-insensitive sorting or to correctly sort strings containing numbers (like "Chapter 2" vs. "Chapter 10"). |
| 145 | + |
| 146 | +```javascript |
| 147 | +const files = ['item 10', 'item 2']; |
| 148 | +const numericCollator = new Intl.Collator(undefined, { numeric: true }); |
| 149 | +console.log(files.sort(numericCollator.compare)); |
| 150 | +// Output: [ 'item 2', 'item 10' ] |
| 151 | +``` |
| 152 | + |
| 153 | +In this example, by using `undefined`, we are effectively saying: "I don't want to force a specific locale for sorting. Please use the user's default locale, but make sure to apply the `numeric: true` option to it." |
| 154 | + |
| 155 | +This makes the code robust and user-friendly. It adapts to the user automatically, providing locale-correct sorting while still giving us the specific sorting behavior (numeric) that we need. |
| 156 | + |
| 157 | +### 4. Relative Time (`Intl.RelativeTimeFormat`) |
| 158 | + |
| 159 | +This API is perfect for creating human-readable strings like "2 days ago" or "in 3 months". |
| 160 | + |
| 161 | +```javascript |
| 162 | +const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' }); |
| 163 | + |
| 164 | +console.log(rtf.format(-1, 'day')); // "yesterday" |
| 165 | +console.log(rtf.format(2, 'week')); // "in 2 weeks" |
| 166 | +console.log(rtf.format(3, 'month')); // "in 3 months" |
| 167 | + |
| 168 | +const rtf_es = new Intl.RelativeTimeFormat('es'); |
| 169 | +console.log(rtf_es.format(-1, 'day')); // "hace 1 día" |
| 170 | +``` |
| 171 | + |
| 172 | +The `numeric` option in `Intl.RelativeTimeFormat` can have two values: |
| 173 | + |
| 174 | +1. `always` (default): Always use a number. |
| 175 | +2. `auto`: Use a word (like "yesterday" or "tomorrow") if the locale has a special term for that relative time. Otherwise, fall back to using a number. |
| 176 | + |
| 177 | +### By the way |
| 178 | + |
| 179 | +This article only scratches the surface. The `Intl` API also includes `Intl.PluralRules` (for plural-sensitive formatting), `Intl.ListFormat` (for "A, B, and C"), `Intl.DisplayNames` (for translating region or language names), and more. |
| 180 | + |
| 181 | +By embracing the ECMAScript Internationalization API, you move localization logic from bulky libraries into the browser's native engine. You write less code and provide a more correct and performant experience for users worldwide. |
| 182 | + |
| 183 | +## Further reading |
| 184 | + |
| 185 | +- [BCP 47](https://www.rfc-editor.org/info/bcp47) |
| 186 | +- [The Intl Object](https://tc39.es/ecma402/#intl-object) |
0 commit comments