読者です 読者をやめる 読者になる 読者になる

toLowerCaseとtoUpperCaseはトルコだと文字化けするらしい

Tumblrからの移行記事1

先日仕事中に仕事をせずにAngularJSのソースを眺めてたところ、↓のようなメソッドがありました。

var lowercase = function(string){return isString(string) ? string.toLowerCase() : string;};
var uppercase = function(string){return isString(string) ? string.toUpperCase() : string;};

単純にtoLowerCaseやtoUpperCaseを呼んでアルファベットを小文字や大文字に変更して返しているだけです。
さらにその次には↓こんなのもありました。

var manualLowercase = function(s) {
  return isString(s)
      ? s.replace(/[A-Z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) | 32);})
     : s;
};
var manualUppercase = function(s) {
 return isString(s)
      ? s.replace(/[a-z]/g, function(ch) {return fromCharCode(ch.charCodeAt(0) & ~32);})
     : s;
};

これも文字コードを足したり引いたりして、アルファベットを小文字や大文字に変更して返しているだけです。

この時点で、最初の2つとmanualXXXの2つは同じ結果返すのに、なんで両方定義されてるんだろうと思ったところ、次にその理由が書いてありました。

// String#toLowerCase and String#toUpperCase don’t produce correct results in browsers with Turkish
// locale, for this reason we need to detect this case and redefine lowercase/uppercase methods
// with correct but slower alternatives.
if (‘i’ !== ‘I’.toLowerCase()) {
  lowercase = manualLowercase;
  uppercase = manualUppercase;
}

トルコのブラウザだとStringのtoLowerCaseとtoUpperCaseは正しい結果を返してくれないよと。その対応としてmanualXXXを定義して、トルコの場合はそっちに処理を置き換えてますよってことらしいです。
トルコかどうかの判定で、小文字の i と大文字の I を比べてるので、少なくともこれはtoLowerCaseだ正しく変換されないってことなんでしょう。

もうちょっと詳しく知りたいと思い調べてたところ、隣の席の人が見つけてくれたこちらのページにその説明が書かれてました。

f:id:yuji0316:20130407155404g:plain

Dotless-i, is a lowercase ‘i’ without dot. The uppercase of this character is the usual “I”. There is another character, “I with dot”. The lowercase of this character is the usual lowercase “i”.

この絵の右上の I と左下の i が普通のアルファベットで、左上の i(点無し) と右下の I(点有り) がトルコ独自(?)の文字のようです。 そしてアルファベットの I をtoLowerCaseすると i(点無し) になってしまい、 同じくアルファベットの i をtoUpperCaseすると I(点有り) になってしまうと。

トルコの人にとってはこの変換のほうが違和感無いってことなんでしょうかね。

そして実はJavadocにもこれに関する注意が書いてありました。

public String toLowerCase() デフォルトロケールの規則を使って、この String 内のすべての文字を小文字に変換します。これは、toLowerCase(Locale.getDefault()) の呼び出しと等価になります。

注: このメソッドはロケールに依存するため、ロケールが単独で解釈されるような文字列に使用すると、予期せぬ結果を引き起こすことがあります。例として、プログラミング言語識別子、プロトコルキー、HTML タグがあります。たとえば、トルコ語ロケールの "TITLE".toLowerCase() は "t?tle" を返します。ここで、「?」は点のないラテン小文字の I の文字です。ロケールに依存しない文字列の正しい結果を取得するには、toLowerCase(Locale.ENGLISH) を使用します。

昔からあるメソッドなのにこんなことも知らなかったとはお恥ずかしい。。。

じゃぁAngularJSはmanualXXXだけあればいいじゃんとも思うのですが、 わざわざ2つあるってことは、toLowerCaseの方が速いんですかねぇ。

とりあえず、国際化対応とかしてる方は気をつけましょう。