本記事は下記のtweetから始まるスレッドに触発され、@qnighyや@na4zagin3からアイディアを拝借して書いた。
i18n力が最強の国は国内に複数の言語があり、そのうちいくつかは他国でも使われている言語の方言で、1バイト文字での代替表記が困難で、歴史的にISO-2022ベースの文字コードとUnicodeと独自エンコーディングが混在していて、フリガナなどの特殊な組版規則があり、右書き左書き縦書きを併用し、
— Masaki Hara (@qnighy) 2018年8月6日
皆さんのおかげで最強のi18n国家が建設されつつある。一瞬で滅びそう
— Masaki Hara (@qnighy) 2018年8月6日
長い前置き
ソフトウェアのi18nは難しい。自文化では当たり前と思っていてハードコードしてしまった仮定が崩れて、大幅な再設計を余儀なくされるからだ。気づいて再設計できればまだ良くて、気づかずに出荷してユーザビリティに深刻な問題を引き起こしたり、セキュリティ上の問題を生んだり、国際問題になったりする。
逆に、たとえ難しいi18n上の問題であっても自文化で見聞きしたことがあれば理解は容易だし、備えようという気にもなるし、ある程度勘も働くから変なバグを作り込みにくくなる。
例えば日本で暮らしていると文字エンコーディングとタイムゾーンの話が対照的だ。
最近でこそUTF-8が主流になったとは言え日本では長らくISO-2022-JPとEUC-JPとShift_JISとこれらの変種がそれぞれ使われており、「世の中にはエンコーディングというものが存在するし取り扱いを間違えると文字化けする」という程度のことは文字列処理を書く人ならみんな知っている。
一方日本に暮らして国内向けの製品を書いているとタイムゾーンとは向き合わなくても済むので、ついついタイムゾーン情報を捨てて時刻を操作するコードを書いたりしがちだ。私も随分そういうコードを書いた。
USの開発者は正反対の状況にいた。USには複数のタイムゾーンがあってユーザーの入力や期待される出力がどれになるかは分からないので、その処理には慣れている。でも、Ruby 1.8からRuby 1.9への移行でRubyの文字列オブジェクトがエンコーディングを持つようになったとき、結構混乱したユーザーもいたようだった。今まで1byte=1charだと思って処理してきたし、何も考えずに文字列を結合できたのになんで突然Encoding::CompatibilityError
が出るようになったんだ? 1
ここから考えられるのは、もしあらゆる軸で最強に他国と互換性を欠く文化の国があったとすれば、その国出身の開発者は自然にi18nのあらゆる勘所を体得していて変な設計を無意識に避けていける最強のi18nエンジニアな訳だ。
それはどんな国だろう。
言語
- 国内に複数の言語がある
- インドとか。
- 国内市場向けなら言語を決め打ちできるという仮定が成立しない。複数の公用語を平等にサポートするように法律で規定されている場合もあり、「割り切ってスモールスタート」もできない。
- 国内でも複数の言語をサポートする必要があるので、間違いなくエンドユーザー向けメッセージをソースコード内に埋め込む習慣はないだろう。最低でもGNU gettextのような仕組みでメッセージはカタログから引く仕組みを自然に書いている筈だ。
- 同じ開発チーム内でも話す言語が異なるのが普通なので自分の母語でコードにコメントを書く習慣はないだろう。主たる公用語で書くか、英語で書くと思われる。インドとかそんな風だと聞いたことがあるような。
- USの会社のi18nにありがちな、英語版webページと多国語版webページでURL体系が異なるみたいなものもないだろう。
- 他国で使われている言語の方言が使われている
- ブラジルのポルトガル語(pt-BR)とか。
- 言語と国が1:1に対応しないのをよく知っているので、UI言語の選択肢を国旗で表現する悪習は身につけていないだろう。
- ISO 639-1だけでは言語を表現しきれないのを知っていて、 IETF language tagをちゃんと処理する習慣を持っているだろう。
- 敬称が複雑に変化
- 英語とか。相手の性や既婚・未婚により敬称が変化する。
- 相手の属性を知らなければ相手を呼べないので、単純な文字列置き換えによる多言語対応が無理なのを知っている筈だ。他言語対応では、究極的には言語依存のview helperを持たなければ単純なplain textすら生成できないことを認識している。
- 単数, 双数, 複数
- 数が1個(単数), 2個(双数), 3個以上(複数)の場合でそれぞれ名詞の形や対応する形容詞・動詞の形が変化するので、単純に
"%d個の商品があります"
のようなformat templateを書くことはないだろう。自然にview helperで文字列を生成するようになるだろう。 - ちなみに、0個の場合を単数, 双数, 複数のどれで扱うかは言語や地域によって異なる
- FizzBuzzみたいなコードを書かないと判別できない場合もあり、個数表現をテンプレートで処理するのは一般には無理に近い
- 数が1個(単数), 2個(双数), 3個以上(複数)の場合でそれぞれ名詞の形や対応する形容詞・動詞の形が変化するので、単純に
- 動詞の屈折
- 主語の性や数により動詞が変化するので、view template1つ書くのも容易ではない。
<%= user_list.join(",") %>が入室しました
というテンプレートの「入室しました」の部分がuser_list
の内容によって変化する訳だ。
仕方がないのでhelper関数を呼び出して、最低でもセンテンス単位で文字列を生成することになるだろう。
- 主語の性や数により動詞が変化するので、view template1つ書くのも容易ではない。
- 相対敬語を持つ
- 聞き手が誰であるかに依存して、特定の主語に対する敬語の使い方が変化する。このため、コピペで似たメッセージ文を使い回す習慣がなくなるだろう。
- 複数の言語で単語の長さが極端に違う
- たとえば、中国語や日本語に合わせた画面デザインでは、ドイツ語表記が領域内に収まらなくてボタンに何が書いてあるのか読めなくなることがある。逆にドイツ語に合わせたデザインでは、中国語UIがスカスカで不自然になることがある。
- 単純に翻訳するのではなく画面全体のバランスを考えて翻訳できるように、UI翻訳者に依頼するときに画面レイアウト画像を添付し機能の意図や文字数制限のメモを渡すような良い習慣が定着しているだろう。
- 最悪の場合、言語ごとにアプリのレイアウトを個別に設計することもあり、レイアウトを差し替え可能なように設計しているかもしれない。
文字と組版
- 1バイト文字での代替表記が困難
- 日本語とか。
- 1バイトずつ読めば1文字ずつ処理できるという古いコードにありがちな仮定が成立しない。
- 文字境界を意識して、文字列検索時に意図せぬ部分マッチを避ける習慣が身についているだろう。
- ラテンアルファベット表記が困難だと、まずi18nバグを報告しても相手がバグの内容を読めないので理解してもらえないという経験を積める。このためi18nバグをうまく説明するスキルが自然に身についていく。
- 歴史的にISO-2022ベースの文字コードとUnicodeと独自エンコーディングが混在
- Unicodeは基本として、まず現存するデータが単一の文字エンコーディングになっているという仮定が崩壊する。入力時にUnicodeに変換するか、もしくは文字列データにエンコーディングのメタデータを付ける処理を行うだろう。
- ISO-2022でまず文字列処理がstatefulになる。「特定の文字を表すバイト列を読めば、それが何の文字か分かる」という仮定が崩壊するので、文字列先頭から丁寧に処理するアルゴリズムを書くようになるだろう。
- 更に独自エンコーディングがあって、他の規格と異なる固有のエンコーディング処理実装を併存させる必要がある。Shift_JISの5C問題のようにセキュリティホールを作ることもある。文字列処理はセキュリティホールになりえるという基本的認識が身につくだろう。
- Unicode未収録文字
- 知らないと「面倒くさいからUnicodeで良いのでは?」と思うかもしれない。住基ネットのようにUnicode未収録文字を扱っていると、人は結局は用途に応じた文字集合やエンコーディングの使い分けに向き合わなければならないことを知るだろう。Rubyならできるよ。
- 特殊な組版規則
- 日本語とか。
- 日本語組版の要件にまとまっているように、フリガナやら縦中横やらがあると「文章の表示とは文字を横に並べることである」「スタイル情報がない限り文字のサイズは一定で良い」という仮定が崩壊する。特にフリガナは日本語組版の要件 図3.70にあるように振られた範囲がややこしいこともあるので、単純にカッコ書きで代替はできない。日本語に関して言えば禁則処理やら更にいろいろな話題がある
- この文化出身だと、組版というのはすごく専門的で難しいものだという認識を自然に持つだろう。決して自分で実装しようとせずに、素直に既存ライブラリの肩に乗ることを考えるようになる。
- 分かち書きしない表記法
- 日本語とかタイ語とか。分かち書きしないので「空白文字のところを改行候補として字間を調整する」アルゴリズムが機能せず、自然に組版処理が難しい分野だと言うことを認識するはずだ。日本語は行分割可能点のルールが単純で、文字種から決定できるが、タイ語では形態素解析が必要になる。
- 「空白文字区切りで単語を識別できる」という仮定が成立しないので、ごく簡単な文字列処理をするだけでも形態素解析というジャンルを知ることができる。
- 日本のSlackユーザーがよく知っている
https?://[^ ]+
がURLだと仮定すると後続の文章までURL扱いされるやつも、正しく処理できる
- 右書き、左書き、縦書きを併用する
- アラビア語が右から左なのは有名。日本語や中国語は上から下にも書く
- 右書き、左書き、縦書きが全部混在する言語があるという話は聞いたことがない。日本語の縦書きの本でアラビア語に言及すると少しはそういうこともあるけれど、日常的ではない。古代言語では牛耕式や下から上の書き方もあったそうだが、現存しないので現代のi18nとしては一般的な要件ではないだろう。
- 左右書きを併用する文化出身だと、画面デザインの際に左右反転しても見やすいかどうかを自然に気に掛けるようになるだろう。(アラビア語に対応しようとして左右反転デザインにすると画像の配置のバランスが悪くなったり、想定していなくて画面が崩壊するのはありがちなミス)
- 縦書きを併用する文化出身だと、文字レンダリングの際に画面を最初に行分割してから割り付ける方式以外も気に掛けるだろう。
-
リガチャ
- ヨーロッパ諸語とか
- Fontには文字単位でグリフを収録すれば良いという仮定が崩壊する
- 一文字ずつ見て順番にレンダリング/印字すれば良いという仮定が崩壊する
- たぶんUnicodeに合成済みの文字も採録されていて、Unicode正規化を自然に意識するようになるかもしれない。
- 文字がたくさんある
- 日本語や中国語など
- Fontを気軽にデザインしたりアプリに埋め込んだりダウンロードしたりできるという仮定が崩壊する
- この文化出身だと、雰囲気を出すために専用デザインのFontを埋め込むという無茶を安易に試そうと思わないだろう
- 文字種が混在する
- かな漢字アルファベット混じり文など
- 特定の文字種のFontは他の文字種のFontと独立して選定できるという仮定が崩壊する
- うっかりするとアルファベット用のフォントデザインは他の文字を使う言語に影響しないという仮定を措きがちだが、この文化出身だとそれは避けられるだろう。同一文中でアルファベットのデザインとかな漢字のデザインが一貫しないミスを避けられる。
- 他言語と字形の異なる文字を同じコードポイントで共有
- 日本語と中国語とか。CJK統合漢字は日中韓に共通する漢字をどの言語でも同じコードポイントで表現することを意図しているが、あいにくとこれらの言語では漢字のグリフがかなり異なる場合がある。
- 「文字とフォントが定まればグリフが定まる」という仮定が崩壊するので、自然と文字列や文書に言語タグ情報をメタデータとして付ける習慣を身につけるだろう。
- 同じ文字でもソート順が異なる複数の言語が使われている
- ドイツ語とスウェーデン語など。
- Intl.Collatorなどを用いた適切なソートを実装するようになる。
- 表記された文字ではなく発音によって文字列をソートする習慣がある
- 日本語。表記された文字だけを見てもどちらを先に並べるべきか分からない。最低でも辞書データを引くか、最悪の場合辞書を引いても文字列の発音が分からない。
- 表記だけではなく発音もメタデータとして保持する習慣がつく。
暦法と時間
「タイムゾーン呪いの書」も参照のこと。
- 独自の太陰暦
- グレゴリオ暦とはどんどん年がずれていくので、日付換算が複雑になる。単純にYYYY-mm-ddみたいな文字列に当てはめれば日付表記を生成できるという仮定から自由になれる。
- 20世紀以降に暦法を変更した
- 比較的最近の日付でもX年のY月Z日が(X-1)年Y月Z日の1年後とは限らない。このため、きちんと日付表記を暦法データベースと照合して解析し、正しく処理する習慣が身につくことだろう。
- 週の始まりが土曜日
- 週の始まりの曜日は月曜日とする地域や日曜日とする地域が多いが、いっその事イスラム圏の一部のように土曜日始まりだとこの問題を強く意識するようになるだろう。カレンダー表示を実装するときや週次売上集計などを実装する際、設定を切り替える機能をつけ忘れなくなる。
- 国内に複数のタイムゾーンがある
- ユーザープロファイルに必ずタイムゾーンを記録するようにして、時刻入力を受け付けるときには入力者のプロファイルと見比べたり、別途タイムゾーンを入力させたりするUIを作るようになるだろう。
- UTCからのオフセットが12時間以上
- うっかりするとタイムゾーンはUTC-12からUTC+12だと誤解しそうだが、キリバスなどの例を正しく処理するコードが書けるようになる
- UTCからのオフセットが1時間未満の端数を持つ
- うっかりするとタイムゾーンのオフセットは1時間が最小単位だと思い込みがちだが、ネパールなどのタイムゾーンを正しく処理するコードが書けるようになる。なお、仕様上はオフセットの最小単位はナノ秒である。
- サマータイムが途中から導入された
- 季節によってタイムゾーンが変化するため、地域ごとにタイムゾーンを固定するようなコードを書かなくなる。
- タイムゾーン切り替え時に表記上は同じになる時刻が繰り返される(サマータイム終了時に01:59の次は01:00になる)ので、時刻を素朴に整形した値が単調増加であるという間違った仮定に依存しなくなるだろう。バックアップファイルを上書きしたり夜間バッチが走らなくて死ぬのを避けられる。
- また過去にはサマータイムが適用されなかった年が存在するので、時刻表記のparseをハードコードできなくなる。よって、季節だけでなく年も含めて正しくデータベースを引いてタイムゾーンを選択するコードを書けるようになる。
- サマータイムの時期は毎年発表される
- もはやサマータイムデータベースを埋め込んでおくこともできないので、通信するなり保守要員が現地に行くなりしてデータベースを更新する運用が計画されている。
- この文化出身だとtzdataを更新する運用を組むのは自然なので、他国のタイムゾーンが変更されたり国が分裂したりしても難なく対応できる。
地理
- 街区方式と道路方式の住所システムが混在
- 日本とかイギリスとか。
- 地域によって「X丁目Y番Z号」方式の表記と「ベイカー通り221B番」や「寺町通御池上る」のような道路方式が混在しているので、住所表記解析器を書くときに異なる実装を使い分けることを想定できる
- 道路方式の感覚で道案内を書くと街区方式の地域ではユーザーが道に迷うので、適切な方法を選択して案内したりサイトに地図をマメに埋め込む習慣ができるだろう。
- 隣国との係争領域がある
- 自国版の地名表記のまま輸出したりwebサイトを提供したりすると炎上するので、機器が利用されている地点やアクセス元IPに応じて地名データベースを使い分ける習慣が身につく
- 純粋なビジュアルデザインでも、国境線を引くときは市場を意識するようになる
- 首都領域が法定行政区画としては存在しない。
- 日本の首都は「東京」「京都」「未規定」など諸説あるが、仮に「東京」だとしても「東京」が意味するものは謎だ。東京都では広すぎるし東京市は既に存在せず、適当な行政区画がない。シドニー(州都)も同様である。このため、国から首都への対応関係を保持するRDBテーブルにおいて、行政区画テーブルの主キーを参照しようとするとまずいことになる。
- こういう文化出身だと、行政区画と都市を区別してモデリングする習慣が付く。
- 用途によってはIATA都市コードが比較的近いのだが、これが振られていない都市もあるので難しい。
- 非法人地域を持つ
- 比較的歴史の長い国家では領土内の任意の地点が何らかの自治体に属するが、USなどではそうではない。こういった文化出身の場合、住所をグルーピングする目的で安易に自治体名を使うようなコードは書かないだろう。
- 選挙区や学区が住所体系や行政区画と直交
- 広告ターゲティング目的ではこういった体系が住所や行政区画より便利なことがあると聞いたことがあるような気がする。
- こういう文化出身だと、マーケティングや広告向けのシステムで地域指定処理を書くときには住所以外の体系をサポートするのを忘れないようになるだろう。
その他
- 華氏とヤードポンド法
- 世界的には摂氏とメートル法が主流なのに自国はそうではないせいで、温度、距離、重量の単位を切り替える機能を忘れずに付けられるようになる。USの製品にはよくある機能。
- 数字表記が12.34.567,89 (小数点がカンマ, 桁区切りがピリオド)
- 日本では1,234,567.89のように書くが、フランスのように1.234.567,89と書く地域もある。インドのように桁区切りの単位が途中で変化する地域もある。
- 数値を整形するときに、固定テンプレートを使ったり言語組み込みのデバッグ目的の簡易文字列化メソッドを呼んだりするのではなく、Locale依存のテンプレートを利用するのを忘れないようになる
- 数値部分を文字列から切り出すとき、固定の正規表現をコードに埋め込むミスもなくなっているだろう。
- 通貨単位を前置する言語と後置する言語がある
- 実例としては、英語では前置、フランス語では後置
- 姓を持たない氏族がいる
- 姓名入力欄を作る際に姓を必須にするミスをしなくなる
- 任意個の父称やミドルネームを名乗る氏族がいる
- 姓名入力欄を作る際に半端な文字数制限を実装してしまうミスを避けられる
- 名(given name)がたくさんある
- もはや姓だけでも名だけでも、ミドルネームを除いた名の部分ですら長過ぎて表示スペースに収まらない可能性がある。そこで、ユーザープロファイルにDisplay NameやNicknameを記録できるような設計を忘れないだろう。
- 姓名の変更が届け出制
- ユーザーが自分で姓・名を変更できるUIを付けるのを忘れなくなる。
- 東南アジアにそんな例があったと思うんだけど、届け出制なため、中二病に掛かると3回ぐらい格好いい名前に変更するとか聞いたような気がする。姓名の変更が稀だと安易に仮定してUIを付けないでおくと、ユーザーサポートがパンクする
- 性別の変更手続きが存在する
- 日本とか西欧諸国とか
- ユーザーが自分で性別を変更するUIを付けるのを忘れなくなる
- 性別が原則的に変わらないと仮定してUIを付けなかったり使いもしないのに性別情報を収集すると、ユーザーサポートに無駄な負荷が掛かる
- Excelの関数名がローカライズされている
- 例はフランスだったかな?
- Excelの関数名が英語版とは異なるので、Excelファイル生成時には固定のひな形ファイルを利用できない。
- コマンド名がローカライズされている
- ショートカットキーに対応する文字が統一できない
- メニュー上の順番も異なるためGUIの場合アイコンでしかコマンドを識別できなくなる
あとで書くかも
- 外国人が文字集合を整備したので不十分な点があり、規格が混乱している
- 国際規格があるからそれに従って文字符号化できると思った?
- グリフは同一だが支配者一族を表記するための専用の文字がある
- 文章の文脈を読んで補正しないとOCRや他言語からの翻訳に失敗して、不敬罪的なもので処刑されるのでは。
- 複数の小文字が1つの大文字に対応する
- たとえばドイツ語のß = SS / ssもそう
- ドイツ語のßに対応する大文字として慣習的にSSが使われるが逆変換ができない。2017年に言語がバージョンアップして大文字ẞが正式に足された。
- 対応する大文字が存在しない小文字がある
- ドイツ語のエスツェット(大文字も最近定義されつつはあるらしい)
- 文字符号の順序と人間向けのソート順は異なる
- 文字ごとに文字符号を比較して辞書式にソートしても見やすくない。
- ソート順に複数の文字を単一文字とみなしてソートする場合がある(例: スペイン語のchはcの後でdの前)
- キー入力イベント列だけでは入力された文字を特定できない(IME)
- OSごと、インストールされているアプリケーションごと、その時点までに学習したデータごとに、キー入力シーケンスが意味する文字が変わるんだ。
- 文字入力中にEnterやShift+Enterが交じる(IME)
- EnterやShift+Enterが入力されたらchatメッセージを送信したり次のフィールドにフォーカスを移すUIが親切だと思った? 残念。未変換のカナが送信されました。
- 文字入力方式とキーボードのレイアウトが独立(IME)
- キーボードのどのキーをどの符号に割り当てるかと、どんな符号列をどんな文字列として認識するかは全く独立した話題なんだ。ドイツ語キーボード対応するのと同じ感覚で一つのドロップダウンメニューで選択させようとすると死ぬ。つまり、IMEを選択する機能はキーボードを選択する機能とは別につける必要がある。
- 10進法でない通貨補助単位が存在する
- 不定期に暦の紀元が切り替わる(元号)
言い訳とお願い
i18nがすごく専門的で難しい分野なのは知っていて、自分がそれに精通しているという認識はない。
本稿は業務上実際に悩まされた問題と、私が本で読んだことがある知識と、どこかで聞いたことがある話題が混在しており、情報精度にはばらつきがある。
このため、間違いを見つけたら是非とも編集リクエストをいただけるとありがたい。
-
本当は、今まで知らずに文字化けしていたのを検出できるようになっただけなんだけど ↩