C.A.T. Clubにおける多言語対応について
-
2024年3月26日
お疲れ様です、クライアントエンジニアTです。
今回はC.A.T. Clubにおける多言語対応の実装について書きたいと思います。
本記事はあくまでクライアントエンジニア目線における対応なので、主にアプリ内に埋め込まれているテキストの多言語対応の記事になります。
基本情報
ローカライズのシステムは基本的にUnityのLocalizationパッケージを用いています。
また、Text表示において、ユーザー入力がある部分はUGUIのText、それ以外は基本的にTextMeshProを使用しています。
画像系については今回スコープから外れているので対応していませんが、
対応する際はLocalizationパッケージの機能で基本的には切り替える予定です。
翻訳のデータはGoogleスプレッドシートで管理し、
Unityで登録したKeyと英語をシートAにPush、シートAを元に作られたシートBをPullという形で取り込みました(これについては後述)。
C.A.T. Clubは英語でリリースして後日、日本語に対応しています。
そのため、日本語から英語に対応する際の文字数によるUI調整等は最小限でした。
しかしながら、英語で使うフォントと日本語で使うフォントのサイズ差により言語毎にフォントサイズを切り替える対応や一部文言については英語の時のフォントを利用したいという要望がありました。
実装内容
主に行なった実装は三つあります。
①Prefabのテキストを検索してLocalize用Keyを設定
基本的にTextはUGUIだろうとTextMeshProだろうとやることは同じで、LocalizedStringのStringChangedを監視して文字列を更新します。
問題は、Textに割り振るLocalize用Keyを一つずつ手作業で設定するのは骨が折れる&対応漏れが発生する点です。
この解決策はツールで自動的にKeyを発行、設定を行い、全てのTextに反映するという手法でした。
ただし、その場合、どのテキストがどこで使われているテキストかがわからないという問題点があったので対策を考える必要がありました。
Custom Locale の悪用
あえて悪用と書きましたが、あまり推奨される手法じゃない対処をしました。
上記問題の対策としてCustom LocaleでPrefabの名前やオブジェクト階層を記載する為のLocaleを追加して、一つの言語情報としてPrefab名やオブジェクト階層を記載する事にしました。
また、これを利用して特定のテキストではフォントを切り替えるか等の情報も付与出来る様にしました。
これら全ての情報をツールで一度に生成し、スプレッドシートに反映するところまで実装しました。
②Script内のテキストをLocalizationのテーブルから取得した文言と置き換え
C.A.T. Clubでは動的に生成される文言の多くはスクリプト内に埋め込まれていました。
それを全て洗い出して、全ての文言をLocalizationのテーブルから取得する関数を呼び出す様に置き換えます。
ここまでは物量が多いだけの作業です。
しかしながら、その後の作業であるLocalizationのテーブルのKeyと文言の登録にあたって、追加漏れやKeyの登録ミスが起こる懸念があったので自動化しました。
特定関数を呼び出している箇所の静的解析
上記で置き換えた関数の呼び出し箇所を検索し、その引数と呼び出し元のファイル名を元にKeyを発行するシステムを作り、開発がひと段落した際に実行する手順としました。
これにより、都度Keyをつける手間を削減し、ミスも減らせる想定です。
// 関数呼び出しを検索
var methodInvocationNodes = root.DescendantNodes()
.OfType()
.Where(invocation => invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
memberAccess.Name is IdentifierNameSyntax identifierName &&
identifierName.Identifier.Text ==”hoge”);
// 引数の取得
foreach (var invocation in methodInvocationNodes)
{
var memberAccess = (MemberAccessExpressionSyntax)invocation.Expression;
var className = memberAccess.Expression.ToString();
if (className != "HogeClass")
continue;
var argumentList = invocation.ArgumentList;
var arguments = argumentList.Arguments;
if (arguments.Count <= 0)
continue;
if (arguments[0].Expression is not LiteralExpressionSyntax literal)
continue;
var argumentValue = literal.Token.ValueText;
res.Add(argumentValue);
}
③スプレッドシートの更新フローの整備
上記2点によってKeyが生成された後、それを翻訳するシートは基本情報で語った通りGoogleスプレッドシートで管理しています。
スプレッドシートはPrefab用、Script用で分かれていますが基本的な作りは同じです。
シートは取り込み用シート、翻訳データシート、Keyシートがあり、
Unityで発行されたKeyと英文はKeyシートに反映、その後GAS(Google Apps Script)で翻訳シートのデータの更新・追加を行い、更新のあったセルに色をつけます。
翻訳担当者の方には色のついたセルの翻訳を翻訳データシートに記載してもらいます。
取り込み用シートはKeyシートのKeyを参照し、それをトリガーとして翻訳データシートにある英文・日本語文を表示しており、それをUnity側でImportする事によってLocalizationテーブルを更新します。
シートを分けたのにはいくつか理由があり、
・更新があった部分を翻訳者が把握しやすくするため
・不意なKeyの削除や編集を避けるため
・ミスでKeyが削除された際にrevertするだけで翻訳をし直さなくて済むため
等があります。
最後に
本記事では英語から日本語への多言語対応を語ってきましたが、
その本質としてはいかに作業者の負担が減るかを重視したものになります。
翻訳作業をする人だけでなく、UI実装のデザイナーさんやクライアントエンジニアそれぞれの負担がなるべく小さくなる様に努力を行ってきました。
しかしながら、上記で語ったフローはあくまでC.A.T. Clubでの開発において最善を尽くしたものであって、これを読んでいる方のプロジェクトでは違う運用の方が良い場合もあります(むしろその場合の方が多いと思います)。
大事なのは完璧な作業フローよりもそのプロジェクトにあった柔軟なフローを構築する事だと思います。