Firefox だけ未実装シリーズ：Intl.Segmenter の状況

# はじめに

この記事は [2023 年の MDN 翻訳 Advent Calendar](https://qiita.com/advent-calendar/2023/mdn-translation-community-japan) 向けに作成したものです。

こんにちは。debiru です。連日 MDN 翻訳のアドベントカレンダーを書いていますが、私は Firefox 開発者でもなければ、MDN のコアメンテナでもありません。ただのいちコントリビューターです。

Firefox 開発者ではないので詳しいことは分からず、正確性に欠ける情報をお伝えしてしまうかもしれませんが、今日は私が知り得た `Intl.Segmenter` の Firefox の実装状況についてお伝えしたいと思います。

# JavaScript で文字列を逆順にする関数を書いて下さい

最初に考えるのは、`String` に `reverse` 的なメソッドがないかということです。ありそうなものですが、実は JavaScript には `String.prototype.reverse` はありません。

ということで、これを実現するには、`String` を配列的に扱って逆順にする処理を施すということになります。

## 配列化して逆順にする

```js
function reverse(str) {
  return str.split('').reverse().join('');
}
```

通常の文字列ならこれでも十分に題意を満たせるのですが、Unicode 文字には色々と種類があって、合字（複数のコードポイントの組み合わせで 1 つの文字を表す文字）が含まれている場合には、これだとうまくいかないケースがあるのです。

## 考慮すべき文字について

うまくいかないケースとなる文字は以下の Examples にいくつか示されています。

https://github.com/flmnt/graphemer/tree/1.4.0#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('Ĺo͂řȩm̅'); // returns ["Ĺ","o͂","ř","ȩ","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̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞'); // returns ["Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍","A̴̵̜̰͔ͫ͗͢","L̠ͨͧͩ͘","G̴̻͈͍͔̹̑͗̎̅͛́","Ǫ̵̹̻̝̳͂̌̌͘","!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞"]
```

その他、関連する記事を示しておきます。

- https://github.com/flmnt/graphemer/tree/1.4.0#examples
- [Unicode 絵文字にまつわるあれこれ (絵文字の標準とプログラム上でのハンドリング)](https://qiita.com/_sobataro/items/47989ee4b573e0c2adfc)
- [JavaScript 文字列を反転させたい。結合文字とサロゲートペア対応。](https://qiita.com/standard-software/items/24a8cc99171f988e4523)
- [文字列を反転させたい｜nona](https://note.com/ttuusskk/n/n6f874b0274bd)
- [JavaScript has a Unicode problem · Mathias Bynens](https://mathiasbynens.be/notes/javascript-unicode)

## 考えられる色々な reverse 関数の実装方法

```js
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;
  },
];
```

なお、これらの実装を考える上で、次のツイートを参考にさせていただきました。

https://twitter.com/petamoriken/status/1501461305257259009

これらのコードを CodePen に上げてみました。

https://codepen.io/arcxor/pen/qBgLpar

![上記 CodePen を Chrome で表示した結果です](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178936/57b22bc0-7bdf-4cde-10a0-76005b97dfc8.png)

`reverse_1`, `reverse_2`, `reverse_3` は合字の絵文字（絵文字に限らないですが）が含まれると reverse 処理に失敗しています。

`reverse_4` では `Intl.Segmenter` の力を借りることで、見事に全てのテストケースで reverse 処理を成功できました。

## 余談：`Intl.Segmenter` を使わずに成功させる方法

合字を含めた Unicode 文字をうまいこと 1 文字ずつ分離するためのライブラリが存在しています。

- https://github.com/flmnt/graphemer
    - https://github.com/flmnt/graphemer/blob/1.4.0/src/Graphemer.ts#L113
- https://lodash.com/docs/4.17.15#split
    - https://github.com/lodash/lodash/blob/4.17.15/lodash.js#L205

上記のライブラリでは `Intl.Segmenter` を使わずに実装されています。ではどうしているのかというと、上記のリンクで示している通り、コードポイントごとに条件分岐を地獄のように書いて処理しています。考えられる最もナイーブな実装ということでしょうか。

`Intl.Segmenter` を使うと、地獄のような実装をせずともやりたいことが実現できるようになります。

# Intl.Segmenter とは

https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Intl/Segmenter

をお読みいただだければ概要が分かるでしょう。入力文字列に対して、形態素解析をしたり、文字を解析したりすることができるものです。

`Intl.Segmenter` の歴史については、次の記事が参考になります。

https://sbfl.net/blog/2020/11/21/javascript-intl-segmenter/

そんな便利な `Intl.Segmenter` は、Chrome, Safari, Edge, Opera では 2020 年 11 月から 2021 年 4 月にかけてサポートされ、もちろん Vivaldi でも使えるものの、Firefox だけサポートされていないという状況でした。

![ブラウザー互換性テーブル。Chrome, Edge, Opera, Safari がサポートする中、Firefox だけがサポートされていない。](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/178936/d49081ab-6ec5-110a-9dd8-f2ddcd0b73de.png)

Bugzilla の `Intl.Segmenter` の導入を扱うレポートも、2020 年頃から実装の見込みがない様子で止まっていました。

https://bugzilla.mozilla.org/show_bug.cgi?id=1423593

それが、なんと 2023 年の 5 月頃から動きが見られ始めました。そして 12 月現在ではなんだかコメントがいっぱいついて、実装が進んでいるような雰囲気が窺えます。

まだ、リリースの目処は立っていないようですが、近々、Firefox でも `Intl.Segmenter` が使える日がやってきそうに思います。……というお話でした。

# さいごに

ど、どうしよう、そろそろ MDN のネタが尽きてきました。[2023 年の MDN 翻訳 Advent Calendar](https://qiita.com/advent-calendar/2023/mdn-translation-community-japan) は、まだこんなに空いているというのに。もしこの記事を読んでくれている MDN 関係者さんがいたら今からでも全然遅くないので、ぜひアドベントカレンダーに登録して 1 つくらい記事を書いてみてもいいんじゃないでしょうか＞＜

本当はこの記事、もうちょっと正確な実装状況を調べてご報告するつもりでしたが、関連するレポートやら何やらを読んでも実装状況が私には理解できなくてご紹介できませんでした……。

少しでも面白いなと思ってくれたら嬉しいです。というわけで、`Intl.Segmenter` のお話でした。

おわり。