i18n :Twig

internationalization = i(18文字)n = i18n

サイトを国際化対応すること。つまり、ページを見る人によって、言語や時刻や通貨などを変える。

言語の国際化に便利な機能として、gettextというライブラリがC言語で開発されており、PHPでも拡張として利用できる。

そのPHP用gettextを、さらにTwig側で拡張してTwigの記法で埋め込めるようにしたものを利用する。

流れ

  1. 環境を用意する(PHP用gettext拡張、Twig用i18n拡張、OS言語設定)
  2. Twigファイル中の翻訳したい文言が書かれた部分を「{% trans %} {% endtrans %}」で囲う
  3. Twigファイルをphpファイルに変換する
  4. phpから翻訳が必要な箇所(transで囲んだ箇所)を自動抽出した.poファイルを作る
  5. .poファイル内の文言を1つ1つ別の言語に翻訳する
  6. .poファイルを.moファイルにコンパイルする
  7. .moファイルを所定のフォルダ構造に配置し、Twigにパスを与える
  8. 準備完了!
  1. アクセスが来たら、ブラウザ情報などを読み取ってOSの言語を(PHP上で仮想的に)変更する
  2. 翻訳された結果が出力される!

導入

gettextのインストール

まず、PHPにgettext拡張が無ければ始まらないのでインストール。といっても勝手に入ってるかも知れないので、phpinfo()等で確認してからでもいいかも。

Twig拡張セットのインストール

Twig用拡張セットをComposerでインストール。i18n拡張もこれに含まれている。Twigのバージョンが古すぎると失敗する(1.27以降?)

> composer require twig/extensions

OSの言語の確認、インストール

gettextはOSの言語設定を読み取って設定される。PHPで利用する際は、setlocale()で目的の言語に変更する。

その際、OSにインストールされている言語でないと変更できない。(言語設定は実質的な処理には全く関わってこないため、無駄な気もするが)目的の言語が入っているか確認し、無ければインストールする。

$ locale -a
C
C.UTF-8
en_US.utf8
...

ここで、ja_JPが欲しいのに無かったとする。

(Ubuntuの場合)
$ sudo apt-get install language-pack-ja

これで入る。

$ locale -a
(略)
en_US.utf8
ja_JP.utf8

このlocaleコマンドで出てきた言語名は、後で利用するので控えておく。

裏技?

以下のページで、OSの言語を変更せずともgettextで言語を切り替えようと試みられている。

$domainの部分に言語名を指定することで、OSの言語をそのままにしつつ、.poのファイル名による言語切り替えが可能になるという方法らしい。

が、どうもOSの言語の設定が「C」の場合は、gettextは翻訳を行わずそのままの文字列を出力するため、Ubuntuなどはダメなようだ。

使い方

例えばこんなシステム構成とする

root/
|- app
|   |- templates
|   |   `- index.twig
|   |- templates_cache   
|   `- locale
|       |- en_US
|       |   `-LC_MESSAGES
|       |      |- messages.po (まだ無くてよい)
|       |      `- messages.mo (まだ無くてよい)
|       `- ja_JP
|           `-LC_MESSAGES
|              |- messages.po (まだ無くてよい)
|              `- messages.mo (まだ無くてよい)
`- index.php
フォルダ・ファイル命名制限
locale何でもよい
en_US, ja_JP翻訳言語のOS上での名称
LC_MESSAGES固定
messages.po.moの作成に必要なだけで、実際は不要
messages.mo何でもよいが、元となる翻訳文言が同じファイルは各言語で名称を統一する

インスタンス生成時に拡張を指定

<?php
require_once '/path/to/vendor/autoload.php';

$loader = new Twig_Loader_Filesystem('/path/to/templates');
$twig = new Twig_Environment($loader, ['cache' => '/path/to/compilation_cache']);

// 拡張を加える
$twig->addExtension(new \Twig_Extensions_Extension_I18n());

環境設定

PHPよりOSの環境を変更する。

// 目的の言語
$lang = 'ja_JP.utf8';  // localeコマンドで出てきた名称を指定

// ファイル名
// システム構成のLC_MESSAGESフォルダ内にある.poのファイル名と同じものを指定する
$domain = 'messages';
textdomain($domain);

// 言語を指定
setlocale(LC_MESSAGES, $lang);

// localeフォルダまでのパスを指定
bindtextdomain($domain, '/path/to/root/app/locale/');

// 文字コードは適宜指定
bind_textdomain_codeset($domain, 'UTF-8');

.twigファイルにタグを埋め込む

直接書かれた文字列なら、{% trans %}~{% endtrans %}で囲う。{% Twigタグ %}内の文字列なら、「|trans」フィルターを用いる。

<title>English Site!</title>

{% set COMMENT = 'Hello World!' %}

<title>{% trans %}English Site!{% endtrans %}</title>

{% set COMMENT = 'Hello World!'|trans %}

レンダリングしてキャッシュファイルを作る

適当に作業用ディレクトリを用意してRecursiveDirectoryIteratorで全ファイルをTwigに処理させ、レンダリングされた.phpを作成する。

$tplDir = 'app/templates';
$tmpDir = 'app/templates_cache/';
$loader = new Twig_Loader_Filesystem($tplDir);

// force auto-reload to always have the latest version of the template
$twig = new Twig_Environment($loader, array(
    'cache' => $tmpDir,
    'auto_reload' => true
));
$twig->addExtension(new Twig_Extensions_Extension_I18n());
// configure Twig the way you want

// iterate over all your templates
foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($tplDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file)
{
    // force compilation
    if ($file->isFile()) {
        $twig->loadTemplate(str_replace($tplDir.'/', '', $file));
    }
}

キャッシュファイルに対してxgettextで.poファイルを作る

xgettextで、PHPファイル内のgettextを用いると指定した箇所を全て洗い出し、poファイルを作成する。

xgettext --default-domain=messages -p ./app/locale --from-code=UTF-8 -n --omit-hader -L PHP ./app/templates_cache/*/*.php

これで ./app/localemessages.po という名称で、{% trans %}で囲った部分が網羅されたファイルが出来る。(作る場所はどこでもいい)

messages.po
#: /var/www/html/app/template_cache/34/34de2683ab8f366dd6d96d37dfe3fe7057abf8afbedb8074bacdbac0c5cd760a.php:25
msgid "English Site!"
msgstr ""

msgid "Hello World!"
msgstr ""
...

.poファイルを各言語のLC_MESSAGESにコピーして翻訳する

root/app/locale/ja_JP/LC_MESSAGES/messages.po
#: /var/www/html/app/template_cache/34/34de2683ab8f366dd6d96d37dfe3fe7057abf8afbedb8074bacdbac0c5cd760a.php:25
msgid "English Site!"
msgstr "日本語のサイトです!"

msgid "Hello World!"
msgstr "こんにちは西園寺!"

.poファイルを.moファイルにコンパイルする

msgfmtコマンドを使用する。各poファイルのある場所まで行き、

$ msgfmt messages.po -o messages.mo

これで、同じ階層にmoファイルが出来る。ひとまずファイル側の準備は完了。

PHPで、OS言語を変更する処理を実装する

OSの言語を翻訳したいものにすると、gettextが自動的に翻訳結果を作ってくれる。

そのためには、何らかの手段でユーザの求める言語を特定する必要がある。主には以下の3つかな?

  • GETで指定された言語
  • ブラウザの設定言語
  • (アカウントを作るようなサイトでは)ユーザ設定

ブラウザの言語を最優先にすると、日本語ブラウザで一時的に英語サイトが見たい時とか困るから、GETの判定はそれより前に入れるべき。あとはセッションなどで保持するとよいだろう。

ブラウザの言語判定法

ブラウザでは、言語を優先度付きで複数設定できる。サイトで対応する言語の中から優先順位の高いものを見つけるサンプルコード。

/**
 * @return str 言語のOS上での名称
 */
function judgeLang()
{
	// 対応言語(省略2文字 => OS上での名称)
	$available = ['en' => 'en_US.utf8', 'ja' => 'ja_JP.utf8'];
	$default = 'ja_JP.utf8';

	// ブラウザの言語を取得
	$languages = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
	
	// 頭2文字が候補に無いか確認
	foreach ($languages as $language) {
	    foreach (array_keys($available) as $lk) {
	        $pat = "/^{$lk}/i";
	        if (preg_match($pat, $language)) {
	            $lang = $available[$lk];
	            return $lang;
	        }
	    }
	}
	
	return $default;
}

OS言語の指定方法

judgeLang()から返ってきた言語を設定する。$domainはファイル名。

bindtextdomainにはlocaleフォルダへのパスを指定する。locale/(言語)/LC_MESSAGES/以下のフォルダが探される。

function setLocale($locale)
{
    $domain = 'messages';
    $res = setlocale(LC_MESSAGES, $locale);  // $resには成功フラグが返る
    bindtextdomain($domain, '/path/to/locale/');
    bind_textdomain_codeset($domain, 'UTF-8');
    textdomain($domain);
}

表示確認する

programming/php/library/twig/i18n.txt · 最終更新: 2018/05/10 by ikatakos
CC Attribution 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0