新刊出ました。
多言語対応サイトはD7の時代からいくつかやってきているけれど、多言語機能は本質的に難しい。
Drupalの言語には、サイトのデフォルトの言語、コンテンツ(エンティティ)言語、表示言語があると理解している。元々英語で作られているが、メッセージなどユーザーに目に見える文字列はt()で囲むことになっている。この関数については、文字列整形用関数 format_string() t() st() とそのキーの接頭辞による効果まとめが詳しい。Interface Translationは、これに対応したdrupal-10.1.5.ja.poなどが、siteのfiles/translationsに納められている。ただし、t()はこのファイルを見ているのではなく、データベース上のlocales_sourceとlocales_targetに納められているデータを見ている。構成にかかわる情報(Configuration entity)もコンテンツ情報(Content entity)も全てデータベース上に納められている。
Drupalではnodeなどのコンテンツはフィールド単位でテーブルになっている。
例えば、英語でインストールして英語を追加したサイトで、Basic Pageのコンテンツを作成すると、以下のようなテーブルが作成される(リビジョンの話には触れていない)。
前半は、Content TranslationでBasic pageを翻訳対象にマークする前で、後半はマークした後の結果だ。
違うのは、node_field_dataのcontent_translation_sourceとcontent_translation_outdatedが加わったところ。このコンテンツを、日本語に訳すと、以下のように変わる。
リビジョンが上がるので、vidやrevision_idは2になるが、nidは同じ1、つまり/node/1のnode_field_dataとnode__bodyのレコードは2件となる。さらに、日本語で基本ページを追加すると以下のようになる。
日本語で基本ページを作成したので、nodeのlangcodeはjaとなる。fieldのlangcodeもjaとなる。データベース的に読むと、nid/entity_idとlangcodeが結合ユニークキーになっているのが想像できる。
これを、Viewsで無限定に取り出すと以下のようになる。
テストコンテンツのOriginal languageがなぜJapaneseになるのかよくわからないので、ググってみたら、Original language of entities not accessible in views anymoreというissueがあった。node_field_dataがエンティティだから、その言語をOriginal languageとして出しているが、nodeのlangcodeを出すのがD7時代の互換性を保つ方法だろうということだが、もう使うの止めませんかという提案になっている。良い子は使うなと受け取るべきだろう。通常は、Default translationとTranslation languageを組み合わせれば問題なく所期の結果を得ることができる。Default translationをTrueでフィルターすれば、全件1回ずつ出てきて、レンダリング言語をページの言語にすれば、翻訳があればその言語で出力され、翻訳がなければそのコンテンツのDefault translationのエンティティが表示される。逆に翻訳があるものだけを表示したければ、Translation languageでフィルターをかければ良い。
ちなみに、Default translationは重要で、そのレコードを消そうとすると、翻訳も全部消えるけど後悔しない?というメッセージが出る。つまり、Default translationとなっているエンティティを切り替えることはできないということだ。サイトのデフォルト言語は後で変えられないという制約もある。
あらゆる翻訳を対等に扱えたら良いのにと思うのだが、いろいろなケースを想定すると中々うまく行かないのだ。日本語だけで使うなら、それほど問題は出ないのだが、多言語を扱うとサイトのデフォルト言語が重要な意味を持つので、これをDrupalのベース言語の英語にしておかないと様々な困難に直面することになる。日本語サイトから多言語サイトに本格的に移行するのであれば、英語でインストールして、必要な言語を追加した上で、マイグレーションを行って行くのが良いだろう。
Configuration entityはconfigテーブルに納められていて、collectionにNULLかlanguage.jaのようなロケール情報があり、nameとdataのカラムがある。Configurationをdrush cexを使って吐き出すと、syncの下にnameのymlファイルで値が納められる。Configuration translationデータが存在すると、sync/language/jaの下にnameのymlファイルで値が納められるので状況を把握できる。ラベルとメッセージなどのテキストなどがCongiguration entityの中に含まれていれば、それが翻訳対象となり、翻訳対象は管理画面の/admin/config/regional/config-translationで管理できる。ただ、何に訳を当てるべきなのかは実は難しい。例えば、Language switcherの場合、日本語モードで「日本語」、「英語」というラベルで表示されていたら、英語しか理解できない人は「英語」を押下することができないだろう。また、ソート順にも影響を与えるため、国名が漢字に翻訳されていると探すのが困難だったりする。これはDrupalに固有な問題ではなく多言語対応のユーザーインタフェースをどう考えるかという話である。
改めて原点に帰れば、Configuration entityはノンプログラミングで構成定義をすればサイトの挙動を変えることができるもので、コンテンツタイプを作り、フィールドを追加していくことで、PHPやSQLを知らなくても店舗コンテンツなどを定義することができるメリットがある。フィールド名とそのラベルが代表的な例となるが、Drupalではコンテンツタイプを作ると必ずtitle(マシン名)が必須項目となる。データベースで見れば、node_field_dataのカラム名だ。
このTitleフィールドに「Shop name」といったラベルを与えるのもConfigurationの一例となる。元々は、別に翻訳のことを考えてConfigurationを作ったわけではなく、ラベルにTitleと違う名前をつけたかったという話に過ぎない。Configuration translationは、Configurationの要素の中で、ラベルなどを翻訳可能にすれば他言語にも対応できるようになるという考え方で整理されている。
今回、実際に執筆したのは2章(の元ネタ)だが、どういう仕組で、どういう思想でDrupalが多言語対応しているかについては改めて学び直すことになった。入門書なので、書ける範囲は限られるが、Drupalもシステムなので、データ構造の理解こそが本質的理解の鍵となる。どうやったら、ノウハウだけでなく構造的な理解を容易にするような書籍が書けるのか中々悩みは尽きない。
Drupalは本当によくできていると思う。ぜひもっと普及させたいと願ってやまない。