実装Read this article in English

翻訳したテキストがUIからはみ出す理由と、本当に測るべき指標

英語のラベルにぴったり合わせたボタンは、「他の言語も同じくらいの長さになるだろう」という賭けをしていることになります。この賭けはほぼ確実に負けます。ある言語は同じ意味を表すのにより多くの文字数を必要とし、別の言語では文字数自体は減っても1文字あたりの表示幅が広がる。どちらの方向にせよ、1言語でしか検証していないレイアウトは崩れます。

伸びる方向と縮む方向は対称ではない

テキストの伸び縮みは言語の組み合わせと元の文字列の長さによって変わります。短い文字列ほど、比率としては伸びやすい傾向があります。冠詞や助詞、活用語尾といった文法上の固定コストは、短いラベルほど相対的に効いてくるためです。ドイツ語の複合語や格変化のように、同じ意味でもより広い横幅を必要とすることでよく知られる言語もあります。

CJK言語は逆方向に働くことが多いです。日本語や中国語の訳文は、英語の原文より文字数としては少なくなることがよくあります。漢字1文字で英単語1語ぶんの意味を担えるためです。しかし、そのそれぞれの文字は同じフォントサイズであれば、ラテン文字のおよそ2倍の幅で描画されるのが普通です。CJK用の全角字形はもともと広い送り幅を持つよう設計されているためです。文字数が少ないことは、幅が狭いことを意味しません。

「長さ」は少なくとも4通りの測り方がある

長さの上限が意味を持つのは、それが何を数えているのか関係者全員が同じ理解をしている場合だけです。実際には同じ文字列でも「長さ」の測り方によって違う数字が出ます。

  • 人が数える文字数 — 読み手が1文字として認識する、かな・漢字・記号の単位。
  • 書記素クラスタ(grapheme cluster) — 上記を技術的に正確に表現したもの。人が1文字として知覚する単位でも、内部的には複数のUnicodeコードポイントから構成されることがある。基底文字に結合分音記号が付く場合や、絵文字にゼロ幅接合子で修飾子を結合する場合など。
  • コードポイント数 — 文字列に含まれるUnicodeスカラー値の単純な個数。結合文字が使われるだけで、すでに書記素クラスタ数とずれ始める。
  • コード単位数 — コード上で実際に測られている数で、最も直感に反しやすい。UTF-16(例えばJavaScriptの文字列の内部表現)では、U+FFFFを超えるコードポイントはサロゲートペアという2つの16ビット単位で格納されるため、`string.length`はこの単位を数える。絵文字1文字がlength 2になることがある。
  • 描画幅 — UIが本当に気にしているのはこれだけであり、上記のどれからも確実には予測できない。フォント、文字種ごとの標準的なグリフ幅、カーニングに依存するため。

具体的な失敗パターン

`string.length`に上限を課すコードは、実際にはUTF-16のコード単位数に上限を課しているだけで、翻訳者やプレイヤーが「文字数」と呼ぶものとは一致しません。純粋なCJKやラテン文字だけならこの差はほとんど無視できますが、結合文字や補助面(基本多言語面の外側)の文字が登場した瞬間に、静かに実際より多くカウントされます。

const label = "café"; // "cafe\u0301" のように、eに結合アキュート・アクセントが付く表現もある
label.length; // 見た目は4文字なのに5になる

const emoji = "🎮";
emoji.length; // サロゲートペアなので1ではなく2

フィールドごとに長さの予算を決め、機械的にチェックする

解決策は「賢い文字数カウント関数」ではなく、UIの各フィールドについて実際に何が制約になっているのかを決め、単一の万能な文字数上限ではなく、その制約に対してチェックすることです。

  • 固定幅の要素(ボタン、タブラベル、名前欄)にとっての本当の制約は、対象フォントでの描画幅であって文字数ではない。書き出したフォントに対してビルド時に測るか、実行時に描画結果をコンテナと突き合わせて測る。
  • 固定長の制約が避けられない場合(データベースのカラム、固定サイズのバッファ)は、それがバイト数・コードポイント数・コード単位数のどれを数えているのかを明記し、文字列の実際の格納・比較方法と一致する測り方を選ぶ。
  • 問題が起きてから気づくのではなく、翻訳の前に目安となる予算を伝える(例えば「英語の1.3倍程度まで」など)。レイアウトを作り直すより、翻訳段階で短い言い回しを依頼するほうがコストは低い。
  • CJKが縮む方向にも同じ注意を払う。短い日本語ラベルに合わせて作ったボタンは、同じボタンに長い英語やドイツ語の文字列が入ったときに窮屈でバランスの悪い見た目になる。

関連記事