如何显示拉丁文、西里尔文、阿拉伯文等脚本名称?

使用 Intl.DisplayNames 将脚本代码转换为任何语言中可读的书写系统名称。

简介

一种书写系统被称为文字系统。拉丁文字是英语、法语和西班牙语使用的文字系统。西里尔文字是俄语、保加利亚语和乌克兰语使用的文字系统。阿拉伯文字是阿拉伯语、波斯语和乌尔都语使用的文字系统。文字系统与语言不同,因为同一种语言可以用多种文字系统书写。例如,塞尔维亚语同时使用西里尔文字和拉丁文字。塞尔维亚的用户可以选择他们偏好的文字系统。

当您构建语言选择器、字体选择器或文本输入控件时,您需要显示文字系统的名称,以便用户能够识别书写系统。Intl.DisplayNames API 可以将文字代码转换为本地化的、易于理解的名称,而无需您维护翻译表。

理解文字系统与语言

文字系统与语言并不是同一回事。语言是人们说的内容,而文字系统是人们书写的方式。

英语是一种语言,拉丁文字是一种文字系统。英语使用拉丁文字,但还有许多其他语言也使用拉丁文字,包括西班牙语、法语、德语、越南语和土耳其语。

塞尔维亚语是一种可以用两种文字系统书写的语言。用西里尔文字书写的塞尔维亚语看起来像“Српски”。用拉丁文字书写的塞尔维亚语看起来像“Srpski”。两者都代表相同的语言,具有相同的单词和语法。不同之处仅在于书写系统。

中文有两种常见的文字变体。简体中文使用简化的汉字,繁体中文使用传统的汉字。同一句话根据使用的文字系统不同会有不同的表现形式。

在构建界面时,这种区别非常重要。塞尔维亚用户可能更喜欢西里尔文字而不是拉丁文字。中文用户需要在简体和繁体字符之间进行选择。您的界面需要显示文字系统的名称,以便用户做出这些选择。

硬编码脚本名称的问题

您可以创建一个查找表,将脚本代码映射到脚本名称。

const scriptNames = {
  Latn: "拉丁文",
  Cyrl: "西里尔文",
  Arab: "阿拉伯文",
  Hans: "简体中文",
  Hant: "繁体中文"
};

console.log(scriptNames.Latn);
// "拉丁文"

这种方法仅适用于英语使用者。讲其他语言的用户会看到他们可能无法理解的英文脚本名称。您需要为支持的每种语言提供翻译。

const scriptNames = {
  en: {
    Latn: "Latin",
    Cyrl: "Cyrillic",
    Arab: "Arabic"
  },
  es: {
    Latn: "latino",
    Cyrl: "cirílico",
    Arab: "árabe"
  },
  fr: {
    Latn: "latin",
    Cyrl: "cyrillique",
    Arab: "arabe"
  }
};

这种方法很快就会变得难以维护。每增加一种新语言,都需要一整套翻译。每增加一种新脚本,都需要在每种语言中添加条目。您需要一个更好的解决方案。

使用 Intl.DisplayNames 获取脚本名称

Intl.DisplayNames 构造函数创建一个格式化器,将脚本代码转换为人类可读的名称。您需要指定一个区域设置,并将类型设置为 "script"

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

第一个参数是一个区域标识符数组。第二个参数是一个选项对象,其中 type: "script" 告诉格式化器您需要脚本名称。of() 方法接受一个脚本代码并返回其名称。

脚本代码遵循 ISO 15924 标准。每种脚本都有一个四个字母的代码,第一个字母大写,其余三个字母小写。例如,拉丁文是 Latn,西里尔文是 Cyrl,阿拉伯文是 Arab

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

console.log(names.of("Latn"));
// "Latin"

console.log(names.of("Cyrl"));
// "Cyrillic"

console.log(names.of("Arab"));
// "Arabic"

console.log(names.of("Hani"));
// "Han"

console.log(names.of("Hira"));
// "Hiragana"

console.log(names.of("Kana"));
// "Katakana"

格式化器处理了维护脚本名称翻译的所有复杂性。您只需要提供脚本代码即可。

常见的脚本代码

ISO 15924 标准定义了超过 160 种脚本的代码。以下是最常用的代码。

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

console.log(names.of("Latn"));
// "Latin"

console.log(names.of("Cyrl"));
// "Cyrillic"

console.log(names.of("Arab"));
// "Arabic"

console.log(names.of("Hebr"));
// "Hebrew"

console.log(names.of("Deva"));
// "Devanagari"

console.log(names.of("Thai"));
// "Thai"

console.log(names.of("Hani"));
// "Han"

console.log(names.of("Hans"));
// "Simplified Han"

console.log(names.of("Hant"));
// "Traditional Han"

console.log(names.of("Hang"));
// "Hangul"

console.log(names.of("Hira"));
// "Hiragana"

console.log(names.of("Kana"));
// "Katakana"

console.log(names.of("Beng"));
// "Bengali"

console.log(names.of("Grek"));
// "Greek"

拉丁文覆盖了大多数西欧语言。西里尔文覆盖了俄语、保加利亚语、乌克兰语和其他斯拉夫语言。阿拉伯文覆盖了阿拉伯语、波斯语和乌尔都语。汉字覆盖了中文,其中 Hans 表示简体中文,Hant 表示繁体中文。韩文覆盖了韩语。平假名和片假名是日语的脚本。

在不同语言环境中显示脚本名称

脚本名称会根据显示语言环境进行本地化。通过创建不同语言环境的格式化器,可以查看不同语言中的名称。

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

console.log(enNames.of("Latn"));
// "Latin"

console.log(esNames.of("Latn"));
// "latino"

console.log(frNames.of("Latn"));
// "latin"

console.log(jaNames.of("Latn"));
// "ラテン文字"

每个格式化器都会返回其显示语言环境中的脚本名称。这自动处理了脚本名称翻译的所有复杂性。

相同的模式适用于任何脚本代码。

const enNames = new Intl.DisplayNames(["en"], { type: "script" });
const deNames = new Intl.DisplayNames(["de"], { type: "script" });
const zhNames = new Intl.DisplayNames(["zh"], { type: "script" });

console.log(enNames.of("Cyrl"));
// "Cyrillic"

console.log(deNames.of("Cyrl"));
// "Kyrillisch"

console.log(zhNames.of("Cyrl"));
// "西里尔文"

console.log(enNames.of("Arab"));
// "Arabic"

console.log(deNames.of("Arab"));
// "Arabisch"

console.log(zhNames.of("Arab"));
// "阿拉伯文"

格式化器会自动为每种语言应用正确的语言学惯例。

为语言变体构建脚本选择器

某些语言为用户提供了脚本选择的选项。例如,塞尔维亚语可以用西里尔字母或拉丁字母书写。中文可以用简体或繁体字符书写。您需要显示这些选项,以便用户可以进行选择。

function getScriptOptions(language, userLocale) {
  const names = new Intl.DisplayNames([userLocale], { type: "script" });

  if (language === "sr") {
    return [
      { code: "Cyrl", name: names.of("Cyrl") },
      { code: "Latn", name: names.of("Latn") }
    ];
  }

  if (language === "zh") {
    return [
      { code: "Hans", name: names.of("Hans") },
      { code: "Hant", name: names.of("Hant") }
    ];
  }

  return [];
}

console.log(getScriptOptions("sr", "en"));
// [
//   { code: "Cyrl", name: "Cyrillic" },
//   { code: "Latn", name: "Latin" }
// ]

console.log(getScriptOptions("zh", "en"));
// [
//   { code: "Hans", name: "Simplified Han" },
//   { code: "Hant", name: "Traditional Han" }
// ]

console.log(getScriptOptions("zh", "es"));
// [
//   { code: "Hans", name: "han simplificado" },
//   { code: "Hant", name: "han tradicional" }
// ]

此函数以用户界面语言返回脚本选项。塞尔维亚语用户会看到西里尔字母和拉丁字母的选项。中文用户会看到简体和繁体的选项。名称以用户理解的语言显示。

显示包含脚本信息的完整区域标识符

区域标识符可以包含脚本代码以区分书写系统。格式为 language-script-region,例如 sr-Cyrl-RS 表示在塞尔维亚用西里尔字母书写的塞尔维亚语,或 zh-Hans-CN 表示在中国用简体中文书写的中文。

当您显示这些区域标识符时,提取脚本代码并将其转换为可读名称。

function parseLocaleWithScript(locale) {
  const parts = locale.split("-");

  if (parts.length < 2) {
    return null;
  }

  const [language, script] = parts;

  if (script.length === 4) {
    return {
      language,
      script: script.charAt(0).toUpperCase() + script.slice(1).toLowerCase()
    };
  }

  return null;
}

function formatLocaleWithScriptName(locale, displayLocale) {
  const parsed = parseLocaleWithScript(locale);

  if (!parsed) {
    return locale;
  }

  const languageNames = new Intl.DisplayNames([displayLocale], {
    type: "language"
  });
  const scriptNames = new Intl.DisplayNames([displayLocale], { type: "script" });

  const languageName = languageNames.of(parsed.language);
  const scriptName = scriptNames.of(parsed.script);

  return `${languageName} (${scriptName})`;
}

console.log(formatLocaleWithScriptName("sr-Cyrl", "en"));
// "Serbian (Cyrillic)"

console.log(formatLocaleWithScriptName("sr-Latn", "en"));
// "Serbian (Latin)"

console.log(formatLocaleWithScriptName("zh-Hans", "en"));
// "Chinese (Simplified Han)"

console.log(formatLocaleWithScriptName("zh-Hant", "en"));
// "Chinese (Traditional Han)"

console.log(formatLocaleWithScriptName("sr-Cyrl", "es"));
// "serbio (cirílico)"

此模式通过将语言名称与脚本名称结合,使区域标识符更易于理解。用户会看到 "Serbian (Cyrillic)" 而不是 "sr-Cyrl"。

使用脚本名称创建字体选择器

字体选择界面通常按支持的脚本对字体进行分组。您需要显示脚本名称,以便用户了解每种字体支持哪些脚本。

function createFontOptions() {
  const fonts = [
    {
      name: "Arial",
      scripts: ["Latn", "Cyrl", "Grek", "Hebr", "Arab"]
    },
    {
      name: "Noto Sans CJK",
      scripts: ["Hans", "Hant", "Hira", "Kana", "Hang"]
    },
    {
      name: "Noto Sans Devanagari",
      scripts: ["Deva"]
    }
  ];

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

  return fonts.map((font) => ({
    name: font.name,
    scripts: font.scripts.map((code) => names.of(code))
  }));
}

console.log(createFontOptions());
// [
//   {
//     name: "Arial",
//     scripts: ["Latin", "Cyrillic", "Greek", "Hebrew", "Arabic"]
//   },
//   {
//     name: "Noto Sans CJK",
//     scripts: ["简体汉字", "繁体汉字", "平假名", "片假名", "韩文"]
//   },
//   {
//     name: "Noto Sans Devanagari",
//     scripts: ["天城文"]
//   }
// ]

这将创建一个包含字体及其支持脚本的列表,以人类可读的形式显示。用户可以根据所需的书写系统选择字体。

按脚本显示可用的输入法

操作系统和浏览器为不同的脚本提供输入法。例如,日语输入法将拉丁字符转换为平假名、片假名或汉字;中文输入法将拼音转换为简体或繁体汉字。您可以显示可用的输入法及其脚本名称。

function getInputMethods(userLocale) {
  const inputMethods = [
    { id: "latin-ime", script: "Latn" },
    { id: "japanese-ime", script: "Hira" },
    { id: "chinese-pinyin-simplified", script: "Hans" },
    { id: "chinese-pinyin-traditional", script: "Hant" },
    { id: "korean-ime", script: "Hang" },
    { id: "arabic-ime", script: "Arab" },
    { id: "hebrew-ime", script: "Hebr" }
  ];

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

  return inputMethods.map((method) => ({
    id: method.id,
    name: names.of(method.script)
  }));
}

console.log(getInputMethods("en"));
// [
//   { id: "latin-ime", name: "Latin" },
//   { id: "japanese-ime", name: "Hiragana" },
//   { id: "chinese-pinyin-simplified", name: "Simplified Han" },
//   { id: "chinese-pinyin-traditional", name: "Traditional Han" },
//   { id: "korean-ime", name: "Hangul" },
//   { id: "arabic-ime", name: "Arabic" },
//   { id: "hebrew-ime", name: "Hebrew" }
// ]

console.log(getInputMethods("ja"));
// [
//   { id: "latin-ime", name: "ラテン文字" },
//   { id: "japanese-ime", name: "ひらがな" },
//   { id: "chinese-pinyin-simplified", name: "簡体字" },
//   { id: "chinese-pinyin-traditional", name: "繁体字" },
//   { id: "korean-ime", name: "ハングル" },
//   { id: "arabic-ime", name: "アラビア文字" },
//   { id: "hebrew-ime", name: "ヘブライ文字" }
// ]

这将以用户的语言显示输入法名称。例如,当界面为英文时,用户会看到“Hiragana”;当界面为日文时,用户会看到“ひらがな”。

理解脚本代码的大小写规则

脚本代码遵循特定的大小写模式。第一个字母大写,其余三个字母小写。例如,Latn 是正确的,而 LATNlatnLaTn 都不是标准格式。

of() 方法接受任何大小写形式的脚本代码。

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

console.log(names.of("Latn"));
// "Latin"

console.log(names.of("LATN"));
// "Latin"

console.log(names.of("latn"));
// "Latin"

console.log(names.of("LaTn"));
// "Latin"

格式化器能够正确处理所有形式。然而,使用标准的大小写模式可以使代码更具可读性,并与 ISO 15924 标准保持一致。

处理回退语言环境

Intl.DisplayNames 构造函数接受一个语言环境数组。如果第一个语言环境不可用,格式化器会回退到数组中的下一个语言环境。

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

格式化器首先尝试 "xx-XX",但由于不存在该语言环境,它会回退到 "en"。这确保了即使请求的语言环境不可用,代码仍然可以正常工作。

您可以使用 resolvedOptions() 方法检查格式化器实际使用的语言环境。

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

这表明格式化器在回退后解析为英语。

构建多语言内容管理系统

支持多种脚本的内容管理系统需要显示每段内容可用的脚本。您可以显示脚本名称,帮助内容编辑者选择正确的版本。

function getContentVersions(contentId, userLocale) {
  const versions = [
    { script: "Latn", url: `/content/${contentId}/latn` },
    { script: "Cyrl", url: `/content/${contentId}/cyrl` },
    { script: "Arab", url: `/content/${contentId}/arab` }
  ];

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

  return versions.map((version) => ({
    script: version.script,
    name: names.of(version.script),
    url: version.url
  }));
}

console.log(getContentVersions("article-123", "en"));
// [
//   { script: "Latn", name: "Latin", url: "/content/article-123/latn" },
//   { script: "Cyrl", name: "Cyrillic", url: "/content/article-123/cyrl" },
//   { script: "Arab", name: "Arabic", url: "/content/article-123/arab" }
// ]

这种模式可以帮助内容编辑者查看哪些脚本版本存在,并在它们之间导航。

浏览器支持

Intl.DisplayNames API 支持脚本类型功能,可在所有现代浏览器中使用。自 2021 年 3 月起,包括 Chrome、Firefox、Safari 和 Edge 在内的主流浏览器均已支持该功能。

在使用之前,您可以检查该 API 是否可用。

if (typeof Intl.DisplayNames !== "undefined") {
  const names = new Intl.DisplayNames(["en"], { type: "script" });
  console.log(names.of("Latn"));
} else {
  console.log("Intl.DisplayNames 不受支持");
}

对于较旧的浏览器,您需要提供一个回退方案或使用 polyfill。一个简单的回退方案是使用脚本代码到名称的硬编码映射。

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

  const fallbackNames = {
    Latn: "拉丁文",
    Cyrl: "西里尔文",
    Arab: "阿拉伯文",
    Hans: "简体汉字",
    Hant: "繁体汉字"
  };

  return fallbackNames[code] || code;
}

console.log(getScriptName("Latn", "en"));
// "拉丁文"

这可以确保您的代码即使在不支持 Intl.DisplayNames 的浏览器中也能正常工作,但您将失去自动本地化功能。