How to check if a locale identifier is valid

Validate locale identifiers before using them to format dates, numbers, and currencies

Introduction

When your application accepts locale identifiers from user input, API responses, or configuration files, you need to verify they are valid before using them. An invalid locale identifier will cause formatting functions to throw errors or produce unexpected results.

Validation ensures that strings like en-US are structurally correct according to international standards. This prevents runtime errors when passing locale identifiers to the Intl API or other internationalization libraries.

JavaScript provides two built-in methods to validate locale identifiers. Both methods check the structure of the identifier against the BCP 47 standard, which defines the format for language tags.

What makes a locale identifier valid

A locale identifier follows the BCP 47 standard for language tags. This standard defines the structure and rules for combining language, script, region, and extension components.

Valid locale identifiers use hyphens to separate components, not underscores or other characters. The language code must be a recognized ISO 639 code, and region codes must be recognized ISO 3166-1 codes.

Examples of valid locale identifiers:

  • en (English)
  • en-US (American English)
  • zh-Hans (Simplified Chinese)
  • zh-Hans-CN (Simplified Chinese as used in China)
  • en-US-u-ca-gregory (American English with Gregorian calendar)

Examples of invalid locale identifiers:

  • en_US (uses underscore instead of hyphen)
  • english (not a recognized language code)
  • en-USA (region code must be two letters, not three)
  • EN-us (language code must be lowercase)
  • abc-XY (language code does not exist)

The validation methods check these structural rules and verify that the codes are recognized standards.

Using Intl.getCanonicalLocales to validate

The Intl.getCanonicalLocales() method validates locale identifiers and returns them in their canonical form. This method accepts a single locale identifier or an array of identifiers.

const result = Intl.getCanonicalLocales("en-US");
console.log(result);
// Output: ["en-US"]

The method normalizes the input by converting it to the standard format. Even if you pass a locale identifier with incorrect casing, it returns the correct canonical form:

const result = Intl.getCanonicalLocales("EN-us");
console.log(result);
// Output: ["en-US"]

When you pass an invalid locale identifier, the method throws a RangeError:

try {
  Intl.getCanonicalLocales("en_US");
} catch (error) {
  console.error(error.name);
  // Output: "RangeError"
  console.error(error.message);
  // Output: "en_US is not a structurally valid language tag"
}

This error indicates the identifier does not follow BCP 47 structure. You can catch this error to handle invalid input gracefully.

Validating multiple locale identifiers

The Intl.getCanonicalLocales() method accepts an array to validate multiple locale identifiers at once. The method returns an array of canonical forms for all valid identifiers.

const locales = ["en-US", "fr-FR", "es-MX"];
const result = Intl.getCanonicalLocales(locales);
console.log(result);
// Output: ["en-US", "fr-FR", "es-MX"]

The method also removes duplicate locale identifiers from the result:

const locales = ["en-US", "EN-us", "en-US"];
const result = Intl.getCanonicalLocales(locales);
console.log(result);
// Output: ["en-US"]

All three input values represent the same locale, so the method returns only one canonical form.

If any locale in the array is invalid, the entire method throws an error:

try {
  Intl.getCanonicalLocales(["en-US", "invalid", "fr-FR"]);
} catch (error) {
  console.error(error.message);
  // Output: "invalid is not a structurally valid language tag"
}

When validating user-provided lists, validate each locale individually to identify which specific identifiers are invalid.

Using Intl.Locale constructor to validate

The Intl.Locale constructor provides another way to validate locale identifiers. When you create a locale object with an invalid identifier, the constructor throws a RangeError.

try {
  const locale = new Intl.Locale("en-US");
  console.log("Valid locale");
} catch (error) {
  console.error("Invalid locale");
}
// Output: "Valid locale"

The constructor throws an error for invalid identifiers:

try {
  const locale = new Intl.Locale("en_US");
  console.log("Valid locale");
} catch (error) {
  console.error("Invalid locale");
  console.error(error.message);
}
// Output: "Invalid locale"
// Output: "invalid language subtag: en_US"

Unlike Intl.getCanonicalLocales(), the Intl.Locale constructor does not normalize casing. It requires the identifier to use correct casing:

const locale1 = new Intl.Locale("en-US");
console.log(locale1.baseName);
// Output: "en-US"

const locale2 = new Intl.Locale("EN-US");
console.log(locale2.baseName);
// Output: "en-US"

Both uppercase and lowercase language codes are accepted, and the constructor normalizes them to the canonical form.

Choosing a validation method

Both validation methods serve different purposes and provide different features.

Use Intl.getCanonicalLocales() when you need to:

  • Normalize locale identifiers to their canonical form
  • Validate and deduplicate a list of locale identifiers
  • Convert inconsistent casing to the standard format
  • Validate locale identifiers without creating objects

Use the Intl.Locale constructor when you need to:

  • Validate a locale identifier and immediately work with its properties
  • Extract components like language, region, or script from the identifier
  • Create a locale object for use with other Intl APIs
  • Validate and manipulate locale identifiers together

The Intl.Locale constructor is more powerful because it creates an object you can query and modify. However, if you only need validation and normalization, Intl.getCanonicalLocales() is simpler.

Validating user input

When accepting locale identifiers from user input, validate them before using them in your application. This prevents errors and provides clear feedback when users enter invalid values.

function validateUserLocale(input) {
  try {
    const canonicalLocales = Intl.getCanonicalLocales(input);
    return {
      valid: true,
      locale: canonicalLocales[0]
    };
  } catch (error) {
    return {
      valid: false,
      error: "Please enter a valid locale identifier like en-US or fr-FR"
    };
  }
}

const result1 = validateUserLocale("en-US");
console.log(result1);
// Output: { valid: true, locale: "en-US" }

const result2 = validateUserLocale("english");
console.log(result2);
// Output: { valid: false, error: "Please enter a valid locale..." }

This function validates the input and returns either the canonical locale or a user-friendly error message.

Validating configuration data

Applications often load locale identifiers from configuration files or environment variables. Validate these values when your application starts to catch configuration errors early.

function loadLocaleConfig(configLocales) {
  const validLocales = [];
  const invalidLocales = [];

  for (const locale of configLocales) {
    try {
      const canonical = Intl.getCanonicalLocales(locale);
      validLocales.push(canonical[0]);
    } catch (error) {
      invalidLocales.push(locale);
    }
  }

  if (invalidLocales.length > 0) {
    console.warn("Invalid locale identifiers found:", invalidLocales);
  }

  return validLocales;
}

const config = ["en-US", "fr-FR", "invalid", "es_MX", "de-DE"];
const locales = loadLocaleConfig(config);
console.log(locales);
// Output: ["en-US", "fr-FR", "de-DE"]
// Warning: Invalid locale identifiers found: ["invalid", "es_MX"]

This function filters out invalid locale identifiers and logs a warning, allowing the application to continue with the valid locales.

Validating API responses

When receiving locale identifiers from external APIs, validate them before using them in your application. APIs might return locale identifiers in non-standard formats or with errors.

function processApiLocale(apiResponse) {
  const locale = apiResponse.preferredLocale;

  try {
    const validated = new Intl.Locale(locale);
    return {
      success: true,
      language: validated.language,
      region: validated.region,
      baseName: validated.baseName
    };
  } catch (error) {
    console.error("API returned invalid locale:", locale);
    return {
      success: false,
      fallback: "en-US"
    };
  }
}

const response1 = { preferredLocale: "fr-CA" };
console.log(processApiLocale(response1));
// Output: { success: true, language: "fr", region: "CA", baseName: "fr-CA" }

const response2 = { preferredLocale: "invalid_locale" };
console.log(processApiLocale(response2));
// Output: { success: false, fallback: "en-US" }

This function validates the API response and extracts structured information about the locale, or provides a fallback value if the locale is invalid.