がるの健忘録

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

多言語対応のあれこれ

ふと生徒さんに質問をいただいたのもあって。
ちょうどよいきっかけになったので、せっかくなんでBlogで。


本質的には「どの言語のどの領域」でもある程度応用が利くかと思われますが。
一応、おいちゃんの記述なんで「MySQLPHPつかったWebアプリケーション」をど真ん中に据えて、ってな感じで。


まず「ユーザからの入力について多言語対応したい」は簡単で「保存するデータ及びHTMLをUTF-8にしましょう」で、fin。
いやまぁUnicodeであれば大体無問題だと思われるのですが*1。HTMLとかでUTF-16って、あんまり見た記憶がないんですよねぇその辺詳しい諸氏の突っ込み求む。
ひとつポイントがあるとすると「MySQL文字コードは、utf8じゃなくてutf8mb4」ってあたりなのですが、そのためにはMySQL5.5.3+というバージョンが必要なのでそれより低いバージョンの場合はバージョンをあげましょうここに慈悲はない。


お次に「プログラム的に動的な文字の出力」をどうするか、ですが。例えば「お知らせ」とか。
この辺あたりから少し面倒になるのですがおおむね
・ユーザが「表示してほしい」言語を忖度する
・忖度した結果としての「選択された言語」のデータを引っ張ってくる
ってまぁ、こんな感じ。


まぁ最終的には「ユーザに選んでもらう」でよいですし、選んだ結果は「Cookieあたりに保存」しておけばよいのですが。
初手のアクセスで「きっとこの人は日本語圏の人なのではなかろうか?」を推測したいのであれば、HTTP RequestヘッダのAcecpt-Languageを見ると、比較的、ヒントがあったり。
PHPの場合

$_SERVER['HTTP_ACCEPT_LANGUAGE']

で取得可能。帰ってくる値は、例えば

"ja,en-US;q=0.7,en;q=0.3"

ってな感じなので。初手にjaがあったら「なんとなくこの人、日本語圏の人なのではないだろうか?」と推測が可能。
それ以外は適宜しらべて。en-USとかきたら英語圏だし、それ以外で斜めに調べた限りだと「de (ドイツ語)」「es (スペイン語)」「it (イタリア語)」など。
後ろの「q=0.7,en;q=0.3」にも本来的には意味があるので、興味がある諸氏は適宜しらべたし(大まかには、各言語の優先確率)。
雑に行くんなら「先頭2文字で判断」でも、当面は困らないんじゃないかなぁ切り出しておいて問題が起きたら修正すればいいんだし(雑)。


で、あとは例えば「お知らせ」なら、お知らせテーブルに「言語」とかいうカラムをつけておいて

ja: お知らせです。
en: It is news
it:saluti

とかって感じでデータを用意して出力すれば、それでOKな感じ。どっちかってぇと「各言語のコンテンツ」用意するのが面倒だよねぇ、的な。
でもまぁそれはコンテンツ用意する人の問題なので、サイトの骨格であるシステム作成のおいちゃん的にはいったん気にしないw


さて割と一番大きな本題「HTMLなどの静的*2なファイルの文字」をどうするか、ですが。
大枠として2種類あって、かつその2種類にはそれぞれ亜種がいくつか存在します、ので、それぞれ、ある程度(もしくは簡単に触りだけ)説明をしていきたいかなぁ、ってのが、本文章の趣旨。


大まかには
・テンプレートを切り替える
・出力文字を切り替える
の2種類。
それぞれ、少しかみ砕いて。

テンプレートを切り替える

いやまぁそのまんまなのですが。
例えば「ログイン」Pageがあるとして、ボタンに「ログイン」とか日本語で書かれると、英語圏の人は多分いろいろと困るです。
……いや日本のサイトで「login」って書いてあっても困らない気がビシバシとするのですが、その辺は置いといて。


てっとり早いのは「英語圏用のテンプレート」と「日本語圏用のテンプレート」とを別々に用意して出力を切り替える、って方法がありまして。
対応言語数が少ない&ページ数が少ない&更新頻度が低い(更新そのものが少ない)のであれば、割と手っ取り早い解決策だと思われます。


方法としては
・言語ごとにディレクトリを分ける
・言語ごとに拡張子を分ける
って方法がありまして。


例えば(Smartyチックに)login.tpl、ってテンプレートがあるとしますと。
ディレクトリで分ける」パターンであれば、
templates/ja/login.tpl
templates/en/login.tpl
って風に入れて。
拡張子で分けるのであれば、(Smarty的に「本当の意味での拡張子」はいじると面倒なんで)
templates/login.ja.tpl
templates/login.en.tpl
って感じにすると、切り分けられます。


上述のような切り替えの作業は、どこか一か所にまとめておくといろいろと楽ですよね。
うちのフレームワーク(MagicWeapon)であれば、viewクラスのmake_template_filename()メソッドを上書きして、って感じかなぁ。
まぁ大体の*3コードであれば、どこかしら「テンプレートのファイル名を取得する」的な一点があると思うので、そこを「キュッ」と絞めると、いけると思います。

出力文字を切り替える

対応言語数が多い&ページ数が多い&更新頻度が高いのであれば、「出力文字による切り替え」を想定したほうが楽かもしれません……初手面倒ですが。
端的には
・各言語用の翻訳ファイルを用意して
・プログラムを通して文字列を変換する
となります。


これにも方法がいくつかあって、大まかには
・サーバサイドで自力実装
・クライアントサイドで自力実装
・「gettext」ってのが割とあちこちの言語でライブラリとして存在するので、それを使う
のいずれか、になります。


共通があるのでgettextがよさそうなもんですが…それなりに使い方とかお作法とか癖とかがあるので。
PHPの場合は「インストール」も必要ですしねぇ。
前提条件や癖やそのあたりが「呑み込めそう」なら、gettextを使ってみるとよいんじゃないかなぁ、と思います。
xgettextとか面白いんだけどなぁ……「テンプレートエンジンを使う」前提だと、幾分、ハードルが上がったり諸々が以下略。


自力で作る場合は、おおむね
・辞書のフォーマットを決める
・辞書ファイルを必要言語数だけ作る
・テンプレートに辞書ファイルをぶつけて出力する
といった感じ。


んと……ざっぱに、例。
例えば辞書ファイルを「コード: 翻訳文」とします。
日本語と英語を用意してみませう。


辞書.ja

login_button: ログイン
login_text: こちらからログインしてください。
password_reminder_text: パスワードがわからない場合はこのボタンを押してください。
password_reminder_button: パスワードリマインダ


辞書.en

login_button: login
login_text: Please log in from here.
password_reminder_text: Please press this button if you do not know the password.
password_reminder_button: Password reminder

*4


いろいろと面倒なんでいったんSmarty前提。
まずPHP本体側で、「言語にそった辞書」を渡します。
辞書は、key=コード、value=翻訳文、のhash配列方式で情報があると仮定。

// 辞書の選択
if (日本語圏なら) {
  $辞書配列 = 日本語の辞書;
} else if (英語圏なら) {
  $辞書配列 = 英語の辞書;
} else {
    // 例外でもぶん投げるかねぇ
}

// 辞書をアサイン
$smarty_obj->assign('dic', $辞書配列);


んで、Smartyでは、例えばこんな風に記述します。

{$div.login_text}
<form ...>


<button>{$dic.login_button}</button>
</form>

こんな風にしてテンプレートに「一切出力用の自然言語を書かずに」辞書ファイルに追い出すと、まぁいろいろとできたり出来たりします。ざっくりとは。

結論

がっつりと多言語対応って、割と案件数的にも少ない気がするので。
調べると、案外とネットの情報も少ないんですよねぇ……なので、書いてみた。


なんかほかにも手法ありそうなのですが、まずは「こんなのもあるよ〜」的に。
突っ込みとかあったら突っ込んでくださいませ > 諸氏

*1:UTF-7? なにそれ美味しいの?

*2:政敵って変換するFEPについてどう思う?

*3:まっとうな

*4:英訳への突っ込みはこれを禁止するwww