Intl.Locale API

Parse, manipulate, and query locale identifiers with structured JavaScript objects

Introduction

When building applications for multiple languages and regions, you encounter locale identifiers like en-US, fr-FR, or zh-Hans-CN. These strings appear in browser APIs, HTTP headers, and user preferences. They encode information about language, region, writing system, and formatting preferences.

The Intl.Locale API transforms these opaque strings into structured objects you can inspect and manipulate. Instead of parsing strings manually or guessing what zh-Hans-CN means, you create a locale object and read its properties directly.

This guide explains how locale identifiers work, how to use the Intl.Locale API to work with them, and when structured locale objects help solve real problems.

Understanding locale identifiers

A locale identifier is a string that specifies cultural preferences for formatting dates, numbers, currencies, and text. The identifier contains multiple components separated by hyphens.

The most common structure follows the BCP 47 standard:

language-script-region-variant-extensions

Each component is optional except the language code.

Language codes

Language codes use two or three letters from ISO 639. Common examples:

  • en for English
  • es for Spanish
  • fr for French
  • de for German
  • ja for Japanese
  • zh for Chinese
  • ar for Arabic

The language code is always lowercase and appears first in the identifier.

Region codes

Region codes specify geographic areas using two uppercase letters from ISO 3166-1. They indicate which variant of a language to use:

  • en-US for American English
  • en-GB for British English
  • es-ES for European Spanish
  • es-MX for Mexican Spanish
  • fr-FR for French as spoken in France
  • fr-CA for Canadian French

Region codes change formatting conventions. American English uses MM/DD/YYYY dates, while British English uses DD/MM/YYYY.

Script codes

Script codes specify the writing system using four letters with the first letter capitalized. They matter for languages written in multiple scripts:

  • zh-Hans for Simplified Chinese characters
  • zh-Hant for Traditional Chinese characters
  • sr-Cyrl for Serbian in Cyrillic script
  • sr-Latn for Serbian in Latin script

Most locales omit the script code because the language implies a default script. English uses Latin script by default, so you write en instead of en-Latn.

Extension tags

Extension tags add formatting preferences to locale identifiers. They start with -u- followed by key-value pairs:

en-US-u-ca-gregory-nu-latn-hc-h12

Common extension keys:

  • ca for calendar system (gregory, buddhist, islamic)
  • nu for numbering system (latn, arab, hanidec)
  • hc for hour cycle (h12, h23, h11, h24)

Extensions customize how formatters display data without changing the language or region.

Creating locale objects

The Intl.Locale constructor accepts a locale identifier string and returns a structured object:

const locale = new Intl.Locale("en-US");

console.log(locale.language); // "en"
console.log(locale.region); // "US"

You can also pass an options object to override or add properties:

const locale = new Intl.Locale("en", {
  region: "GB",
  hourCycle: "h23"
});

console.log(locale.baseName); // "en-GB"
console.log(locale.hourCycle); // "h23"

The constructor throws a RangeError if the identifier is invalid:

try {
  const invalid = new Intl.Locale("invalid-locale-code");
} catch (error) {
  console.error(error.message); // "invalid language subtag: invalid"
}

This validation ensures you catch malformed locale identifiers before passing them to formatters.

Reading locale properties

Locale objects expose properties that correspond to components of the identifier string. All properties are read-only.

Core properties

The language property returns the language code:

const locale = new Intl.Locale("fr-CA");
console.log(locale.language); // "fr"

The region property returns the region code:

const locale = new Intl.Locale("fr-CA");
console.log(locale.region); // "CA"

The script property returns the script code if present:

const locale = new Intl.Locale("zh-Hans-CN");
console.log(locale.script); // "Hans"

The baseName property returns the complete core identifier without extensions:

const locale = new Intl.Locale("en-US-u-ca-gregory-nu-latn");
console.log(locale.baseName); // "en-US"

Use baseName when you need the language and region but want to ignore formatting preferences.

Extension properties

Extension properties return values from the -u- extension tag or undefined if not specified.

The calendar property returns the calendar system:

const locale = new Intl.Locale("ar-SA-u-ca-islamic");
console.log(locale.calendar); // "islamic"

The numberingSystem property returns the numbering system:

const locale = new Intl.Locale("ar-EG-u-nu-arab");
console.log(locale.numberingSystem); // "arab"

The hourCycle property returns the hour cycle preference:

const locale = new Intl.Locale("en-US-u-hc-h23");
console.log(locale.hourCycle); // "h23"

The caseFirst property returns the collation case preference:

const locale = new Intl.Locale("en-US-u-kf-upper");
console.log(locale.caseFirst); // "upper"

The numeric property indicates whether numeric collation is enabled:

const locale = new Intl.Locale("en-US-u-kn-true");
console.log(locale.numeric); // true

These properties let you inspect formatting preferences without parsing the extension string manually.

Querying locale information

The Intl.Locale API provides methods that return arrays of available options for a locale. These methods help build user interfaces and validate formatting choices.

Available calendars

The getCalendars() method returns calendar systems commonly used for the locale:

const locale = new Intl.Locale("th-TH");
const calendars = locale.getCalendars();
console.log(calendars); // ["buddhist", "gregory"]

The first element is the default calendar. Thai locales default to the Buddhist calendar but also use the Gregorian calendar.

Available collations

The getCollations() method returns collation types for sorting strings:

const locale = new Intl.Locale("de-DE");
const collations = locale.getCollations();
console.log(collations); // ["phonebk", "emoji", "eor"]

German has a phonebook collation that sorts strings differently than standard Unicode collation.

Available hour cycles

The getHourCycles() method returns hour cycle formats:

const locale = new Intl.Locale("en-US");
const hourCycles = locale.getHourCycles();
console.log(hourCycles); // ["h12"]

American English uses 12-hour time by default. Many other locales return ["h23"] for 24-hour time.

Available numbering systems

The getNumberingSystems() method returns numbering systems commonly used for the locale:

const locale = new Intl.Locale("ar-EG");
const numberingSystems = locale.getNumberingSystems();
console.log(numberingSystems); // ["arab", "latn"]

Arabic locales often support both Arabic-Indic numerals and Latin numerals.

Text direction

The getTextInfo() method returns text ordering information:

const locale = new Intl.Locale("ar-SA");
const textInfo = locale.getTextInfo();
console.log(textInfo.direction); // "rtl"

Right-to-left languages like Arabic and Hebrew return "rtl". Left-to-right languages return "ltr".

Week conventions

The getWeekInfo() method returns week structure for the locale:

const locale = new Intl.Locale("en-US");
const weekInfo = locale.getWeekInfo();
console.log(weekInfo.firstDay); // 7 (Sunday)
console.log(weekInfo.weekend); // [6, 7] (Saturday, Sunday)
console.log(weekInfo.minimalDays); // 1

Week conventions vary by region. American weeks start on Sunday, while most European weeks start on Monday.

Time zones

The getTimeZones() method returns time zones for the locale's region:

const locale = new Intl.Locale("en-US");
const timeZones = locale.getTimeZones();
console.log(timeZones); // ["America/New_York", "America/Chicago", ...]

This method returns IANA time zone identifiers for the region code.

Maximizing locale identifiers

The maximize() method adds likely subtags to create a complete identifier. It infers missing script and region codes based on language data.

When you specify only a language code, maximize() adds the most common script and region:

const locale = new Intl.Locale("en");
const maximized = locale.maximize();
console.log(maximized.baseName); // "en-Latn-US"

English defaults to Latin script and the United States region because those are the most common associations.

Chinese maximization depends on the script:

const simplified = new Intl.Locale("zh-Hans");
const maximized = simplified.maximize();
console.log(maximized.baseName); // "zh-Hans-CN"

const traditional = new Intl.Locale("zh-Hant");
const maximizedTrad = traditional.maximize();
console.log(maximizedTrad.baseName); // "zh-Hant-TW"

Simplified Chinese associates with China, while Traditional Chinese associates with Taiwan.

Use maximize() when normalizing user input or comparing locale identifiers. It makes implicit information explicit.

Minimizing locale identifiers

The minimize() method removes redundant subtags to create the shortest equivalent identifier. It removes script and region codes when they match the likely defaults.

When a locale uses the default script and region, minimize() removes them:

const locale = new Intl.Locale("en-Latn-US");
const minimized = locale.minimize();
console.log(minimized.baseName); // "en"

Latin script and United States region are defaults for English, so they can be removed without losing information.

For locales with non-default regions, minimize() keeps the region:

const locale = new Intl.Locale("en-Latn-GB");
const minimized = locale.minimize();
console.log(minimized.baseName); // "en-GB"

British English needs the region code because it differs from the default.

Use minimize() to create compact identifiers for storage or URLs while preserving meaning.

Converting to strings

The toString() method returns the complete locale identifier including extensions:

const locale = new Intl.Locale("en-US", {
  calendar: "gregory",
  numberingSystem: "latn",
  hourCycle: "h12"
});

console.log(locale.toString()); // "en-US-u-ca-gregory-hc-h12-nu-latn"

The string representation is valid for passing to other Intl APIs or storing as configuration data.

You can also convert locale objects to strings implicitly:

const locale = new Intl.Locale("fr-FR");
const formatter = new Intl.DateTimeFormat(locale);

The DateTimeFormat constructor accepts locale objects directly and calls toString() internally.

Practical use cases

The Intl.Locale API solves several common problems when building internationalized applications.

Building locale selectors

Locale selectors let users choose their language and region. The Intl.Locale API helps parse and validate user selections:

function createLocaleSelector(locales) {
  const options = locales.map(identifier => {
    const locale = new Intl.Locale(identifier);
    const displayName = new Intl.DisplayNames([identifier], { type: "language" });

    return {
      value: locale.toString(),
      label: displayName.of(locale.language),
      region: locale.region
    };
  });

  return options;
}

const options = createLocaleSelector(["en-US", "en-GB", "fr-FR", "es-MX"]);
console.log(options);
// [
//   { value: "en-US", label: "English", region: "US" },
//   { value: "en-GB", label: "English", region: "GB" },
//   { value: "fr-FR", label: "French", region: "FR" },
//   { value: "es-MX", label: "Spanish", region: "MX" }
// ]

Validating locale input

When accepting locale identifiers from user input or configuration files, validate them before use:

function validateLocale(identifier) {
  try {
    const locale = new Intl.Locale(identifier);
    return {
      valid: true,
      locale: locale.toString(),
      language: locale.language,
      region: locale.region
    };
  } catch (error) {
    return {
      valid: false,
      error: error.message
    };
  }
}

console.log(validateLocale("en-US")); // { valid: true, locale: "en-US", ... }
console.log(validateLocale("invalid")); // { valid: false, error: "..." }

Extracting language for fallbacks

When implementing language fallback chains, extract the language code without the region:

function getLanguageFallback(identifier) {
  const locale = new Intl.Locale(identifier);
  const fallbacks = [locale.toString()];

  if (locale.region) {
    const languageOnly = new Intl.Locale(locale.language);
    fallbacks.push(languageOnly.toString());
  }

  fallbacks.push("en");

  return fallbacks;
}

console.log(getLanguageFallback("fr-CA"));
// ["fr-CA", "fr", "en"]

This creates a fallback chain that tries the specific locale, then the language without region, then the default language.

Configuring formatters

Use locale objects to configure Intl formatters with specific preferences:

function createFormatter(baseLocale, options = {}) {
  const locale = new Intl.Locale(baseLocale, {
    calendar: options.calendar || "gregory",
    numberingSystem: options.numberingSystem || "latn",
    hourCycle: options.hourCycle || "h23"
  });

  return {
    date: new Intl.DateTimeFormat(locale),
    number: new Intl.NumberFormat(locale),
    locale: locale.toString()
  };
}

const formatter = createFormatter("ar-SA", {
  calendar: "islamic",
  numberingSystem: "arab"
});

console.log(formatter.locale); // "ar-SA-u-ca-islamic-nu-arab"

Detecting user preferences

Browser APIs provide locale strings that you can parse to understand user preferences:

function getUserPreferences() {
  const userLocale = new Intl.Locale(navigator.language);
  const hourCycles = userLocale.getHourCycles();
  const calendars = userLocale.getCalendars();
  const textInfo = userLocale.getTextInfo();

  return {
    language: userLocale.language,
    region: userLocale.region,
    preferredHourCycle: hourCycles[0],
    preferredCalendar: calendars[0],
    textDirection: textInfo.direction
  };
}

const preferences = getUserPreferences();
console.log(preferences);
// { language: "en", region: "US", preferredHourCycle: "h12", ... }

This creates a profile of user preferences based on their browser settings.

Browser support

The Intl.Locale API works in all modern browsers. Chrome, Firefox, Safari, and Edge provide full support for the constructor, properties, and methods described in this guide.

The newer methods like getCalendars(), getCollations(), getHourCycles(), getNumberingSystems(), getTextInfo(), getTimeZones(), and getWeekInfo() require recent browser versions. Check browser compatibility tables before using these methods if you support older browsers.

Node.js supports the Intl.Locale API starting from version 12, with full method support in version 18 and later.

Summary

The Intl.Locale API transforms locale identifier strings into structured objects you can inspect and manipulate. Instead of parsing strings manually, you create locale objects and read their properties.

Core concepts:

  • Locale identifiers contain language, script, region, and extension components
  • The Intl.Locale constructor validates identifiers and creates objects
  • Properties like language, region, and calendar provide structured access
  • Methods like getCalendars() and getHourCycles() return available options
  • The maximize() and minimize() methods normalize identifiers
  • Locale objects work directly with other Intl APIs

Use the Intl.Locale API when building locale selectors, validating user input, implementing fallback chains, or configuring formatters. It provides a standard way to work with locale identifiers in JavaScript applications.