はじめに
この記事は 2023 年の MDN 翻訳 Advent Calendar 向けに作成したものです。
こんにちは。debiru です。連日 MDN 翻訳のアドベントカレンダーを書いていますが、私は Firefox 開発者でもなければ、MDN のコアメンテナでもありません。ただのいちコントリビューターです。
Firefox 開発者ではないので詳しいことは分からず、正確性に欠ける情報をお伝えしてしまうかもしれませんが、今日は私が知り得た Intl.Segmenter
の Firefox の実装状況についてお伝えしたいと思います。
JavaScript で文字列を逆順にする関数を書いて下さい
最初に考えるのは、String
に reverse
的なメソッドがないかということです。ありそうなものですが、実は JavaScript には String.prototype.reverse
はありません。
ということで、これを実現するには、String
を配列的に扱って逆順にする処理を施すということになります。
配列化して逆順にする
function reverse(str) {
return str.split('').reverse().join('');
}
通常の文字列ならこれでも十分に題意を満たせるのですが、Unicode 文字には色々と種類があって、合字(複数のコードポイントの組み合わせで 1 つの文字を表す文字)が含まれている場合には、これだとうまくいかないケースがあるのです。
考慮すべき文字について
うまくいかないケースとなる文字は以下の Examples にいくつか示されています。
// plain latin alphabet - nothing spectacular
splitter.splitGraphemes('abcd'); // returns ["a", "b", "c", "d"]
// two-char emojis and six-char combined emoji
splitter.splitGraphemes('🌷🎁💩😜👍🏳️🌈'); // returns ["🌷","🎁","💩","😜","👍","🏳️🌈"]
// diacritics as combining marks, 10 JavaScript chars
splitter.splitGraphemes('Ĺo͂řȩm̅'); // returns ["Ĺ","o͂","ř","ȩ","m̅"]
// individual Korean characters (Jamo), 4 JavaScript chars
splitter.splitGraphemes('뎌쉐'); // returns ["뎌","쉐"]
// Hindi text with combining marks, 8 JavaScript chars
splitter.splitGraphemes('अनुच्छेद'); // returns ["अ","नु","च्","छे","द"]
// demonic multiple combining marks, 75 JavaScript chars
splitter.splitGraphemes('Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞'); // returns ["Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍","A̴̵̜̰͔ͫ͗͢","L̠ͨͧͩ͘","G̴̻͈͍͔̹̑͗̎̅͛́","Ǫ̵̹̻̝̳͂̌̌͘","!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞"]
その他、関連する記事を示しておきます。
- https://github.com/flmnt/graphemer/tree/1.4.0#examples
- Unicode 絵文字にまつわるあれこれ (絵文字の標準とプログラム上でのハンドリング)
- JavaScript 文字列を反転させたい。結合文字とサロゲートペア対応。
- 文字列を反転させたい|nona
- JavaScript has a Unicode problem · Mathias Bynens
考えられる色々な reverse 関数の実装方法
const functions = [
function reverse_1(str) {
return str.split('').reverse().join('');
},
function reverse_2(str) {
return [...str].reverse().join('');
},
function reverse_3(str) {
const rev = s => {
const m = s.match(/^(.)(.*)(.)$/u);
return m ? m[3] + rev(m[2]) + m[1] : s;
}
return rev(str);
},
function reverse_4(str) {
let ret = '';
for (const segment of new Intl.Segmenter().segment(str)) {
ret = segment.segment + ret;
}
return ret;
},
];
なお、これらの実装を考える上で、次のツイートを参考にさせていただきました。
これらのコードを CodePen に上げてみました。
reverse_1
, reverse_2
, reverse_3
は合字の絵文字(絵文字に限らないですが)が含まれると reverse 処理に失敗しています。
reverse_4
では Intl.Segmenter
の力を借りることで、見事に全てのテストケースで reverse 処理を成功できました。
余談:Intl.Segmenter
を使わずに成功させる方法
合字を含めた Unicode 文字をうまいこと 1 文字ずつ分離するためのライブラリが存在しています。
上記のライブラリでは Intl.Segmenter
を使わずに実装されています。ではどうしているのかというと、上記のリンクで示している通り、コードポイントごとに条件分岐を地獄のように書いて処理しています。考えられる最もナイーブな実装ということでしょうか。
Intl.Segmenter
を使うと、地獄のような実装をせずともやりたいことが実現できるようになります。
Intl.Segmenter とは
をお読みいただだければ概要が分かるでしょう。入力文字列に対して、形態素解析をしたり、文字を解析したりすることができるものです。
Intl.Segmenter
の歴史については、次の記事が参考になります。
そんな便利な Intl.Segmenter
は、Chrome, Safari, Edge, Opera では 2020 年 11 月から 2021 年 4 月にかけてサポートされ、もちろん Vivaldi でも使えるものの、Firefox だけサポートされていないという状況でした。
Bugzilla の Intl.Segmenter
の導入を扱うレポートも、2020 年頃から実装の見込みがない様子で止まっていました。
それが、なんと 2023 年の 5 月頃から動きが見られ始めました。そして 12 月現在ではなんだかコメントがいっぱいついて、実装が進んでいるような雰囲気が窺えます。
まだ、リリースの目処は立っていないようですが、近々、Firefox でも Intl.Segmenter
が使える日がやってきそうに思います。……というお話でした。
さいごに
ど、どうしよう、そろそろ MDN のネタが尽きてきました。2023 年の MDN 翻訳 Advent Calendar は、まだこんなに空いているというのに。もしこの記事を読んでくれている MDN 関係者さんがいたら今からでも全然遅くないので、ぜひアドベントカレンダーに登録して 1 つくらい記事を書いてみてもいいんじゃないでしょうか><
本当はこの記事、もうちょっと正確な実装状況を調べてご報告するつもりでしたが、関連するレポートやら何やらを読んでも実装状況が私には理解できなくてご紹介できませんでした……。
少しでも面白いなと思ってくれたら嬉しいです。というわけで、Intl.Segmenter
のお話でした。
おわり。