跳到主要内容
第10页,共12页
正文中添加了77个字符
分号
  • 7.2公里
  • 2
  • 31
  • 39

我在现有的有关以下问题的答案中没有看到任何提及星体层代码点或国际化。“大写字母”在使用给定脚本的每种语言中的含义并不相同。

起初,我没有看到任何解决星体层代码点相关问题的答案。那里是一个,但它有点埋了(我想这个会是这样的!)

隐藏问题及其各种解决方法概述

大多数建议的功能如下所示:

函数大写首字母(str){return str[0].toUpperCase()+str.slice(1);}

然而,一些大小写字符不在BMP(基本多语言平面,代码点U+0到U+FFFF)范围内。以Deseret文本为例:

大写首字母(“𐐶𐐲𐑌𐐼𐐲𐑉"); // "𐐶𐐲𐑌𐐼𐐲𐑉"

这里的第一个字符无法大写,因为字符串的数组索引属性不访问“字符”或代码点*。他们访问UTF-16代码单元。切片时也是如此,索引值指向代码单元。

UTF-16代码单元正好是1:1,USV代码点在两个范围内,即U+0到U+D7FF和U+E000到U+FFFF。大多数大小写字符都属于这两个范围,但不是全部。

从ES2015开始,处理这个问题变得更容易了。String.prototype[@@iterator]生成与代码点**对应的字符串。例如,我们可以这样做:

函数大写FirstLetter([first='',…rest]){return[first.toUpperCase(),…rest].join('');}大写首字母(“𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"

对于较长的字符串,这可能不是非常有效的***-我们实际上不需要迭代余数。我们可以使用字符串.原型.代码指向为了得到第一个(可能的)字母,但我们仍然需要确定切片应该从哪里开始。避免迭代余数的一种方法是测试第一个码点是否在BMP之外;如果不是,切片从1开始,如果是,切片从2开始。

函数大写首字母(str){if(!str)返回“”;const firstCP=str.codePointAt(0);常量索引=第一个CP>0xFFFF?2 : 1;return String.fromCodePoint(firstCP).toUpperCase()+str.slice(index);}大写首字母(“𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"

你可以用位数学代替>0xFFFF(0xFF关闭)在那里,但是用这种方式可能更容易理解,而且两者都会达到同样的目的。

如果必要的话,我们也可以在ES5及以下版本中进一步完善这个逻辑。ES5中没有用于处理代码点的内部方法,因此我们必须手动测试第一个代码单元是否是代理****:

函数大写首字母(str){if(!str)返回“”;var firstCodeUnit=字符串[0];if(firstCodeUnit<'\uD800'||firstCode Unit>'\uDFFF'){return str[0].toUpperCase()+str.slice(1);}return str.slice(0,2).toUpperCase()+str.slices(2);}大写首字母(“𐐶𐐲𐑌𐐼𐐲𐑉") // "𐐎𐐲𐑌𐐼𐐲𐑉"

深入国际化(谁的资本化?)

一开始我还提到了国际化的考虑。其中一些很难解释,因为它们不仅需要知识什么语言正在被使用,但也可能需要对语言中的单词有特定的知识。例如,爱尔兰有向图“mb”在单词开头大写为“mb”。另一个例子是德语eszett,它从不以单词开头(afaik),但仍然有助于说明问题。小写的eszett(“ß”)大写为“SS”,但“SS”可以小写为“ࢹ”或“SS”-您需要具备德语的课外知识才能知道哪一个是正确的!

这类问题中最著名的例子可能是土耳其语。在土耳其语拉丁语中,i的大写形式是is,而i的小写形式是In——它们是两个不同的字母。幸运的是,我们确实有办法解释这一点:

函数大写FirstLetter([first='',…rest],语言环境){return[first.toLocaleUpperCase(locale),…rest].join('');}大写首字母(“意大利”,“en”)//“意大利”大写首字母(“italya”,“tr”)//“伊塔利亚”

在浏览器中,用户最喜欢的语言标记由导航器.语言,可以在中找到按优先顺序排列的列表导航器.语言,并且给定DOM元素的语言可以(通常)通过对象(element.closest('[lang]')).lang||YOUR_DEFAULT_HERE在多语言文档中。

在ES2018中引入的支持RegExp中Unicode属性字符类的代理中,我们可以通过直接表示我们感兴趣的字符来进一步清理内容:

函数capitalieFirstLetter(str,locale=navigator.language){return str.replace(/^\p{CWU}/u,char=>char.toLocaleUpperCase(locale));}

这可以稍作调整,以处理字符串中多个单词的大写,至少在某些语言中具有相当好的准确性,但如果这样做,无论主要语言是什么,都很难完全避免出现离群情况。

这个CWU公司更改_何时更新character属性匹配在缺少特定区域设置数据的通用情况下大写时更改的所有代码点。您可能希望使用其他与案例相关的Unicode字符属性。这是一个很酷的探索区域,但如果我们在这里一一列举,我们会继续一整天。不过,如果你不熟悉,以下内容可以激发你的好奇心:\p{下}是一个比\p{小写字母}(又名\p{Ll})-方便图示由Unicode提供的此工具中的默认字符集比较.(注意:并非所有可以引用的内容都可以在ES正则表达式中使用,但您可能需要的大多数内容都是)。

JS中case-mapping的替代方案(Firefox和CSS喜欢荷兰!)

如果具有唯一区域设置/语言/正字法大写规则的有向图碰巧有一个Unicode中的单个码点“组合”表示,那么可以使用这些代码点来明确大写期望,就像组合的U+133 I-J有向图与荷兰语关联一样ij即使在缺少区域设置数据的情况下:

大写首字母(“ijsselmeer”);//“塞尔米尔”

另一方面,预合成的有向图和类似的图有时会被弃用(就像那样,看起来!),并且在互换文本中可能是不受欢迎的,因为如果人们在实践中不按常规方式键入序列,那么可能会产生复制糊麻烦。不幸的是,在缺少预组合“提示”的情况下,显式语言环境在这里没有帮助(至少据我所知)。如果我们拼写艾塞尔米尔和一个普通人+j,大写首字母即使我们明确指出,也会产生错误的结果荷兰作为区域设置:

大写首字母('ijselmeer','nl');//“Ijsselmeer”:(

(我不完全确定是否有某些情况下行为与ICU数据可用性有关——也许其他人可以这么说。)

不过,如果转换的目的是在web浏览器中显示文本内容,那么您可以选择一个完全不同的选项,这可能是您的最佳选择:利用web平台的其他核心语言HTML和CSS的功能。使用HTMLlang=。。。和CSS文本转换:。。。,您得到了一个(伪)声明性解决方案,为用户代理留出了额外的空间“聪明。”JS API需要在所有浏览器中都有可预测的结果(通常),并且不能自由地进行启发式实验。然而,用户代理本身只对其用户负责,当输出是针对人类时,启发式解决方案是公平的。如果我们告诉它“此文本是荷兰语,但请以大写形式显示”,现在不同浏览器的特定结果可能会有所不同,但这可能是每个浏览器所能做到的最好的结果。让我们看看:

<!DOCTYPE html><dl><dt>未转换伊斯塞尔梅尔<dt>以CSS和<code>lang=en大写ijsselmeer公司<dt>以CSS和<code>lang=nl大写ijsselmeer公司

在写作时,Chromium中的英语和荷兰语行都是伊塞尔梅尔-所以它并不比JS好。但在当前的Firefox中试试吧!我们告诉浏览器包含Dutch的元素将正确地呈现为伊杰塞尔梅尔那里。

这个解决方案是针对特定目的的(无论如何,它在Node中都不会对你有所帮助),但我之前没有引起人们的注意是愚蠢的,因为有些人可能没有意识到他们在谷歌上搜索到了错误的问题。感谢@paul23在实践中进一步澄清IJ有向图的性质并促使进一步调查!


截至2021年1月,所有主要引擎都实现了Unicode属性字符类功能,但根据您的目标支持范围,您可能还无法安全使用它。最后一个引入支持的浏览器是Firefox(78;2020年6月30日)。您可以使用Kangax兼容表.Babel可用于编译RegExp文本,其中包含对等效模式的属性引用,但要注意,生成的代码有时可能非常庞大。除非您确定这种权衡对于您的用例来说是合理的,否则您可能不想这样做。


问这个问题的人很可能与Deseret资本化或国际化无关。但是,意识到这些问题是件好事,因为即使这些问题目前并不令人担忧,最终你也很有可能会遇到它们。它们不是“边缘”案例,或者更确切地说,它们不是按定义边缘案例——在整个国家,大多数人都说土耳其语,将代码单元与代码点混为一谈是一个相当常见的错误源(尤其是表情符号)。字符串和语言都很复杂!


*UTF-16/UCS2的代码单元在某种意义上也是Unicode代码点,例如U+D800在技术上是一个代码点,但这不是它在这里的“意思”。。。某种程度上。。。尽管它变得相当模糊。但是,代理项肯定不是USV(Unicode标量值)。

**尽管如果代理代码单元是“孤立的”,即不是逻辑对的一部分,您仍然可以在这里获得代理。

***也许吧。我还没有测试过它。除非你确定资本化是一个有意义的瓶颈,否则我可能不会费劲——选择你认为最清晰易读的内容。

****这样的函数可能希望测试第一个和第二个代码单元,而不是只测试第一个,因为第一个单元可能是孤立的代理。例如,输入“\uD800x”将X as-is大写,这可能是预期的,也可能不是预期的。

分号
  • 7.2公里
  • 2
  • 31
  • 39