31.3. 翻訳アダプタの使用法

次は、アダプタをコード内で使用する方法です。

例 31.1. 単一言語の PHP コードの例

<?php
print "Example\n";
print "=======\n";
print "Here is line one\n";
print "Today is the " . date("d.m.Y") . "\n";
print "\n";
print "Fix language here is line two\n";
?>

上の例の出力は、翻訳に対応していません。 おそらく実際はあなたの母国語でコードを書くでしょう。 一般に、翻訳すべきなのは出力だけではありません。 たとえばエラーメッセージやログメッセージなども対象となります。

次のステップは、既存のコードに Zend Translate をインクルードすることです。 もちろん、あとからコードを変更するよりも 最初から Zend_Translate を使ったコードを書くほうがずっと簡単です。

例 31.2. 多言語対応の PHP コードの例

<?php
require_once("Zend/Translate.php");

$translate = new Zend_Translate('gettext', '/my/path/source-de.mo', 'de');
$translate->addTranslation('//my/path/fr-source.mo', 'fr');

print $translate->_("Example")."\n";
print "=======\n";
print $translate->_("Here is line one")."\n";
printf($translate->_("Today is the %1\$s") . "\n", date("d.m.Y"));
print "\n";

$translation->setLanguage('fr');
print $translate->_("Fix language here is line two") . "\n";
?>

では、何が起こっているのか、そしてどうやって Zend_Translate をコードに組み込むのかについて もうすこし詳しく見ていきましょう。

新しい Translation オブジェクトを作成し、もととなるアダプタを定義します。

<?php
require_once("Zend/Translate.php");

$translate = new Zend_Translate('gettext', '/my/path/source-de.mo', 'de');
?>

この例では、 Gettext アダプタ を使うことにしました。 source-de.mo というファイルを /my/path に置いています。 この gettext ファイルにはドイツ語の翻訳が含まれています。 また、その後で別途フランス語のファイルも追加しています。

次に行うのは、翻訳対象の文字列をすべてラップすることです。 一番シンプルな手法は、このように文字列や文章を囲むことです。

<?php
print $translate->_("Example")."\n";
print "=======\n";
print $translate->_("Here is line one")."\n";
?>

中には翻訳する必要のない文字列もあるでしょう。 区切り線などは、たとえ言語が何であっても同じです。

データの値を翻訳文字列に組み込むこともできます。 この場合は埋め込みパラメータを使用します。

<?php
printf($translate->_("Today is the %1\$s") . "\n", date("d.m.Y"));
?>

print() の代わりに printf() 関数を使用し、 すべてのパラメータを %1\$s のように置き換えます。 最初のパラメータが %1\$s、その次が %2\$s、 といったようになります。 これにより、実際の値を知らなくても翻訳を進めることができます。 今回の例では、日付は常に今日の日付になります。 しかし、文字列を翻訳する際には、実際の日付が何であるかを知る必要はありません。

各文字列は、メッセージ ID によって識別します。 文字列の代わりに、コード内でメッセージ ID を指定することもできます。 その場合は、このようになります。

<?php
print $translate->_(1)."\n";
print "=======\n";
print $translate->_(2)."\n";
?>

しかし、この方法にはいくつか欠点があります。

コードを見ただけでは、そこでどんな内容が出力されるのかがわかりません。

また、文字列の一部が翻訳されていない場合にも問題が起こるでしょう。 翻訳の動作原理について考えてみましょう。 まず Zend_Translate は、指定した ID あるいは文字列に対応する翻訳が その言語に存在するかどうかを探します。 翻訳文字列が見つからない場合は、Zend_Locale で定義されているその次の言語の翻訳を探します。 つまり "de_AT" の場合は "de" のみで探します。 "de" の翻訳も見つからない場合は、 もとのメッセージをそのまま返します。 このようにして、たとえ翻訳文字列がなくても何らかの出力を得ることになっています。 Zend_Translate は、文字列の翻訳の際に エラーや例外を発生させることはありません。

31.3.1. 翻訳ソースの構造

次に行うのは、翻訳したい言語用の翻訳ソースを作成することです。 それぞれのアダプタには個別の方法があるので、それをここで説明します。 その前に、すべてのアダプタに共通する一般的な事項について説明しておきます。

どこに翻訳ソースファイルを保存すべきなのかを知っておきましょう。 Zend_Translate では特に制限はありませんが、 以下のような構造を推奨します。

  • 単一構造のソース

    /application
    /languages
      lang.en
      lang.de
    /library
    

    利点: すべての言語のソースファイルを同じディレクトリに配置できます。 関連するファイルを分割する必要がありません。

  • 言語ごとに分けた構造

    /application
    /languages
      /en
        lang.en
        other.en
      /de
        lang.de
        other.de
    /library
    

    利点: すべての言語がひとつのディレクトリにまとめられます。 各言語のチームは、ひとつのディレクトリを翻訳するだけですみます。 また、複数のファイルを透過的に使用できます。

  • アプリケーションごとに分けた構造

    /application
      /languages
        lang.en
        lang.de
        other.en
        other.de
    

    利点: すべての言語のソースファイルを同じディレクトリに配置できます。 関連するファイルを分割する必要がありません。

    欠点: 同じ言語で複数のファイルを使用する場合に問題が発生します。

  • Gettext 形式の構造

    /languages
      /de
        /LC_MESSAGES
          lang.mo
          other.mo
      /en
        /LC_MESSAGES
          lang.mo
          other.mo
    

    利点: 以前から使っている gettext 形式のソースを、 そのままの形式で使用できます。

    欠点: これまでに gettext を使ったことがない人たちにとって、 サブディレクトリの中にまたサブディレクトリという構造は不可解でしょう。

  • ファイル構造のソース

    /application
      /models
        mymodel.php
        mymodel.de
        mymodel.en
      /views
      /controllers
        mycontroller.de
    /document_root
      /images
      /styles
      .htaccess
      index.php
      index.de
    /library
      /Zend
    

    利点: すべてのファイルについて、翻訳ソースを関連付けられます。

    欠点: 小さな翻訳ファイルがあちこちに散らばってしまうので、翻訳が面倒です。 また、すべてのファイルに対して翻訳ソースを追加する必要があります。

Zend_Translate で最も便利なのは、単一構造か 言語ごとに分けた構造でしょう。

さあ、これでどんな構造でいくかが決まりました。 次に翻訳ソースファイルを作っていきましょう。

31.3.2. Array ソースファイルの作成

Array ソースファイルは、単なる配列です。 しかし、専用のツールはないので自分でそれを定義しなければなりません。 とは言え、この配列は非常にシンプルです。 コードが期待通りに動作しているかを確認するのにも最も手っ取り早いでしょう。 翻訳作業を始めるにあたっては、一般的に最適なアダプタであるといえます。

$english = array('message1' => 'message1',
                 'message2' => 'message2',
                 'message3' => 'message3');
$german = array('message1' => 'Nachricht1',
                'message2' => 'Nachricht2',
                'message3' => 'Nachricht3');

$translate = new Zend_Translate('array', $english, 'en');
$translate->addTranslation($deutsch, 'de');

31.3.3. Gettext ソースファイルの作成

Gettext ソースファイルは、GNU の gettext ライブラリで作成します。 あなたのコードをパースして gettext ソースファイルを作成してくれるツールが、 フリーで公開されています。このファイルは、 *.mo のような名前のバイナリファイルとなります。 ファイルを作成するためのフリーソフトウェアのひとつに poEdit があります。これは、ファイルの作成だけでなく翻訳作業自体もサポートしています。

// mo ファイルを作成し、翻訳を済ませているものとします
$translate = new Zend_Translate('gettext', 'path/to/english.mo', 'en');
$translate->addTranslation('path/to/german.mo', 'de');

ご覧の通り、アダプタの使用法はまったく同じです。 違っているのはたったの一点だけ。 'array' が 'gettext' になっているということです。 その他の部分は、どのアダプタを使用してもまったく同じになります。 gettext アダプタを使用する際には、 gettext の標準的なディレクトリ構造や bindtextdomain、 textdomain にこだわる必要はありません。 単にパスとファイル名をアダプタに渡せばいいのです。

[注意] 注意

ソースのエンコーディングには常に UTF-8 を使用しなければなりません。そうしないと、 複数のソースエンコーディングを使用することで問題が発生します。 たとえば、あるソースファイルは ISO-8815-11 でエンコードされており、 たのファイルは CP815 でエンコードされているとしましょう。 ソースファイルのエンコーディングはひとつしか指定できないので、 どちらか一方は正しく表示されなくなります。

UTF-8 は可搬性の高いフォーマットで、全言語をサポートしています。 すべての言語で UTF-8 を使用することで、 エンコーディングの非互換性による問題をなくすことができます。

31.3.4. TMX ソースファイルの作成

TMX ソースファイルは、新しい業界標準です。 XML ファイルを使用しているので、どんなエディタでも読み込め、 かつ人間にも読める形式であるという利点があります。 TMX ファイルはテキストエディタで作成することもできますし、 ツールを用いて作成することもできます。しかし、 現在使用できる TMX ソースファイル作成ツールの多くは フリーソフトウェアではありません。

例 31.3. TMX ファイルの例

<?xml version="1.0" ?>
<!DOCTYPE tmx SYSTEM "tmx14.dtd">
<tmx version="1.4">
 <header creationtoolversion="1.0.0" datatype="winres" segtype="sentence" adminlang="en-us" srclang="de-at" o-tmf="abc" creationtool="XYZTool" >
 </header>
 <body>
  <tu tuid='message1'>
   <tuv xml:lang="de"><seg>Nachricht1</seg></tuv>
   <tuv xml:lang="en"><seg>message1</seg></tuv>
  </tu>
  <tu tuid='message2'>
   <tuv xml:lang="en"><seg>message2</seg></tuv>
   <tuv xml:lang="de"><seg>Nachricht2</seg></tuv>
  </tu>
$translate = new Zend_Translate('tmx', 'path/to/mytranslation.tmx', 'en');
// TMX では、複数の言語をひとつの TMX ファイルに含めることができます

TMX ファイルには、同一ファイルに複数の言語を含めることができます。 含まれている言語はすべて自動的に追加されるので、 addLanguage() をコールする必要はありません。

31.3.5. CSV ソースファイルの作成

CSV ソースファイルは、サイズが小さく可読性があります。 顧客が自分で翻訳をしたいということなら、 おそらく CSV アダプタを使用することになるでしょう。

例 31.4. CSV ファイルの例

#Example csv file
message1;Nachricht1
message2;Nachricht2
$translate = new Zend_Translate('csv', 'path/to/mytranslation.csv', 'de');
$translate->addTranslation('path/to/other.csv', 'fr');

CSV 文字列の標準の区切り文字は ';' 記号です。 しかし、必ずこれでなければならないというわけではありません。 オプション 'separator' を使用すると、 別の区切り文字を使用することができます。

区切り文字を翻訳文字列に埋め込む必要がある場合は、 翻訳文字列内でそれをふたつ続けて記述します。 最初の区切り文字が元文字列と翻訳文字列を区切り、 ふたつ目の区切り文字が文字列の一部として解釈されます。 詳細は、次の例を参照ください。

例 31.5. CSV ファイルの例 その 2

#Example csv file
# 元メッセージは 'message,1'
message,,1,Nachricht1
# 翻訳は 'Nachricht,2'
message2,Nachricht,,2
# 元メッセージは 'message3,'
message3,,,Nachricht3
$translate = new Zend_Translate('csv', 'path/to/mytranslation.csv', 'de', array('separator' => ','));
$translate->addTranslation('path/to/other.csv', 'fr');

31.3.6. アダプタのオプション

すべてのアダプタで、オプションを使用することができます。 もちろん、アダプタによってオプションは異なります。 アダプタを作成する際に、オプションを設定することができます。 すべてのアダプタで共通のオプションは 'clear' で、これは、翻訳データを既存のものに追記するのかしないのかを指定します。 標準の動作は、新しい翻訳を既存の翻訳に追記します。 しかし、これを指定すると、既存の翻訳データをいったん消去して 新しいデータを追加します。消去されるのは選択した言語のみであり、 その他の言語は影響を受けません。

オプションを一時的に設定するには、 addTranslation($data, $locale, array $options = array()) とオプションの三番目のパラメータを指定します。 setOptions() 関数でオプションを設定することもできます。

例 31.6. 翻訳オプションの使用

$options = array('clear' => true);
$translate = new Zend_Translate('csv', 'path/to/mytranslation.csv', 'de');
$translate->addTranslation('path/to/other.csv', 'fr');
... // 何かをします
$translate->addTranslation('path/to/new.csv', 'fr', $options); // 言語 fr を消去し、新しい翻訳を使用します

31.3.7. 翻訳の確認

通常は、テキストが翻訳されているかどうかを気にすることはありません。 しかし、そのテキストが翻訳されているかどうかを、ソースコードから調べたいこともあるでしょう。 そんな場合に使用するメソッドが isTranslated() です。

isTranslated($messageId, $original = false, $locale = null) の最初のパラメータには、翻訳されているかどうかを調べたいテキストを指定します。 また、オプションの三番目のパラメータには、翻訳を調べたいロケールを指定します。 オプションの二番目のパラメータで指定するのは、 その言語に完全に一致した翻訳があるのか、あるいはもう少し広い範囲の翻訳を使用するのかという内容です。 たとえば、あるテキストについて 'en' の翻訳はあるが 'en_US' の翻訳はないといった場合、 通常は 'en' の翻訳を取得することになるでしょう。しかし $original を true にしておくと、このような場合は isTranslated() は false を返すようになります。

例 31.7. テキストの翻訳が存在するかどうかの確認

$english = array('message1' => 'Nachricht 1',
                 'message2' => 'Nachricht 2',
                 'message3' => 'Nachricht 3');
$translate = new Zend_Translate('array', $english, 'de_AT');

if ($translate->isTranslated('message1')) {
    print "'message1' の翻訳が存在します";
}
if (!($translate->isTranslated('message1', true, 'de'))) {
    print "'message1' は 'de' に翻訳することはできません。'de_AT' 用の翻訳しかありません";
}
if ($translate->isTranslated('message1', false, 'de')) {
    print "'message1' は 'de_AT' に翻訳できます。もし存在しない場合は代替として 'de' を使用できます";
}