cf.completefrontendCode editorOpen lab
THE JAVASCRIPT FIELD GUIDE

Internationalization with Intl

Format numbers, dates, lists, plurals, display names, and locale-aware labels with JavaScript’s Intl APIs without guessing a reader’s conventions.

By the end, you can
  • 01
    Choose a localeRead locale tags with Intl.Locale and canonicalize user input safely.
  • 02
    Format real dataUse NumberFormat, DateTimeFormat, RelativeTimeFormat, ListFormat, PluralRules, and DisplayNames.
  • 03
    Write deterministic UIFix locales, options, dates, and time zones in tests while computing reader-locale demos on the client.

Same data, many audiences

INTERACTIVE

Internationalization, often shortened to i18n, is the work of making an interface fit more than one language, region, script, calendar, numbering system, and writing convention. JavaScript’s Intl namespace gives you built-in formatters for the pieces that should not be hand-written: numbers, currency, dates, relative time, lists, plural categories, and names for codes.

Real-life analogyIntl is a team of local translators

Imagine handing the same invoice to several local translators. You do not change the amount owed. You tell each translator who will read it and whether it should look like currency, a percent, a date, or a list. Intl is that formatting team.

In real life: Tell the translator the audience
In JavaScript: Pass a locale such as en-IN, hi-IN, or ja-JP
In real life: Tell them the style
In JavaScript: Pass options such as style: "currency"
In real life: Hire once for repeated labels
In JavaScript: Reuse a formatter instead of recreating it in a loop

Where the analogy stops: Intl formats structured data; it does not translate whole paragraphs, product copy, or legal text for you.

The playground uses one number, two currencies, one fixed date, one relative offset, one list, and plural categories. Pick a locale and notice that the data stays the same while the presentation changes. This is computed in the browser after mount; the server-rendered page does not guess the reader’s default locale.

Same data, many audiences
Audience formatting codePop out in the code editor (opens in a new tab)JavaScript
const locale = "en-IN";const number = 1234567.891;const date = new Date("2026-01-15T13:30:00Z");const items = ["mangoes", "bananas", "guavas"]; new Intl.NumberFormat(locale).format(number);new Intl.NumberFormat(locale, { style: "currency", currency: "INR" }).format(number);new Intl.DateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short", timeZone: "UTC" }).format(date);new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(-3, "day");new Intl.ListFormat(locale, { type: "conjunction" }).format(items);
Output for en-INfixed data
number12,34,567.891
inr₹12,34,567.89
usd$12,34,567.89
percent12.3%
compact1.2K
date15 Jan 2026, 1:30 pm
relative3 days ago
listmangoes, bananas and guavas
plurals1 → one, 2 → other, 5 → other
Try it yourself
Locale:

The browser computes these examples after mount so server rendering never guesses a reader's locale.

Every row is produced by the real Intl APIs in your browser with explicit dates and options.

Locales & Intl.Locale

STEP THROUGH

A locale tag is an address for an audience. hi-IN says Hindi as used in India. en-Latn-IN says English, Latin script, India. Unicode extension keys can add preferences such as calendars: ja-JP-u-ca-japanese asks for Japanese as used in Japan with the Japanese calendar.

Real-life analogyA locale tag is an address

An address gets a letter to the right audience. A locale tag gets text into the right conventions. The tag is compact, but it carries enough information for ICU data to make good formatting choices.

In real life: Country and city on an envelope
In JavaScript: Region and language in a locale tag
In real life: Apartment details
In JavaScript: Optional script, calendar, numbering system, or collation extensions
In real life: A postal service normalizes the address
In JavaScript: getCanonicalLocales() normalizes casing and aliases

Where the analogy stops: Locales are preferences, not identity. A person may speak several languages or choose a locale that differs from where they live.

Read a locale tag
Step 0 of 7Ready
Your turn: follow the blue line

Step through a locale tag as an address: language, script, region, and supported data.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
console.log(locale.language, locale.script, locale.region); const zh = new Intl.Locale("zh");console.log(zh.maximize().toString());console.log(zh.maximize().minimize().toString()); console.log(Intl.getCanonicalLocales(["EN-in"])[0]);console.log(Intl.supportedValuesOf("currency").length > 0);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Intl.Locale is useful when you need to inspect or adjust a tag before formatting. maximize() and minimize() use likely-subtag data, so their results come from ICU, not from a rule you should copy by hand.

Intl.NumberFormat

STEP THROUGH

Never store a price as “₹1,234.50”. Store the amount as a number or decimal-friendly domain value, then format it for humans at the edge of your UI. Intl.NumberFormat covers decimal numbers, currency, percents, units, compact notation, scientific notation, signs, digit counts, rounding, and formatToParts().

Format numbers and parts
Step 0 of 5Ready
Your turn: follow the blue line

Watch one raw number become decimal text, currency text, unit text, and parts you can style.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
 new Intl.NumberFormat("en-IN").format(amount);new Intl.NumberFormat("en-IN", {  style: "currency",  currency: "INR",}).format(amount);new Intl.NumberFormat("en-IN", {  style: "unit",  unit: "kilometer-per-hour",  unitDisplay: "long",}).format(88);new Intl.NumberFormat("en-IN", {  style: "currency",  currency: "INR",}).formatToParts(1234.5);
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

Currency formatting needs both a locale and a currency code because the locale and the currency are different decisions. A German page can show Japanese yen, and a Japanese page can show euros. For custom visual emphasis, use formatToParts() instead of parsing punctuation yourself.

Intl.DateTimeFormat & RelativeTimeFormat

STEP THROUGH

DateTimeFormat formats instants and calendar values for an audience. In examples and tests, always pass explicit locales and a timeZone; otherwise the same instant may render as a different day or clock time on another machine. The earlier Date & time lesson introduced toLocaleString; here you are using the formatter directly so you can reuse it and specify exact options.

Format fixed dates and relative time
Step 0 of 6Ready
Your turn: follow the blue line

Dates and relative times are also audience-specific, so fix timeZone in examples and tests.

Running in
  1. script
Next: line 1
Click the blue line to take the next stepPop out in the code editor (opens in a new tab)JavaScript
const ended = new Date("2026-01-18T13:30:00Z"); new Intl.DateTimeFormat("en-IN", {  dateStyle: "medium",  timeStyle: "short",  timeZone: "Asia/Kolkata",}).format(started);new Intl.DateTimeFormat("en-IN", {  month: "short",  day: "numeric",  timeZone: "Asia/Kolkata",}).formatRange(started, ended);new Intl.RelativeTimeFormat("en-IN", { numeric: "auto" }).format(-1, "day");new Intl.RelativeTimeFormat("en-IN", { numeric: "always" }).format(-1, "day");
CallStoreChangeResultRun = next line. Ran = already executed.
Recent returnsNothing yet. Start with the blue line.
A guided replay recorded from real JavaScript calls, not an engine debugger. Step follows executed statements; Back reviews a snapshot. Reset starts a fresh run.

RelativeTimeFormat receives a signed number and a unit. Negative values point to the past; positive values point to the future. With numeric: "auto", a locale may use words such as yesterday. With numeric: "always", it stays numeric: 1 day ago.

Intl.PluralRules & ListFormat

PluralRules does not write a sentence. It chooses a category for a number in a locale, and your message table supplies the words. English cardinal plurals mostly use one and other; Arabic has more categories. For ar-EG, a few checked values are: 0:zero, 1:one, 2:two, 3:few, 11:many, 100:other.

Ordinal rules are separate. An English helper can map one to st, two to nd, few to rd, and other to th. That gives 1st, 2nd, 3rd, 4th, and 22nd.

ListFormat joins complete items. Indian English conjunction style gives Asha, Ravi and Meera; other locales may use different punctuation or words. Use type: "disjunction" for “or” lists.

Intl.DisplayNames & Intl.DurationFormat

FEATURE DETECT

DisplayNames turns standardized codes into localized labels. Verified examples: US as a French region is États-Unis, de as a language in Japanese is ドイツ語, and JPY as an English currency is Japanese Yen.

DurationFormat availability

Intl.DurationFormat is newer and is not available in Node 22. Browser support can differ, so production code should feature-detect before using it. The playground below shows a duration only if the current browser actually implements the API.

Feature-detect DurationFormat
DurationFormat feature detectionPop out in the code editor (opens in a new tab)JavaScript
if ("DurationFormat" in Intl) {  const formatter = new Intl.DurationFormat("en-IN", { style: "long" });  formatter.format({ hours: 3, minutes: 15 });} else {  "Intl.DurationFormat is not available in this runtime yet.";}
Browser result

Press Check support to ask this browser.

Try it yourself

Press Check support to ask this browser.

Node 22 does not expose Intl.DurationFormat. Browsers may differ, so the code checks first.

getCanonicalLocales & supportedValuesOf

Intl.getCanonicalLocales(["EN-in"]) returns ["en-IN"]. Use it to validate and normalize user-entered locale tags before saving preferences. Intl.supportedValuesOf("currency") returns supported currency codes; supportedValuesOf("calendar"), "unit", "numberingSystem", and "timeZone" are also useful when building settings UIs. Check membership or that the list is non-empty; do not hard-code counts because ICU data changes.

Which Intl constructor fits?
APIPurposeExample
Intl.LocaleParse and adjust locale tagsnew Intl.Locale("en-IN").region → IN
Intl.NumberFormatFormat decimals, currency, percents, units, notation, signs, and partsen-IN currency → ₹1,234.50
Intl.DateTimeFormatFormat dates, times, ranges, calendars, and time zonestimeZone: "UTC" keeps tests stable
Intl.RelativeTimeFormatSay yesterday, 3 days ago, or localized equivalentsnumeric: "auto" can use words
Intl.PluralRulesChoose plural categories for text you writeEnglish has one/other; Arabic has more
Intl.ListFormatJoin lists with localized conjunctions or disjunctionsA, B, and C vs localized punctuation
Intl.DisplayNamesShow names for regions, languages, currencies, scripts, and moreUS in French → États-Unis
Intl.DurationFormatFormat duration-like records when supportedFeature-detect: not in Node 22

Where you’ll use this

SORTER

Intl belongs wherever raw data becomes reader-facing text: invoices, dashboards, product cards, travel calendars, analytics summaries, file timestamps, preference screens, and accessibility labels. It also builds on earlier text lessons: use Comparing & sorting text for Intl.Collator, and remember the Unicode lesson’s Intl.Segmenter when splitting user-visible text.

Which Intl API?
  • Show 1234.5 as rupees for India
  • Show 88 as kilometers per hour
  • Show a meeting in the Kolkata time zone
  • Say 3 hours ago
  • Choose whether to say item or items
  • Join Asha, Ravi, and Meera naturally
  • Show US as a country name in French
  • Turn EN-in into en-IN
  • Check which currency codes this engine knows
Try it yourself
0 of 9 correct

Sort each job by the Intl tool that should handle it.

Choose a category for every card. You can change an answer at any time; Reset clears them all.
Testing Intl without flaky output

Use explicit locales, options, fixed dates, and fixed time zones. ICU data versions can change spaces, especially non-breaking spaces in times and currencies, so tests may normalize U+00A0 and U+202F when spacing is not the point.

Common misconceptions

“Intl translates my whole app.”

No. Intl formats structured data and some standardized names. You still need translated messages for sentences.

“The browser default locale is fine everywhere.”

Defaults are convenient for quick UI, but examples, SSR, and tests need explicit locales and options.

“A locale and a country are the same thing.”

A locale is a preference bundle. It may include language, script, region, calendar, numbering system, and more.

“Formatted numbers are safe to parse later.”

Formatted text is for humans. Keep raw data separately; use parts for styling, not parsing.

“All Intl APIs exist everywhere.”

Most APIs in this lesson are in Node 22 with full ICU. Intl.DurationFormat is not, so feature-detect it.

Practice: format for real audiences

5 EXERCISES
Exercise 1 · Warm-upFormat two prices

Run both lines. Check the Indian rupee answer here, then compare the Japanese yen output yourself.

Starter codePop out in the code editor (opens in a new tab)JavaScript
console.log(new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(1234.5));
console.log(new Intl.NumberFormat("ja-JP", { style: "currency", currency: "JPY" }).format(1234.5));

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · PracticeBuild an ordinal suffix helper

    Use ordinal plural rules to return the right suffix for 22.

    Starter codePop out in the code editor (opens in a new tab)JavaScript
    const rules = new Intl.PluralRules("en-IN", { type: "ordinal" });
    const suffixes = { one: "st", two: "nd", few: "rd", other: "th" };
    const n = 22;
    console.log(String(n) + (suffixes[rules.select(n)] ?? "th"));

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeWrite 3 hours ago

      Use RelativeTimeFormat with explicit en-IN and numeric: "always".

      Starter codePop out in the code editor (opens in a new tab)JavaScript
      console.log(new Intl.RelativeTimeFormat("en-IN", { numeric: "always" }).format(-3, "hour"));

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFormat a list of names

        Join Asha, Ravi, and Meera for Indian English readers.

        Starter codePop out in the code editor (opens in a new tab)JavaScript
        console.log(new Intl.ListFormat("en-IN", { type: "conjunction" }).format(["Asha", "Ravi", "Meera"]));

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeShow a country in another language

          Use DisplayNames to show the United States in French.

          Starter codePop out in the code editor (opens in a new tab)JavaScript
          console.log(new Intl.DisplayNames(["fr"], { type: "region" }).of("US"));

          Answer, then press Check. Spacing and letter case don’t matter.

            Quiz: check your understanding

            7 QUESTIONS
            Lesson quiz · 7 questionsScore: first tries count
            1. Question 1 of 7What two things do most Intl formatters need?

              Choose an answer to see the explanation.

            2. Question 2 of 7What does DisplayNames print here?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(Intl.getCanonicalLocales(["EN-in"])[0]);

              Choose an answer to see the explanation.

            3. Question 3 of 7What does this print in Node 22 with full ICU?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(new Intl.RelativeTimeFormat("en-IN", { numeric: "auto" }).format(-1, "day"));

              Choose an answer to see the explanation.

            4. Question 4 of 7Which API should choose one, few, many, or other before you pick words?

              Choose an answer to see the explanation.

            5. Question 5 of 7What does this print?

              Read the code, then predictPop out in the code editor (opens in a new tab)JavaScript
              console.log(new Intl.DisplayNames(["fr"], { type: "region" }).of("US"));

              Choose an answer to see the explanation.

            6. Question 6 of 7Why should tests pass explicit timeZone to DateTimeFormat?

              Choose an answer to see the explanation.

            7. Question 7 of 7What should you do before using Intl.DurationFormat in this lesson’s environment?

              Choose an answer to see the explanation.

            Key takeaways

            • Intl formats structured data for a locale; it does not translate full messages.
            • Think audience plus style: locale tags name the audience, options name the style.
            • Reuse formatters when formatting many values with the same locale and options.
            • Pin locales, options, dates, and time zones in tests; normalize special spaces only when appropriate.
            • Feature-detect newer APIs such as Intl.DurationFormat.

            Remember the one-liner.
            Intl is JavaScript’s built-in formatting team: give it a locale and options, and it writes human-facing text for that audience.

            Up next: The JavaScript runtime at a glance.

            CompleteFrontend Clear concepts. Working examples.