TMXとTBXとは — 翻訳メモリと用語集のファイル形式を実例で理解する
TMXファイルは翻訳メモリの書き出しで、実際の翻訳作業で確定した原文と訳文の対が並んでいます。TBXファイルは用語集(termbase)の書き出しで、1つの概念に1エントリ、各言語での呼び方が入っています。どちらもオープンなXMLの交換形式で、ゲームに読み込む文字列ファイルではありません。読み込む先は翻訳ツールです。
納品の最後に渡されたTMXはしまわれがちですが、納品物の中で最も寿命が長く、次を安くできるのはこのファイルだけです。この記事では両形式の最小限の完全な例、一致率がツールごとに変わる理由、翻訳メモリが効く場面と効かない場面、壊れる5つのパターンを扱います。
TMXファイルの中身 — 最小限の完全な例
TMX文書はルート要素が1つ、headerが1つ、翻訳ユニットの入ったbodyが1つという構造です。TMX 1.4の仕様はheaderが必ず持つ属性を定めており、そのうち srclang と segtype は一致を直接左右します。
- srclang — どの言語から訳したものとして扱うか。方向が1つに定まらないメモリ用のワイルドカード値もあり、正確な文字列は仕様のheaderの節にあります。
- segtype — 書き出したツールが使った分節の粒度(block、paragraph、sentence、phrase)。誰も読まないのに一致率を決める属性です。
- adminlang — ファイル内の管理用の注記の言語。翻訳する2言語のどちらかである必要はありません。
- creationtool と creationtoolversion — どのツールが書き出したか。取り込みが失敗したとき、誰の方言かが分かります。
- datatype と o-tmf — 元の内容が何だったか、どの内部メモリ形式から書き出したか。
<?xml version="1.0" encoding="UTF-8"?>
<tmx version="1.4">
<header
creationtool="example-exporter"
creationtoolversion="1.0"
segtype="sentence"
o-tmf="plain"
adminlang="en"
srclang="en-US"
datatype="plaintext"/>
<body>
<tu tuid="1">
<tuv xml:lang="en-US">
<seg>Are you sure you want to leave the dungeon?</seg>
</tuv>
<tuv xml:lang="ja-JP">
<seg>ダンジョンから出てもよろしいですか?</seg>
</tuv>
</tu>
<tu tuid="2">
<tuv xml:lang="en-US">
<seg>You found <ph type="item">{0}</ph>.</seg>
</tuv>
<tuv xml:lang="ja-JP">
<seg><ph type="item">{0}</ph> を見つけた。</seg>
</tuv>
</tu>
</body>
</tmx>bodyの中のtuが1つの翻訳ユニットで、言語ごとのtuvが入り、各tuvのsegにテキストが入ります。言語はtuvのxml:lang属性で示し、XMLの仕様はこの値をIETFの言語タグと定義しています。BCP 47の言語タグの組み立て方がそのまま当てはまります。
TBXファイルの中身 — 概念ごとに用語とステータスを持つ
TBXは文ではなく概念(concept)を軸にします。1つの用語エントリは、名前を必要とする1つの物事です。その中に言語ごとの言語セクション、さらに用語ごとの用語セクションが入ります。1つの言語が同じ概念に複数の用語を持て、それぞれにステータスを付けられます。これが「こちらが承認済みで、こちらはもう使わない」を記録する方法です。1用語1行のスプレッドシートでは、この使い分けも自動の用語チェックもできません。
TBXを読むコードを書く前に、ファイルの宣言を確認してください。TBXには複数の版と、TBX-BasicやTBX-Coreなどの方言があり、要素名と許される注記が違います。下の例は2008年当時の標準に基づくTBX-Basic相当の入れ子です。2019年の改訂版は入れ子は同じで、ルート要素と概念・言語・用語の各要素名が変わりました。版と方言はルート要素で、ステータスに使える値はその方言のピックリストの節で確認してください。
<?xml version="1.0" encoding="UTF-8"?>
<martif type="TBX-Basic" xml:lang="en">
<martifHeader>
<fileDesc>
<sourceDesc><p>Game UI and economy glossary</p></sourceDesc>
</fileDesc>
</martifHeader>
<text>
<body>
<termEntry id="c001">
<langSet xml:lang="en">
<descripGrp>
<descrip type="definition">The premium currency spent on cosmetic items.</descrip>
</descripGrp>
<tig>
<term>Gem</term>
<termNote type="partOfSpeech">noun</termNote>
<termNote type="administrativeStatus">preferredTerm-admn-sts</termNote>
</tig>
</langSet>
<langSet xml:lang="ja">
<tig>
<term>ジェム</term>
<termNote type="administrativeStatus">preferredTerm-admn-sts</termNote>
</tig>
<tig>
<term>宝石</term>
<termNote type="administrativeStatus">deprecatedTerm-admn-sts</termNote>
</tig>
</langSet>
</termEntry>
</body>
</text>
</martif>一致の仕組み — 完全一致・あいまい一致・分節の落とし穴
完全一致とは、ツールが両方の文字列を正規化して同一と判定したことです。何を正規化に含めるかはツールの判断で、TMXは定めていません。だから見た目が同じ文が静かに一致しません。全角の疑問符(U+FF1F)と半角の疑問符は別のコードポイントなので、画面上は区別できない2文が文字列として等しくありません。NFKC正規化はこの2つを畳みますが、NFCは畳みません。「が」を合成済みの1文字で書いた場合と「か」に濁点の結合文字を続けた場合も不一致で、こちらはNFCだけで一致します。
もっと高くつく誤解はあいまい一致(fuzzy match)です。TMXが保存しているのは文の対だけで、一致率は保存していません。同一のTMXを2つのツールに渡せば、同じ検索に違う一致率が返ります。編集距離の定義も、句読点・数字・インラインタグの重み付けも、ツールごとに違います。見積書の流用率はツールについての数字なので、2社を比べるならどのツールで解析したのかを先に聞いてください。
健全なメモリが空に見える失敗が分節(セグメント)です。headerのsegtype属性が「1ユニットとは何か」を宣言します。段落単位のメモリに文単位で問い合わせると、文がすべてその中にあっても完全一致はほとんど出ません。分節規則自体にもSRXという交換形式があり、2つのツールで文の終わりを揃えるためのものです。ツールを変えた直後に一致率だけ落ちたなら、ここを疑ってください。
最後がインラインタグで、文中の書式や変数を示す要素が次のツールに「ここに値が入る」と伝えています。取り込み時にこれを素のテキストに潰すツールは、見かけの一致率を上げながらその情報を壊します。訳文がプレースホルダを失ったまま出荷される普通の経路です。変数があるならタグは保ってください。中身はICU MessageFormatのプレースホルダと複数形が、作業を渡すファイルはXLIFF形式が扱っています。
ゲーム開発で翻訳メモリが効く場面と効かない場面
翻訳メモリが元を取るのは、同じ文が戻ってくるときです。具体的には次の状況です。
- 前作のシステム・メニュー・アイテム説明を流用する続編やスピンオフ
- パッチやアップデートの周期。ファイルの大半が変わらず、新しい行だけ訳す場合
- ライブ運営のコンテンツ。イベント告知文が毎回同じ型で、名前と数字だけ変わる場合
- 複数タイトルで共通の定型文を共有するスタジオ。設定画面、コントローラー表示、ストアや法務の文言
逆にほとんど効かない場面が2つあり、効くふりをすると悪い訳が承認されます。1つは短いUI断片の集まりです。2語程度の文字列で100パーセント一致が出ても節約ではなく危険です。同じ原語がある画面では動詞、別の画面ではラベルでも、メモリはどちらを記録したか教えてくれません。もう1つは文脈依存の強いもの。話者で訳が変わる行、直前の行で語調が決まる返事、意図的に他と違う話し方のキャラクター。別の話者から取った完全一致は、緑のチェックマーク付きの不具合です。
メモリが答えるのは「この文を以前訳したか」、用語集が答えるのは「これを何と呼ぶか」です。短い断片や固有名詞で助けになるのは用語集と原文に添えた注記のほうで、メモリはほぼ役に立ちません。文脈の注記は後追いのメールではなく引き渡し物に入れてください。中身はローカライズキットに何を入れるかにまとめています。
資産として持つ — 納品物に何を入れ、何を確認するか
まず契約から。メモリと用語集が自社のものだと契約書に書いてある必要があり、この文言は資格を持つ専門家に確認してください。そのうえで次の4つをやります。
- 書き出しファイルを納品物として着手前に書面で決め、各マイルストーンで受け取る。最終日に一度だけ受け取るメモリは、欠けていても誰も気づきません。
- 生の書き出しファイルを原文と同じリポジトリ、少なくとも同じバックアップに置く。ツールのアカウント内にだけあるメモリは、解約1回で消えます。
- ツールを移行するときは、書き出して取り込み、ユニット数を比べてから旧ツールを解約する。取り込みで件数が静かに減るのは珍しくなく、当日なら安く気づけます。
- 外に出す内容を決める。メモリの書き出しは、まだ出していないものを含む訳文すべての記録です。未発表タイトル、キャラクター名、物語の展開、未発表地域向けのストア文言。TMXはユニットごとに誰が作成し誰が変更したかの属性も持ち、実務ではそこに翻訳者のアカウント名が入っています。新しい取引先に渡す前に、設定ファイルではなくデータベースのダンプとして扱ってください。
壊れる5つのパターンと、1つのコマンドで分かること・分からないこと
5つのうち2つはXMLとして普通の失敗で、整形式(well-formed)の検査が一瞬で見つけます。xmllint --noout memory.tmx はmacOSに最初から入っており、Linuxではlibxml2のツールに含まれます。終了ステータス0なら整形式、失敗すれば行と桁が出ます。
- 宣言した文字コードと実際のバイト列の不一致。UTF-8と宣言しているのに日本語のセグメントがCP932のファイルは検査で落ち、パーサーが該当バイトを表示するので文字コードを特定できます。文字化けの原因と対処やShift_JISとCP932の落とし穴と同じ系統の問題です。
- UI文字列の中のエスケープされていないアンパサンドや山括弧。Save & Exit というセグメントはパーサーエラーです。XMLを文字列連結で組み立てたときに出ます。
- 言語タグの表記揺れ。検査では捕まりません。大文字小文字の違いは無害で、JA-jp と ja-JP は正規化すると同じタグです。しかし ja と ja-JP は正規化しても別の文字列なので、単純に文字列比較するツールは2言語と解釈し、ユニットの半分を頼んでいない言語に入れます。ja_JP のようなアンダースコア形式は言語タグではありませんが、xml:langの中身は検証されないので整形式の検査は通ります。
- 往復でインラインタグが落ちる。プレースホルダを失ったファイルも整形式なので、検査は何も言いません。
- ツール独自の拡張。どちらもツールが独自のプロパティを付けられ、次のツールが知らないものは無言で捨てられます。往復させてユニット数と中身を比べてから信用してください。
$ xmllint --noout memory.tmx
memory.tmx:7: parser error : Input is not proper UTF-8, indicate encoding !
Bytes: 0x8C 0xAE 0x82 0xF0
<tuv xml:lang="ja"><seg>������������B</seg></tuv>
^
$ xmllint --noout glossary.tmx
glossary.tmx:6: parser error : xmlParseEntityRef: no name
<tuv xml:lang="en"><seg>Save & Exit</seg></tuv>
^整形式であることは、取り込めることとは違います。次に読むのは取り込みログで、比べる数字はツールが報告するユニット数とファイル側の件数です。実装側はPO形式(gettext)、訳文が戻ったあとの工程はローカライズQA(LQA)が扱っています。