Skip to content

Commit 9e5428d

Browse files
authored
Add a JavaScript Internationalization article (#681)
1 parent e3eb806 commit 9e5428d

File tree

2 files changed

+281
-0
lines changed

2 files changed

+281
-0
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
var trans = { }
2+
3+
trans.versions = ['en']
4+
5+
trans.outofdatetranslations = []
6+
7+
trans.updatedtranslations = [];
8+
9+
trans.unlinkedtranslations = []

articles/intl/index.en.html

Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Guide to the ECMAScript Internationalization API</title>
6+
<meta name="description" content="Learn how to use the ECMAScript Internationalization API (Intl object) to format dates, numbers, currencies, and more for global audiences." />
7+
<script>
8+
var f = { }
9+
10+
// AUTHORS should fill in these assignments:
11+
f.directory = 'articles/intl'+'/'; // the name of the directory this file is in
12+
f.filename = 'index'; // the file name WITHOUT extensions
13+
f.authors = 'Fuqiao Xue, W3C'; // author(s) and affiliations
14+
f.previousauthors = ''; // as above
15+
f.modifiers = ''; // people making substantive changes, and their affiliation
16+
f.searchString = 'article-intl'; // blog search string - usually the filename without extensions
17+
f.firstPubDate = '2025-08-22'; // date of the first publication of the document (after review)
18+
f.lastSubstUpdate = { date:'2025-08-22', time:'12:43'} // date and time of latest substantive changes to this document
19+
f.status = 'review'; // should be one of draft, review, published, or notreviewed
20+
f.path = '../../' // what you need to prepend to a URL to get to the /International directory
21+
22+
// AUTHORS AND TRANSLATORS should fill in these assignments:
23+
f.thisVersion = { date:'2025-08-22', time:'12:43'} // date and time of latest edits to this document/translation
24+
f.contributors = 'Richard Ishida'; // people providing useful contributions or feedback during review or at other times
25+
// also make sure that the lang attribute on the html tag is correct!
26+
27+
// TRANSLATORS should fill in these assignments:
28+
f.translators = 'xxxNAME, ORG'; // translator(s) and their affiliation - a elements allowed, but use double quotes for attributes
29+
30+
f.breadcrumb = 'cultural';
31+
</script>
32+
<script src="index-data/translations.js"> </script>
33+
<script src="../../javascript/doc-structure/article-dt.js"> </script>
34+
<script src="../../javascript/boilerplate-text/boilerplate-en.js"> </script>
35+
<!--TRANSLATORS must change -en in the line just above to the subtag for their language! -->
36+
<script src="../../javascript/doc-structure/article-2022.js"> </script>
37+
<script src="../../javascript/articletoc-2022.js"></script>
38+
<link rel="stylesheet" href="../../style/article-2022.css" />
39+
<link rel="copyright" href="#copyright"/>
40+
<script src="../../javascript/prism.js"></script>
41+
<link rel="stylesheet" href="../../style/prism.css">
42+
</head>
43+
44+
<body>
45+
<header>
46+
<nav id="mainNavigation"></nav><script>document.getElementById('mainNavigation').innerHTML = mainNavigation</script>
47+
48+
<h1>Guide to the ECMAScript Internationalization API</h1>
49+
</header>
50+
51+
52+
<div id="audience">
53+
<div id="updateInfo"></div><script>document.getElementById('updateInfo').innerHTML = g.updated</script>
54+
</div>
55+
56+
<p>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.</p>
57+
<p>Fortunately, modern browsers now have a built-in, standardized solution: the ECMAScript Internationalization API, available globally in JavaScript via the <code>Intl</code> 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.</p>
58+
<p>This article will serve as a practical overview of the most essential parts of the <code>Intl</code> API, providing actionable examples you can use to internationalize your web applications today.</p>
59+
<section id="the-core-concept-locales-and-options">
60+
<h2>The Core Concept: Locales and Options</h2>
61+
<p>Before diving into specific formatters, it's important to understand the two fundamental arguments that nearly every <code>Intl</code> constructor takes:</p>
62+
<ol>
63+
<li><strong><code>locales</code></strong>: A string representing a language tag (following the BCP 47 standard), such as <code>'en-US'</code> (American English), <code>'fr-FR'</code> (French in France), or simply <code>'ja'</code> (Japanese). You can also provide an array of locales, like <code>['fr-CA', 'fr-FR']</code>, and the browser will use the first one it supports. If omitted, the browser's default locale will be used.</li>
64+
<li><strong><code>options</code></strong>: 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.</li>
65+
</ol>
66+
</section>
67+
<section id="intl-datetimeformat">
68+
<h2>Formatting Dates and Times</h2>
69+
<p>One of the most common internationalization 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. <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat"><code>Intl.DateTimeFormat</code></a> solves this ambiguity effortlessly.</p>
70+
<p>The basic usage is simple. You create a formatter instance and then call its <code>.format()</code> method.</p>
71+
<figure class="example">
72+
<pre><code class="lang-javascript">const eventDate = new Date();
73+
74+
// For a user in the United States
75+
const usFormatter = new Intl.DateTimeFormat('en-US');
76+
console.log(usFormatter.format(eventDate));
77+
// Output: 10/26/2025
78+
79+
// For a user in Germany
80+
const deFormatter = new Intl.DateTimeFormat('de-DE');
81+
console.log(deFormatter.format(eventDate));
82+
// Output: 26.10.2025</code></pre>
83+
</figure>
84+
<p>Note that numeric dates are often problematic for readers, and it's much better to avoid the ambiguity by expanding the month. See how to do that in <a href="#fine-grained-control-with-options">Fine-Grained Control with Options</a>.</p>
85+
86+
<section id="current-date-vs-a-specific-date">
87+
<h3>Current Date vs a Specific Date</h3>
88+
<p>To display the current date, use <code>new Date()</code>:</p>
89+
<figure class="example">
90+
<pre><code class="lang-javascript">const fmt = new Intl.DateTimeFormat('en-GB', { dateStyle: 'long' });
91+
92+
// Current date (today)
93+
fmt.format(new Date()); // e.g., 26 October 2025</code></pre>
94+
</figure>
95+
<p>To display a specific date, use <code>new Date()</code> with parameters. Months are 0-based:</p>
96+
<figure class="example">
97+
<pre><code class="lang-javascript">// A specific calendar date: 27 June 2025 (local time)
98+
const june27Local = new Date(2025, 5, 27);
99+
fmt.format(june27Local); // 27 June 2025</code></pre>
100+
</figure>
101+
</section>
102+
103+
<section id="fine-grained-control-with-options">
104+
<h3>Fine-Grained Control with Options</h3>
105+
<p>You can achieve much more detailed and readable formats using the <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#options"><code>options</code> object</a>. The modern approach uses <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#datestyle"><code>dateStyle</code> and <code>timeStyle</code></a>.</p>
106+
<figure class="example">
107+
<pre><code class="lang-javascript">const options = {
108+
dateStyle: 'full',
109+
timeStyle: 'long',
110+
};
111+
112+
const formatter = new Intl.DateTimeFormat('es-ES', options);
113+
console.log(formatter.format(eventDate));
114+
// Output: viernes, 15 de agosto de 2025, 10:30:00 UTC</code></pre>
115+
</figure>
116+
<p>You can also specify time zones.</p>
117+
<figure class="example">
118+
<pre><code class="lang-javascript">const japanFormatter = new Intl.DateTimeFormat('ja-JP', {
119+
year: 'numeric',
120+
month: 'long',
121+
day: 'numeric',
122+
timeZone: 'Asia/Tokyo',
123+
});
124+
125+
console.log(japanFormatter.format(eventDate));
126+
// Sample Output: 2025年10月27日 (Note the date may change due to timezone)</code></pre>
127+
</figure>
128+
</section>
129+
</section>
130+
131+
<section id="intl-numberformat">
132+
<h2>Handling Numbers, Currencies, and Units</h2>
133+
<p>Numbers are formatted differently across the world. For example, the decimal separator can be a period or a comma. <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat"><code>Intl.NumberFormat</code></a> handles this seamlessly.</p>
134+
<figure class="example">
135+
<pre><code class="lang-javascript">const largeNumber = 1234567.89;
136+
137+
// United States
138+
console.log(new Intl.NumberFormat('en-US').format(largeNumber));
139+
// Output: 1,234,567.89
140+
141+
// Germany
142+
console.log(new Intl.NumberFormat('de-DE').format(largeNumber));
143+
// Output: 1.234.567,89
144+
145+
// India
146+
console.log(new Intl.NumberFormat('en-IN').format(largeNumber));
147+
// Output: 12,34,567.89
148+
149+
// Thailand using native Thai digits
150+
console.log(new Intl.NumberFormat('th-TH-u-nu-thai').format(largeNumber));
151+
// Output: ๑,๒๓๔,๕๖๗.๘๙</code></pre>
152+
</figure>
153+
154+
<section id="currency-formatting">
155+
<h3>Currency Formatting</h3>
156+
<p>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. <code>Intl.NumberFormat</code> requires the <code>style</code> to be <code>'currency'</code> and an ISO 4217 currency code.</p>
157+
<figure class="example">
158+
<pre><code class="lang-javascript">const price = 99.95;
159+
160+
// US Dollars
161+
console.log(new Intl.NumberFormat('en-US', {
162+
style: 'currency',
163+
currency: 'USD'
164+
}).format(price)); // Output: $99.95
165+
166+
// Euros for a German customer
167+
console.log(new Intl.NumberFormat('de-DE', {
168+
style: 'currency',
169+
currency: 'EUR'
170+
}).format(price)); // Output: 99,95 €
171+
172+
// Euros for an Irish customer
173+
console.log(new Intl.NumberFormat('en-IE', {
174+
style: 'currency',
175+
currency: 'EUR'
176+
}).format(price)); // Output: €99.95</code></pre>
177+
</figure>
178+
</section>
179+
180+
<section id="unit-and-compact-formatting">
181+
<h3>Unit and Compact Formatting</h3>
182+
<p>The API also supports unit formatting and compact notation for large numbers.</p>
183+
<figure class="example">
184+
<pre><code class="lang-javascript">// Unit formatting
185+
console.log(new Intl.NumberFormat('en-GB', {
186+
style: 'unit',
187+
unit: 'kilometer-per-hour'
188+
}).format(100)); // Output: 100 km/h
189+
190+
// Compact notation
191+
console.log(new Intl.NumberFormat('en-US', {
192+
notation: 'compact',
193+
compactDisplay: 'short'
194+
}).format(2500000)); // Output: 2.5M</code></pre>
195+
</figure>
196+
</section>
197+
</section>
198+
199+
<section id="intl-collator">
200+
<h2>Locale-Aware Sorting</h2>
201+
<p>If you've ever tried to sort an array of strings in a language with accents, you know that JavaScript's default <code>Array.prototype.sort()</code> can fail. It sorts based on <a href="https://www.w3.org/TR/i18n-glossary/#dfn-code-point">code points</a>, which often leads to incorrect alphabetical order.</p>
202+
<p><a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator"><code>Intl.Collator</code></a> provides a locale-sensitive string comparison function.</p>
203+
<figure class="example">
204+
<pre><code class="lang-javascript">const names = ['Émilie', 'Zoe', 'Elodie', 'Stéphane', 'Åsa', 'Örjan'];
205+
206+
// Default sort
207+
console.log([...names].sort());
208+
// Output: ['Elodie', 'Stéphane', 'Zoe', 'Åsa', 'Émilie', 'Örjan']
209+
210+
// Using Intl.Collator for French
211+
const frCollator = new Intl.Collator('fr');
212+
console.log(names.sort(frCollator.compare));
213+
// Output: ['Åsa', 'Elodie', 'Émilie', 'Örjan', 'Stéphane', 'Zoe']
214+
215+
// Using Intl.Collator for Swedish
216+
const svCollator = new Intl.Collator('sv');
217+
console.log(names.sort(svCollator.compare));
218+
// Output: ['Elodie', 'Émilie', 'Stéphane', 'Zoe', 'Åsa', 'Örjan']</code></pre>
219+
</figure>
220+
<p>You can even use <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator#options">options</a> for case-insensitive sorting or to correctly sort strings containing numbers (like "Chapter 2" vs. "Chapter 10").</p>
221+
<figure class="example">
222+
<pre><code class="lang-javascript">const files = ['item 10', 'item 2'];
223+
const numericCollator = new Intl.Collator(undefined, { numeric: true });
224+
console.log(files.sort(numericCollator.compare));
225+
// Output: [ 'item 2', 'item 10' ]</code></pre>
226+
</figure>
227+
<p>In this example, by using <code>undefined</code>, 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 <code>numeric: true</code> option to it."</p>
228+
<p>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.</p>
229+
</section>
230+
231+
<section id="intl-relativetimeformat">
232+
<h2>Relative Time</h2>
233+
<p><a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat"><code>Intl.RelativeTimeFormat</code></a> is perfect for creating human-readable strings like "2 days ago" or "in 3 months".</p>
234+
<figure class="example">
235+
<pre><code class="lang-javascript">const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
236+
237+
console.log(rtf.format(-1, 'day')); // "yesterday"
238+
console.log(rtf.format(2, 'week')); // "in 2 weeks"
239+
console.log(rtf.format(3, 'month')); // "in 3 months"
240+
241+
const rtf_es = new Intl.RelativeTimeFormat('es');
242+
console.log(rtf_es.format(-1, 'day')); // "hace 1 día"</code></pre>
243+
</figure>
244+
<p>The <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/RelativeTimeFormat/RelativeTimeFormat#numeric"><code>numeric</code> option</a> in <code>Intl.RelativeTimeFormat</code> can have two values:</p>
245+
<ol>
246+
<li><code>always</code> (default): Always use a number.</li>
247+
<li><code>auto</code>: 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.</li>
248+
</ol>
249+
</section>
250+
251+
<section id="bytheway">
252+
<h2>By the way</h2>
253+
<p>This article only scratches the surface. The <code>Intl</code> API also includes <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules"><code>Intl.PluralRules</code></a> (for plural-sensitive formatting), <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat"><code>Intl.ListFormat</code></a> (for "A, B, and C"), <a rel="nofollow" href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DisplayNames"><code>Intl.DisplayNames</code></a> (for translating region or language names), and more.</p>
254+
<p>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.</p>
255+
</section>
256+
257+
<section id="endlinks">
258+
<h2>Further reading</h2>
259+
<aside class="section" id="survey"> </aside><script>document.getElementById('survey').innerHTML = g.survey</script>
260+
261+
<ul id="full-links">
262+
<li><cite><a href="https://www.rfc-editor.org/info/bcp47">BCP 47</a></cite></li>
263+
<li><cite><a href="https://tc39.es/ecma402/#intl-object">The Intl Object in ECMA-402
264+
265+
</a></cite></li>
266+
</ul>
267+
</section>
268+
269+
<footer id="thefooter"></footer><script>document.getElementById('thefooter').innerHTML = g.bottomOfPage</script>
270+
<script>completePage()</script>
271+
</body>
272+
</html>

0 commit comments

Comments
 (0)