How do I display language names like English, Español, 日本語?

Use Intl.DisplayNames to show language names in their native scripts for language selectors and internationalized interfaces.

Introduction

When you build a language selector or display a list of available languages, you need to show each language name in a way users can recognize. A French speaker looks for "Français", a Spanish speaker looks for "Español", and a Japanese speaker looks for "日本語". Users identify their language by its native script and spelling.

Hardcoding these translations does not scale. You would need to maintain translations of every language name into every other language. The Intl.DisplayNames API solves this problem by providing standardized, locale-aware names for languages, countries, scripts, and currencies.

The problem with hardcoding language names

You can create a language selector by hardcoding language names in an object.

const languageNames = {
  en: "English",
  es: "Spanish",
  fr: "French",
  de: "German",
  ja: "Japanese"
};

console.log(languageNames.en);
// "English"

This approach has three problems. First, these names only work for English speakers. Second, users cannot recognize their language when it appears in English. A Japanese user scanning for their language looks for Japanese characters, not the word "Japanese". Third, you need to manually maintain translations for every language into every other language.

const languageNames = {
  en: {
    en: "English",
    es: "Spanish",
    fr: "French",
    de: "German",
    ja: "Japanese"
  },
  es: {
    en: "Inglés",
    es: "Español",
    fr: "Francés",
    de: "Alemán",
    ja: "Japonés"
  }
  // ... more languages
};

This quickly becomes unmaintainable. You need a better solution.

Using Intl.DisplayNames to get language names

The Intl.DisplayNames constructor creates a formatter that converts language codes into human-readable names. You specify a locale and the type of names you want to display.

const names = new Intl.DisplayNames(["en"], { type: "language" });
console.log(names.of("en"));
// "English"

The first argument is an array of locale identifiers. The second argument is an options object where type: "language" tells the formatter you want language names. The of() method takes a language code and returns its name.

You can get names in any language by changing the locale.

const enNames = new Intl.DisplayNames(["en"], { type: "language" });
const esNames = new Intl.DisplayNames(["es"], { type: "language" });
const frNames = new Intl.DisplayNames(["fr"], { type: "language" });

console.log(enNames.of("es"));
// "Spanish"

console.log(esNames.of("es"));
// "español"

console.log(frNames.of("es"));
// "espagnol"

Each formatter returns the language name in its display locale. This handles all the complexity of maintaining language name translations.

Displaying language names in their native form

The best practice for language selectors is to display each language in its own script. Users recognize their language faster when they see it written in familiar characters.

const names = new Intl.DisplayNames(["ja"], { type: "language" });
console.log(names.of("ja"));
// "日本語"

To get the native name for each language, create a formatter using the target language as the display locale.

function getNativeName(languageCode) {
  const names = new Intl.DisplayNames([languageCode], { type: "language" });
  return names.of(languageCode);
}

console.log(getNativeName("en"));
// "English"

console.log(getNativeName("es"));
// "español"

console.log(getNativeName("fr"));
// "français"

console.log(getNativeName("de"));
// "Deutsch"

console.log(getNativeName("ja"));
// "日本語"

console.log(getNativeName("ar"));
// "العربية"

console.log(getNativeName("zh"));
// "中文"

This pattern works for any language code. The formatter returns the name in the script and form that native speakers use.

Understanding language code formats

The of() method accepts language codes in several formats. You can use basic language codes like "en" or full locale identifiers like "en-US".

const names = new Intl.DisplayNames(["en"], { type: "language" });

console.log(names.of("en"));
// "English"

console.log(names.of("en-US"));
// "American English"

console.log(names.of("en-GB"));
// "British English"

console.log(names.of("zh"));
// "Chinese"

console.log(names.of("zh-Hans"));
// "Simplified Chinese"

console.log(names.of("zh-Hant"));
// "Traditional Chinese"

The formatter recognizes both short codes and extended identifiers with region or script subtags. This allows you to distinguish between language variants.

Controlling how language names appear

The languageDisplay option controls the level of detail in the returned names. It accepts two values.

The "standard" value returns full names that include dialect information. This is the default.

const names = new Intl.DisplayNames(["en"], {
  type: "language",
  languageDisplay: "standard"
});

console.log(names.of("en-US"));
// "American English"

console.log(names.of("en-GB"));
// "British English"

console.log(names.of("pt-BR"));
// "Brazilian Portuguese"

console.log(names.of("pt-PT"));
// "European Portuguese"

The "dialect" value also returns full names with dialect information. In most cases, it produces the same output as "standard".

const names = new Intl.DisplayNames(["en"], {
  type: "language",
  languageDisplay: "dialect"
});

console.log(names.of("en-US"));
// "American English"

console.log(names.of("pt-BR"));
// "Brazilian Portuguese"

For language selectors, the standard format helps users choose the correct variant when multiple dialects are available.

Getting language names localized for the user interface

When you build a settings page or language selector, you need to show language names in the user's current interface language. Create a formatter using the user's locale.

function getLocalizedLanguageName(languageCode, userLocale) {
  const names = new Intl.DisplayNames([userLocale], { type: "language" });
  return names.of(languageCode);
}

// User interface is in English
console.log(getLocalizedLanguageName("ja", "en"));
// "Japanese"

// User interface is in French
console.log(getLocalizedLanguageName("ja", "fr"));
// "japonais"

// User interface is in Spanish
console.log(getLocalizedLanguageName("ja", "es"));
// "japonés"

This approach shows descriptive names in the language the user understands. Combine this with native names to create hybrid labels like "日本語 (Japanese)" that work for both native speakers and others.

Building a language selector with native names

A common use case is building a dropdown or list where users select their preferred language. Display each language in its native form so users can find their option quickly.

const supportedLanguages = ["en", "es", "fr", "de", "ja", "ar", "zh"];

function createLanguageOptions() {
  return supportedLanguages.map((code) => {
    const names = new Intl.DisplayNames([code], { type: "language" });
    const nativeName = names.of(code);
    return { code, name: nativeName };
  });
}

const options = createLanguageOptions();
console.log(options);

This produces an array of language options with native names.

[
  { code: "en", name: "English" },
  { code: "es", name: "español" },
  { code: "fr", name: "français" },
  { code: "de", name: "Deutsch" },
  { code: "ja", name: "日本語" },
  { code: "ar", name: "العربية" },
  { code: "zh", name: "中文" }
]

You can render these options in HTML to create a language selector.

function renderLanguageSelector() {
  const options = createLanguageOptions();
  const select = document.createElement("select");
  select.id = "language-selector";

  options.forEach((option) => {
    const optionElement = document.createElement("option");
    optionElement.value = option.code;
    optionElement.textContent = option.name;
    select.appendChild(optionElement);
  });

  return select;
}

const selector = renderLanguageSelector();
document.body.appendChild(selector);

This creates a dropdown where each language appears in its native script, making it easy for users to identify their language.

Creating hybrid language labels

Some interfaces show both the native name and a translation in the user's language. This helps users who may not recognize all scripts and also makes the interface more accessible.

function createHybridLabel(languageCode, userLocale) {
  const nativeNames = new Intl.DisplayNames([languageCode], {
    type: "language"
  });
  const localizedNames = new Intl.DisplayNames([userLocale], {
    type: "language"
  });

  const nativeName = nativeNames.of(languageCode);
  const localizedName = localizedNames.of(languageCode);

  if (nativeName === localizedName) {
    return nativeName;
  }

  return `${nativeName} (${localizedName})`;
}

// User interface is in English
console.log(createHybridLabel("ja", "en"));
// "日本語 (Japanese)"

console.log(createHybridLabel("ar", "en"));
// "العربية (Arabic)"

console.log(createHybridLabel("en", "en"));
// "English"

// User interface is in Spanish
console.log(createHybridLabel("ja", "es"));
// "日本語 (japonés)"

This pattern combines the recognition benefits of native names with the clarity of localized translations.

Handling fallback locales

The Intl.DisplayNames constructor accepts an array of locales. If the first locale is not available, the formatter falls back to the next locale in the array.

const names = new Intl.DisplayNames(["xx-XX", "en"], { type: "language" });
console.log(names.of("fr"));
// "French"

The formatter tries "xx-XX" first, which does not exist, then falls back to "en". This ensures your code works even when the requested locale is unavailable.

You can check which locale the formatter actually uses with the resolvedOptions() method.

const names = new Intl.DisplayNames(["xx-XX", "en"], { type: "language" });
console.log(names.resolvedOptions().locale);
// "en"

This shows the formatter resolved to English after the fallback.

How different locales format language names

Each locale has its own conventions for language names. The formatter applies these automatically.

const supportedLanguages = ["en", "es", "fr", "de", "ja"];

function showLanguageNamesInLocale(locale) {
  const names = new Intl.DisplayNames([locale], { type: "language" });
  return supportedLanguages.map((code) => ({
    code,
    name: names.of(code)
  }));
}

console.log(showLanguageNamesInLocale("en"));
// [
//   { code: "en", name: "English" },
//   { code: "es", name: "Spanish" },
//   { code: "fr", name: "French" },
//   { code: "de", name: "German" },
//   { code: "ja", name: "Japanese" }
// ]

console.log(showLanguageNamesInLocale("es"));
// [
//   { code: "en", name: "inglés" },
//   { code: "es", name: "español" },
//   { code: "fr", name: "francés" },
//   { code: "de", name: "alemán" },
//   { code: "ja", name: "japonés" }
// ]

console.log(showLanguageNamesInLocale("ja"));
// [
//   { code: "en", name: "英語" },
//   { code: "es", name: "スペイン語" },
//   { code: "fr", name: "フランス語" },
//   { code: "de", name: "ドイツ語" },
//   { code: "ja", name: "日本語" }
// ]

The formatter handles capitalization, script, and linguistic conventions for each language automatically.

Browser support

The Intl.DisplayNames API is available in all modern browsers. It has been supported since March 2021 across major browsers including Chrome, Firefox, Safari, and Edge.

You can check if the API is available before using it.

if (typeof Intl.DisplayNames !== "undefined") {
  const names = new Intl.DisplayNames(["en"], { type: "language" });
  console.log(names.of("fr"));
} else {
  console.log("Intl.DisplayNames is not supported");
}

For older browsers, you need to provide a fallback or use a polyfill. A simple fallback uses a hardcoded mapping of language codes to names.

function getLanguageName(code, locale) {
  if (typeof Intl.DisplayNames !== "undefined") {
    const names = new Intl.DisplayNames([locale], { type: "language" });
    return names.of(code);
  }

  // Fallback for older browsers
  const fallbackNames = {
    en: "English",
    es: "español",
    fr: "français",
    de: "Deutsch",
    ja: "日本語"
  };

  return fallbackNames[code] || code;
}

console.log(getLanguageName("es", "en"));
// "español" (or "Spanish" if you adjust the fallback for localization)

This ensures your code works even in browsers without Intl.DisplayNames support, though you lose the automatic localization features.