如何根据本地规则将文本转换为大写或小写
使用 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());
// Output: "ISTANBUL" (incorrect - uses dotless I)
console.log(turkish.toLocaleUpperCase("tr"));
// Output: "İSTANBUL" (correct - uses dotted İ)
城市名 Istanbul 包含带点的 i 字符。使用土耳其语规则将其大写时,会变为 İSTANBUL(带点 İ)。如果用标准 toUpperCase() 方法,则会变为 ISTANBUL(无点 I),这在土耳其语中是错误的。
当将土耳其语大写文本转换为小写时,也会出现同样的问题。
const uppercase = "İSTANBUL";
console.log(uppercase.toLowerCase());
// Output: "i̇stanbul" (incorrect - creates i with combining dot above)
console.log(uppercase.toLocaleLowerCase("tr"));
// Output: "istanbul" (correct - produces dotted i)
在土耳其语中,大写带点 İ 应该变为小写带点 i。标准 toLowerCase() 方法无法正确处理,可能会生成带有组合点字符的小写 i,虽然看起来相似,但技术上并不正确。
其他特定语言的大小写规则
土耳其语并不是唯一拥有特殊大小写转换规则的语言。还有其他多种语言也需要根据本地化进行处理。
德语中有字母 ß(尖音 s),传统上没有大写形式。2017 年,Unicode 新增了大写字母 ẞ,但许多系统在大写转换时仍将 ß 转换为 SS。
const german = "Straße";
console.log(german.toUpperCase());
// Output: "STRASSE" (converts ß to SS)
console.log(german.toLocaleUpperCase("de"));
// Output: "STRASSE" (also converts ß to SS)
在大多数 JavaScript 环境中,这两种方法对德语文本的处理结果相同。locale 参数不会改变输出,但使用支持本地化的方式可以确保代码在未来 Unicode 处理方式变更时依然正确。
希腊语的西格玛字母有三种不同形式。小写时,单词中间用 σ,单词结尾用 ς。这两种形式在大写时都转换为 Σ。
立陶宛语对带点字母有特殊规则。字母 i 在与某些变音符号组合时,即使大写也会保留点。这会影响本地化方法对特定字符组合的处理方式。
使用 toLocaleUpperCase 进行本地化大写转换
toLocaleUpperCase() 方法会根据本地化大小写映射规则将字符串转换为大写。你可以在字符串上调用该方法,并可选地传入一个本地标识符作为参数。
const text = "istanbul";
const result = text.toLocaleUpperCase("tr");
console.log(result);
// Output: "İSTANBUL"
这会使用土耳其语规则将字符串转换为大写。带点的 i 会变为带点的 İ,这对于土耳其语来说是正确的。
你可以使用不同的本地化规则对同一文本进行转换。
const text = "istanbul";
console.log(text.toLocaleUpperCase("tr"));
// Output: "İSTANBUL" (Turkish rules - dotted İ)
console.log(text.toLocaleUpperCase("en"));
// Output: "ISTANBUL" (English rules - dotless I)
locale 参数决定采用哪种大小写转换规则。土耳其语规则会保留 i 上的点,而英语规则则不会。
如果调用 toLocaleUpperCase() 时不传递参数,则会使用由 JavaScript 运行时环境确定的系统区域设置。
const text = "istanbul";
const result = text.toLocaleUpperCase();
console.log(result);
// Output depends on system locale
输出结果取决于 JavaScript 环境的默认区域设置,通常与用户操作系统的设置一致。
使用 toLocaleLowerCase 进行区域感知的小写转换
toLocaleLowerCase() 方法会根据区域特定的大小写映射规则将字符串转换为小写。其工作方式与 toLocaleUpperCase() 相同,但转换为小写而不是大写。
const text = "İSTANBUL";
const result = text.toLocaleLowerCase("tr");
console.log(result);
// Output: "istanbul"
这会使用土耳其语规则将大写的土耳其语文本转换为小写。带点的 İ 会变为带点的 i,从而生成正确的小写形式。
如果不指定区域参数,标准 toLowerCase() 或默认区域设置下的 toLocaleLowerCase() 可能无法正确处理土耳其字符。
const text = "İSTANBUL";
console.log(text.toLowerCase());
// Output: "i̇stanbul" (incorrect - i with combining dot above)
console.log(text.toLocaleLowerCase("tr"));
// Output: "istanbul" (correct - dotted i)
土耳其语中的带点 İ 需要使用土耳其语大小写规则才能正确转换。使用带 tr 区域的区域感知方法可以确保正确转换。
你还可以处理土耳其语中的无点 I,在转换为小写时应保持无点。
const text = "IRAK";
console.log(text.toLocaleLowerCase("tr"));
// Output: "ırak" (Turkish rules - dotless ı)
console.log(text.toLocaleLowerCase("en"));
// Output: "irak" (English rules - dotted i)
单词 IRAK(土耳其语中的伊拉克)使用了无点 I。土耳其语大小写规则会将其转换为小写无点 ı,而英语规则则会转换为带点的 i。
指定区域标识符
toLocaleUpperCase() 和 toLocaleLowerCase() 都支持 BCP 47 格式的区域标识符。这些标识符与 Intl API 及其他国际化功能中使用的语言标签相同。
const text = "Straße";
console.log(text.toLocaleUpperCase("de-DE"));
// Output: "STRASSE"
console.log(text.toLocaleUpperCase("de-AT"));
// Output: "STRASSE"
console.log(text.toLocaleUpperCase("de-CH"));
// Output: "STRASSE"
这些示例使用了德国、奥地利和瑞士的不同德语区域。对于同一语言的不同地区变体,大小写转换规则通常是一致的,因此三者输出相同。
你也可以传递一个包含多个 locale 标识符的数组。该方法会使用数组中的第一个 locale。
const text = "istanbul";
const result = text.toLocaleUpperCase(["tr", "en"]);
console.log(result);
// Output: "İSTANBUL"
该方法会应用土耳其语规则,因为 tr 是数组中的第一个 locale。如果运行环境不支持第一个 locale,则会依次回退到数组中的后续 locale。
使用浏览器的 locale 偏好设置
在 Web 应用中,你可以使用用户浏览器的 locale 偏好设置来确定应应用哪些大小写转换规则。navigator.language 属性会返回用户的首选语言。
const userLocale = navigator.language;
const text = "istanbul";
const result = text.toLocaleUpperCase(userLocale);
console.log(result);
// Output varies by user's locale
// For Turkish users: "İSTANBUL"
// For English users: "ISTANBUL"
这会根据用户的语言设置自动应用正确的大小写规则。土耳其用户会看到使用土耳其语规则转换的文本,英语用户会看到使用英语规则转换的文本,依此类推。
你也可以传递整个 locale 偏好数组,以启用回退机制。
const text = "istanbul";
const result = text.toLocaleUpperCase(navigator.languages);
console.log(result);
该方法会使用用户偏好中的第一个 locale,在特定 locale 不可用时提供更好的回退处理。
标准方法与 locale 感知方法的对比
标准的 toUpperCase() 和 toLowerCase() 方法在英语下可以正常工作,但在其他语言下可能会出错。locale 感知方法 toLocaleUpperCase() 和 toLocaleLowerCase() 能通过应用 locale 特定规则正确处理所有语言。
const turkish = "Diyarbakır";
// Standard methods (incorrect for Turkish)
console.log(turkish.toUpperCase());
// Output: "DIYARBAKIR" (dotless I - incorrect)
console.log(turkish.toUpperCase().toLowerCase());
// Output: "diyarbakir" (dotted i - lost the dotless ı)
// Locale-aware methods (correct for Turkish)
console.log(turkish.toLocaleUpperCase("tr"));
// Output: "DİYARBAKIR" (dotted İ and dotless I - correct)
console.log(turkish.toLocaleUpperCase("tr").toLocaleLowerCase("tr"));
// Output: "diyarbakır" (preserves both i types - correct)
土耳其城市名 Diyarbakır 包含两种不同的 i。标准方法在大小写转换时无法保留这种差异,而 locale 感知方法可以在两种转换方向上都保持字符的正确性。
对于只包含简单大小写规则字符的文本,两种方法的结果是相同的。
const english = "Hello World";
console.log(english.toUpperCase());
// Output: "HELLO WORLD"
console.log(english.toLocaleUpperCase("en"));
// Output: "HELLO WORLD"
英文文本无论使用哪种方法转换,结果都是一样的。对于仅包含英文的文本,不一定需要使用支持区域设置的方法,但采用该方法可以确保当文本包含其他语言时,代码依然能正确运行。
何时使用支持区域设置的大小写转换
当处理用户生成内容或可能包含多种语言的文本时,应使用支持区域设置的方法。这样可以确保无论文本包含哪种语言,都能正确进行大小写转换。
function normalizeUsername(username) {
return username.toLocaleLowerCase();
}
用户名、电子邮件地址、搜索词以及其他用户输入内容应使用支持区域设置的转换方法。这样可以正确处理国际字符,并避免土耳其语等特殊情况带来的问题。
仅当确定文本只包含英文字符且需要极致性能时,才建议使用标准方法。标准方法执行速度略快,因为无需检查区域设置规则。
const htmlTag = "<DIV>";
const normalized = htmlTag.toLowerCase();
// Output: "<div>"
HTML 标签名、CSS 属性、协议方案以及其他技术标识符均使用 ASCII 字符,不需要考虑区域设置。对于这些内容,标准方法可以正常工作。
字符长度在转换后可能发生的变化
大小写转换并不总是字符一一对应。有些字符在转换为大写时会扩展为多个字符,从而影响字符串长度。
const german = "groß";
console.log(german.length);
// Output: 4
const uppercase = german.toLocaleUpperCase("de");
console.log(uppercase);
// Output: "GROSS"
console.log(uppercase.length);
// Output: 5
德语单词 groß 有四个字符。当转换为大写时,ß 会变成 SS,得到 GROSS,共有五个字符。转换过程中字符串长度增加了一个字符。
这会影响依赖字符串长度或字符位置的操作。不要假设字符串转换为大写或小写后长度与原始字符串相同。
const text = "Maße";
const positions = [0, 1, 2, 3];
const uppercase = text.toLocaleUpperCase("de");
// "MASSE" (5 characters)
// Original position mapping no longer valid
在第 2 个位置的 ß 在转换为大写时会变为 SS,导致后续所有字符的位置发生偏移。原始字符串中的字符位置与转换后字符串中的位置不再对应。
复用 locale 参数
如果你需要使用同一个 locale 转换多个字符串,可以将 locale 标识符存储在变量中并复用。这样可以让你的代码更易维护,并确保 locale 处理的一致性。
const userLocale = navigator.language;
const city = "istanbul";
const country = "türkiye";
console.log(city.toLocaleUpperCase(userLocale));
console.log(country.toLocaleUpperCase(userLocale));
这种方法将 locale 的选择集中管理。如果需要更换使用的 locale,只需更新变量定义即可。
对于处理大量文本的应用,这种方式并不会带来性能提升。每次调用 toLocaleUpperCase() 或 toLocaleLowerCase() 都是独立进行转换。与 Intl API 的格式化器不同,这里没有可复用的 formatter 对象。