如何根据区域规则将文本转换为大写或小写

使用 JavaScript 正确地为不同语言和书写系统更改文本大小写

介绍

当您在大写和小写之间转换文本时,您可能会认为这种操作对所有语言都适用。事实并非如此。不同的书写系统遵循不同的大小写转换规则,如果您未考虑这些规则,可能会产生意想不到的结果。

JavaScript 提供了标准的 toUpperCase()toLowerCase() 方法,这些方法在处理英语时可以正常工作,但在处理其他语言时可能会产生错误的结果。支持区域设置的 toLocaleUpperCase()toLocaleLowerCase() 方法会应用特定语言的大小写转换规则,确保无论是哪种语言,文本都能正确转换。

本课程将解释为什么大小写转换因语言而异,展示使用标准方法时可能出现的具体问题,并说明如何使用支持区域设置的方法来正确处理国际化应用中的大小写转换。

为什么大小写转换因语言而异

字母的大写和小写版本并不是适用于所有书写系统的通用概念。不同的语言根据其历史书写习惯和排版实践,制定了不同的大小写转换规则。

在英语中,大小写转换相对简单。字母 i 转换为大写时变为 I,而 I 转换为小写时变为 i。这种关系适用于整个英语字母表。

其他语言的规则则更为复杂。土耳其语中有四种不同的字母 i,而不是两种。德语中有字母 ß(尖 S),其大写转换有特定规则。希腊语中,字母 sigma 在单词末尾时有不同的形式。

当您使用 JavaScript 的标准方法如 toUpperCase()toLowerCase() 时,转换会遵循英语规则。这会导致其他语言的文本转换结果不正确。支持区域设置的方法会为每种语言应用适当的规则,从而确保转换正确。

土耳其语中的 i 问题

土耳其语是最能清楚说明为什么区域设置对大小写转换很重要的例子。与英语不同,土耳其语中有四个与 i 相关的不同字母:

  • 小写带点的 i:i (U+0069)
  • 大写带点的 İ:İ (U+0130)
  • 小写无点的 ı:ı (U+0131)
  • 大写无点的 I:I (U+0049)

在土耳其语中,小写带点的 i 转换为大写带点的 İ。小写无点的 ı 转换为大写无点的 I。这是两对独立的字母,具有不同的发音和含义。

标准的 JavaScript 方法遵循英语规则,将带点的 i 转换为无点的 I。这会改变土耳其语单词的含义并生成错误的文本。

const turkish = "istanbul";

console.log(turkish.toUpperCase());
// 输出:"ISTANBUL"(错误 - 使用了无点的 I)

console.log(turkish.toLocaleUpperCase("tr"));
// 输出:"İSTANBUL"(正确 - 使用了带点的 İ)

城市名称 Istanbul 包含带点的 i 字符。使用土耳其语规则转换为大写时,它变为 İSTANBUL,带有带点的 İ。使用标准的 toUpperCase() 会生成 ISTANBUL,带有无点的 I,这在土耳其语中是错误的。

在将大写的土耳其语文本转换为小写时也会出现同样的问题。

const uppercase = "İSTANBUL";

console.log(uppercase.toLowerCase());
// 输出:"i̇stanbul"(错误 - 生成了带有组合点的 i)

console.log(uppercase.toLocaleLowerCase("tr"));
// 输出:"istanbul"(正确 - 生成了带点的 i)

在土耳其语中,带点的 İ 转换为小写时应变为带点的 i。标准的 toLowerCase() 无法正确处理这一点,可能会生成带有组合点字符的小写 i,这在技术上是错误的。

其他区域设置特定的大小写规则

土耳其语并不是唯一需要特殊大小写转换规则的语言。还有其他一些语言需要区域设置特定的处理。

德语中有字母 ß(尖 S),传统上没有大写形式。在 2017 年,Unicode 添加了大写的 ẞ 字符,但许多系统在转换为大写时仍将 ß 转换为 SS。

const german = "Straße";

console.log(german.toUpperCase());
// 输出:"STRASSE"(将 ß 转换为 SS)

console.log(german.toLocaleUpperCase("de"));
// 输出:"STRASSE"(也将 ß 转换为 SS)

在大多数 JavaScript 环境中,这两种方法对德语文本生成相同的结果。区域设置参数不会改变输出,但使用区域设置感知的方法可以确保您的代码在未来实现中 Unicode 处理发生变化时仍然正确。

希腊语中,字母 sigma 有三种不同的形式。小写形式在单词中间使用 σ,在单词末尾使用 ς。这两种形式都转换为相同的大写 Σ。

立陶宛语对带点字母有特殊规则。当字母 i 与某些变音符号组合时,即使转换为大写也会保留其点。这会影响区域设置感知方法如何处理特定的字符组合。

使用 toLocaleUpperCase 进行基于区域的大写转换

toLocaleUpperCase() 方法使用特定区域的大小写映射规则将字符串转换为大写。您可以在字符串上调用此方法,并可选地传递一个区域标识符作为参数。

const text = "istanbul";

const result = text.toLocaleUpperCase("tr");
console.log(result);
// 输出: "İSTANBUL"

此方法使用土耳其语规则将字符串转换为大写。带点的 i 变为带点的 İ,这符合土耳其语规则。

您可以使用不同的区域规则转换相同的文本。

const text = "istanbul";

console.log(text.toLocaleUpperCase("tr"));
// 输出: "İSTANBUL" (土耳其语规则 - 带点 İ)

console.log(text.toLocaleUpperCase("en"));
// 输出: "ISTANBUL" (英语规则 - 无点 I)

区域参数决定了应用哪些大小写转换规则。土耳其语规则保留了 i 上的点,而英语规则则没有。

如果调用 toLocaleUpperCase() 时不传递参数,它将使用 JavaScript 运行环境确定的系统区域。

const text = "istanbul";

const result = text.toLocaleUpperCase();
console.log(result);
// 输出取决于系统区域

输出取决于 JavaScript 环境的默认区域,通常与用户的操作系统设置相匹配。

使用 toLocaleLowerCase 进行基于区域的小写转换

toLocaleLowerCase() 方法使用特定区域的大小写映射规则将字符串转换为小写。其工作方式与 toLocaleUpperCase() 相同,但将字符串转换为小写。

const text = "İSTANBUL";

const result = text.toLocaleLowerCase("tr");
console.log(result);
// 输出: "istanbul"

此方法使用土耳其语规则将大写的土耳其语文本转换为小写。带点的 İ 变为带点的 i,生成正确的小写形式。

如果不使用区域参数,标准的 toLowerCase() 或使用默认区域设置的 toLocaleLowerCase() 可能无法正确处理土耳其语字符。

const text = "İSTANBUL";

console.log(text.toLowerCase());
// 输出: "i̇stanbul" (不正确 - 带组合点的 i)

console.log(text.toLocaleLowerCase("tr"));
// 输出: "istanbul" (正确 - 带点的 i)

土耳其语的带点 İ 需要土耳其语大小写规则才能正确转换。使用带有 tr 区域的区域感知方法可以确保正确的转换。

您还可以处理土耳其语中的无点 I,它在转换为小写时应保持无点。

const text = "IRAK";

console.log(text.toLocaleLowerCase("tr"));
// 输出: "ırak" (土耳其语规则 - 无点 ı)

console.log(text.toLocaleLowerCase("en"));
// 输出: "irak" (英语规则 - 带点 i)

单词 IRAK(土耳其语中的伊拉克)使用无点 I。土耳其语大小写规则将其转换为小写的无点 ı,而英语规则将其转换为带点的 i。

指定语言环境标识符

toLocaleUpperCase()toLocaleLowerCase() 方法接受 BCP 47 格式的语言环境标识符。这些标识符与 Intl API 和其他国际化功能中使用的语言标签相同。

const text = "Straße";

console.log(text.toLocaleUpperCase("de-DE"));
// 输出: "STRASSE"

console.log(text.toLocaleUpperCase("de-AT"));
// 输出: "STRASSE"

console.log(text.toLocaleUpperCase("de-CH"));
// 输出: "STRASSE"

这些示例使用了德国、奥地利和瑞士的不同德语语言环境。大小写转换规则在同一语言的区域变体之间通常是一致的,因此所有三个示例的输出相同。

您还可以传递一个语言环境标识符数组。方法会使用数组中的第一个语言环境。

const text = "istanbul";

const result = text.toLocaleUpperCase(["tr", "en"]);
console.log(result);
// 输出: "İSTANBUL"

该方法应用了土耳其语规则,因为 tr 是数组中的第一个语言环境。如果运行时不支持第一个语言环境,它会回退到数组中的后续语言环境。

使用浏览器的语言环境偏好

在 Web 应用程序中,您可以使用用户的浏览器语言环境偏好来确定应用哪些大小写转换规则。navigator.language 属性返回用户的首选语言。

const userLocale = navigator.language;

const text = "istanbul";
const result = text.toLocaleUpperCase(userLocale);

console.log(result);
// 输出因用户的语言环境而异
// 对于土耳其用户: "İSTANBUL"
// 对于英语用户: "ISTANBUL"

这会根据用户的语言设置自动应用正确的大小写规则。土耳其用户会看到使用土耳其语规则转换的文本,英语用户会看到使用英语规则转换的文本,依此类推。

您还可以传递整个语言环境偏好数组以启用回退行为。

const text = "istanbul";
const result = text.toLocaleUpperCase(navigator.languages);

console.log(result);

该方法使用用户偏好中的第一个语言环境,当特定语言环境不可用时提供更好的回退处理。

比较标准方法和区域感知方法

标准的 toUpperCase()toLowerCase() 方法在处理英语时可以正常工作,但在处理其他语言时可能会出错。区域感知方法 toLocaleUpperCase()toLocaleLowerCase() 通过应用特定区域规则,能够正确处理所有语言。

const turkish = "Diyarbakır";

// 标准方法(对土耳其语不正确)
console.log(turkish.toUpperCase());
// 输出: "DIYARBAKIR"(无点的 I - 不正确)

console.log(turkish.toUpperCase().toLowerCase());
// 输出: "diyarbakir"(有点的 i - 丢失了无点的 ı)

// 区域感知方法(对土耳其语正确)
console.log(turkish.toLocaleUpperCase("tr"));
// 输出: "DİYARBAKIR"(有点的 İ 和无点的 I - 正确)

console.log(turkish.toLocaleUpperCase("tr").toLocaleLowerCase("tr"));
// 输出: "diyarbakır"(保留了两种 i 类型 - 正确)

土耳其城市名 Diyarbakır 包含两种类型的 i。标准方法在大小写转换时无法保留这种区别。而区域感知方法在两个方向上都能保持正确的字符。

对于仅包含简单大小写规则字符的文本,两种方法的结果是相同的。

const english = "Hello World";

console.log(english.toUpperCase());
// 输出: "HELLO WORLD"

console.log(english.toLocaleUpperCase("en"));
// 输出: "HELLO WORLD"

英语文本在两种方法下的转换结果相同。对于仅包含英语的文本,不需要使用区域感知版本,但使用它可以确保代码在包含其他语言的文本时也能正确工作。

何时使用区域感知的大小写转换

在处理用户生成的内容或可能包含多种语言的文本时,应使用区域感知方法。这可以确保无论文本包含哪种语言,都能正确进行大小写转换。

function normalizeUsername(username) {
  return username.toLocaleLowerCase();
}

用户名、电子邮件地址、搜索词和其他用户输入内容应使用区域感知转换。这可以正确处理国际字符,并避免土耳其语和其他特殊情况的问题。

仅在您确定文本仅包含英语字符并且需要最大性能时,才使用标准方法。标准方法执行速度稍快,因为它们不需要检查区域规则。

const htmlTag = "<DIV>";
const normalized = htmlTag.toLowerCase();
// 输出: "<div>"

HTML 标签名、CSS 属性、协议方案和其他技术标识符使用 ASCII 字符,不需要区域感知。标准方法对这些内容可以正常工作。

转换后字符长度如何变化

大小写转换并不总是一个一对一的字符映射。一些字符在转换为大写时会扩展为多个字符,从而影响字符串的长度。

const german = "groß";

console.log(german.length);
// 输出: 4

const uppercase = german.toLocaleUpperCase("de");
console.log(uppercase);
// 输出: "GROSS"

console.log(uppercase.length);
// 输出: 5

德语单词 groß 有四个字符。当转换为大写时,ß 变为 SS,生成了 GROSS,共有五个字符。字符串长度在转换过程中增加了一个字符。

这会影响依赖字符串长度或字符位置的操作。不要假设字符串的大写或小写版本与原始字符串具有相同的长度。

const text = "Maße";
const positions = [0, 1, 2, 3];

const uppercase = text.toLocaleUpperCase("de");
// "MASSE" (5 个字符)

// 原始位置映射不再有效

在大写版本中,位置 2 的 ß 变为 SS,导致所有后续字符的位置发生了偏移。原始字符串中的字符位置与转换后的字符串中的位置不对应。

重用区域设置参数

如果需要使用相同的区域设置转换多个字符串,可以将区域设置标识符存储在变量中并重复使用。这使代码更易于维护,并确保一致的区域设置处理。

const userLocale = navigator.language;

const city = "istanbul";
const country = "türkiye";

console.log(city.toLocaleUpperCase(userLocale));
console.log(country.toLocaleUpperCase(userLocale));

这种方法将区域设置选择集中在一个地方。如果需要更改使用的区域设置,只需更新变量定义即可。

对于处理大量文本的应用程序,这种方法并不会带来性能上的优势。每次调用 toLocaleUpperCase()toLocaleLowerCase() 都会独立执行转换。与 Intl API 格式化器不同,这里没有可重用的格式化器对象。