Drupal 8のTwigテンプレート上で翻訳APIを利用する

基本的なルールは、Drupalはモジュールとテーマを英語で書くことを前提としています。

技術的には、モジュールやテンプレート上で日本語を使っても支障はありませんが、Drupalコアやすべてのコントリビュートモジュール、及びテーマは英語をベースとして他の言語に翻訳されることが前提となっており、Drupal.org の [Localization API overview](https://www.drupal.org/docs/7/api/localization-api/localization-api-overview) でもその旨が言及されています。

POファイルをメンテナンスしてDrupalにインポートし、翻訳APIを通してテキストを出力するという一連のワークフローに慣れておくことは、いざグローバルサイトを構築するときに蓄積されたノウハウが役立ちますので、基本的に英語ベースで実装しましょう。

今回は、Twigテンプレート上で静的テキストと変数を翻訳するためのコードの書き方を紹介します。


![drupal8_twig.png](https://qiita-image-store.s3.amazonaws.com/0/39468/87fd849e-0441-69dd-37a8-2fb464642867.png)

#検証環境

* Macbook Pro (Retina, 15-inch, Mid 2014)
* Mac OS X 10.11.6 El Capitan
* PHPStorm 2017.3
* Vagrant 1.9.4
* CentOS 7.4.1708
* Drupal8.4.4


#tフィルターで翻訳する

シンプルな文字列翻訳として、tフィルターが用意されています。これはTwig元々の機能ではなく、Drupal 8で独自に実装されているAPIです。

引数にテキストを指定すると翻訳されます。

**例）tフィルターで文字列を翻訳する in Drupal 8**

```twig
{{ "Translatable text"|t }}
{{ 'Translatable text'|trans }}
```
（transフィルターも用意されていますが、tフィルターのエイリアスなので動作は一緒です）

tフィルターは、Drupal 7のときのt関数に相当するものです。

以下は、Drupal 7のときの文字列翻訳の方法ですが、関数が単純にフィルターになっただけなのでとくに迷うことなく使えると思います。

**例）t関数で文字列を翻訳する in Drupal 7**

```html
<p><?php print t('Translatable text') ?></p>
```

tフィルターはあくまでテンプレート上で静的に埋めこまれたテキストの翻訳で使用します。

なお、ユーザーがフォームに入力したデータをtフィルターにかけるのは避けましょう。ユーザーの意図しない内容になったりなど不都合があるからです。



#transブロックで翻訳する

transブロックで囲まれたテキストが翻訳されますが、tフィルターよりも少し複雑な書き方にも対応しています。

まずは、transブロックの基本的な使い方です。

```twig
{% trans %}Translatable text{% endtrans %}
```

transブロックの改行の仕方の違いで翻訳結果に違いは出ません。

```twig
{% trans %}
  Translatable text
{% endtrans %}
```
どちらも同様の翻訳結果になります。
（Drupal8.0の頃は前後にスペースが入ることで翻訳対象にならない不具合があったが解消済）


それから、pluralスイッチというものを使うと、数値型の値が1より大きいかどうかで処理される内容を切り替えることも出来ます。

```twig
{% set count = comments|length %}
{% trans %}
  {{ count }} comment was deleted successfully.
{% plural count %}
  {{ count }} comments were deleted successfully.
{% endtrans %}
```

また、コンテキストや言語コードを指定することもできます。（出力結果は '1月'）

```twig
{% trans with {'context': 'Long month name', 'langcode': 'ja'} %}
  January
{% endtrans %}
```

####**transブロックとtフィルターの使い分けは？**

静的なテキストを翻訳する場合、基本的にはDrupal標準のtフィルターを使いますが、プレースホルダーに対応する場合はtransブロックを利用しましょう。

transブロック内で利用可能なプレースホルダーの形式は```@variable```または```％variable```となりますが、詳しくは次項で紹介していきます。
なお、Drupal 8.0.x の頃に有効だった```!variable```（<font color="red">※</font>）は最新Drupal 8では利用できなくなっていますのでご注意ください。
<font color="red">※{{ variable|raw }}に相当するものでサニタイズしない形式がtransブロックで使えていたが現在は廃止されたため</font>



##プレースホルダー ```@variable```

transブロック内では、プレースホルダーを含む文字列の翻訳が可能です。

@で始まるプレースホルダーは、純粋に変数を置き換えるためのものです。

**翻訳ソース**

```text
Submitted on @date
```

**Twigテンプレート**

```twig
{% set date = content.field_date|render|striptags|trim %}
{% trans %}Submitted on {{ date }}{% endtrans %}
```

置き換えられた変数は、エスケープ処理がされて安全な文字列になります。例えば、下のような文字列が代入されている場合のエスケープされた結果です。

**例）変数に特殊文字を代入する**

```twig
{% set string = '&"<>' %}
{% trans %}
  Escaped: {{ string }}
{% endtrans %}
```

ブラウザのソースを確認すると、特殊文字がエスケープされたことが確認できます。

```html
Escaped: &amp;&quot;&lt;&gt;
```


このときプレースホルダーに指定する変数は、文字列型である必要があります。オブジェクトは、あらかじめrenderフィルターなどを介してテキストに変換しましょう。
変数はプレースホルダーなので翻訳されることはありません。

それに加えて、翻訳ソースの@から始まるプレースホルダーと、transブロック内で埋めこむ変数名、またはオブジェクトのプロパティ名が一致している必要があります。

**翻訳ソース**

```text
Submitted by @username on @created
```

**Twigテンプレート**

```twig
{% trans %}
  Submitted by {{ author.username }} on {{ node.created }}
{% endtrans %}
```

それから、気にする点として、プレースホルダーはHTML属性、JavaScript、またはCSSではなく、サイトに表示される表示文言のための翻訳として使用します。なぜならセキュリティ上のリスク回避の観点から重要なことだからです。



##プレースホルダー ```%variable```

%で始まるプレースホルダーは、置換後は emタグで囲まれて強調されたテキスト表示がされます。
そのうえで、```@variable```と同様に、置き換えられた変数はエスケープ処理がされて安全な文字列になります。

**翻訳ソース**

```text
Submitted on %date
```    

**Twigテンプレート**

```twig
{% set date = content.field_date|render|striptags|trim %}
{% trans %}Submitted on {{ date|placeholder }}{% endtrans %}
```

ブラウザのソースを確認すると、emタグで囲まれていることが確認できます。

```html
<em class="placeholder">2018/01/01 16:20</em>に投稿しました。
```

```@variable``` との違いは、placeholderフィルターを介して変数を置き換えますが、それ以外の仕様は同じです。


##翻訳されたエンティティの値を出力する

コンテンツに翻訳設定されている場合、Twigテンプレート上でその値を取得することができます。

```twig
{% if node.hasTranslation('ja') %}
  {{ node.getTranslation('ja').field_description.processed }}
{% else %}
  {# No translation #}
{% endif %}
```

上のサンプルコードは言語コードのパラメーターを静的に`'ja'`としてしまっており、良くない状態なのであらかじめテンプレートに言語コードが渡るようにしておきましょう。

**例）モジュールのフックを使ってTwigテンプレートに現在の言語コードを渡す**

```php
/**
 * Implements hook_template_preprocess_default_variables_alter().
 */
function mymodule_template_preprocess_default_variables_alter(&$variables) {
  $variables['langcode'] = \Drupal::languageManager()->getCurrentLanguage()->getId();
}
```

そして、Twigテンプレート側では受け取った`langcode`を引数に指定すればOKです。

```twig
{{ node.getTranslation(langcode).field_description.processed }}
```


エンティティ参照している場合も同様に`getTranslation`で取得できます。

**例）コンテンツから参照しているタームの翻訳を取得する**

```twig
{% set term_category = node.field_term_category.entity %}
{% if term_category.hasTranslation(langcode) %}
  {% set term_category = term_category.getTranslation(langcode) %}
{% else %}
  {# No translation #}
{% endif %}
<span class="tag category">{{ term_category.field_category_name.value }}</span>
```

#参考ドキュメント

* [t function api | Drupal API](https://api.drupal.org/api/drupal/core%21includes%21bootstrap.inc/function/t/8.5.x)

* [FormattableMarkup::placeholderFormat | FormattableMarkup.php | Drupal API](https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Render%21FormattableMarkup.php/function/FormattableMarkup%3A%3AplaceholderFormat/8.5.x)

* [Entity Translation API | Drupal 8 guide on Drupal.org](https://www.drupal.org/docs/8/api/entity-api/entity-translation-api)