Accept-Languageを見て言語別に分けるApacheの多言語化をやってみたのでメモ

index.html.jaとかindex.html.enとかでApacheだけで多言語化出来る事は一応知ってたけど、phpとか使わないサイトだけど多言語化はするなんて、今までやった事無かった。たまたま、使えそうな機会があったので、やってみました。

ブラウザかのHTTP RequestにあるAccept-Language headerを見て、言語別に振り分けるんですが、複数の言語を優先度付きで設定できたりen-USといったロケールのサブセットも指定できるので、ちょっと予想しにくい動きをします。
この辺も、色々設定してcurlで確認してみたので書いておきます。というか、こっちを書きたい。

言語化のやり方

mod_negotiation + mod_mime + FileInfo + Multiviews Options ディレクティブを使った多言語化

いまいち、この方法の名前みたいなのが分からないけど、mod_negotiationが主となる方法っぽい。他のmod_mimeやディレクティブは無くてもやる方法もあるみたい*1だけど、今回は全部セットで使います。
今回は、基本的に日本語のロケールが指定してあれば日本語を、それ以外は英語になる様に設定しました。

コンテントネゴシエーション - Apache HTTP サーバ バージョン 2.2

この機能については、Apacheドキュメントではこのページが詳しい様子。すると、コンテンツネゴシエーションが、この方法の名前ってことになるのかな?なんかしっくりこないけど。

httpd.conf
AllowOverride FileInfo Options=MultiViews

今回は.htaccessに設定を出したので、httpd.confのAllowOverrideに設定します。
後述する.htaccessを、多言語化したいファイルのあるディレクトリに作るけどFileInfoAddLanguageに、Options=MultiViewsは、そのままMultiViews Optionsディレクティブを使う為に。ちなみにAllowOverride AllだとMultiViewsは上書き可能にならないらしい。

.htaccess
AddLanguage ja-jp-mac  .ja
AddLanguage ja-jp          .ja
AddLanguage ja               .ja
Options +MultiViews

AddLanguageの3行は無くても、大抵はhttpd.confに基本的な設定があるので振り分けはされるけど、あった方が多分意図した振り分けになると思う。本当はen-USen-GBとかも書いておいた方が良いのかもしれないけど、あまり深追いしたくなかったので、今回は無しにしました。

用意したファイル
  • index.html.ja
  • index.html.en

index.htmlは置いてません。あまりちゃんと確認してないけどindex.htmlも置いてあると、多言語化よりもindex.htmlが優先されているようでした。

挙動の確認

$  curl -v -H 'Accept-Language: ja-JP, en;q=0.8' -I http://example.com

基本的にcurlコマンドを使って挙動を確認しました。

Accept-Languageに複数のロケールがあり、優先度が不明な場合、LanguagePriorityによって決まる。
Accept-Language: ja, en

もっと下に、httpd.confのこれに関連する部分を抜粋してあります。そのhttpd.confと前述の.htaccessが設定してある状態で考えます。
jaenが指定されているけど優先度が不明。LanguagePriorityではenが最優先なので、index.html.enが出力される。

mod_negotiation - Apache HTTP サーバ バージョン 2.2

AddLanguage ja .jaはAccept-Language: ja-JPとは合致しない

とても不便ですが、ロケール*2のサブセットはサブセット無しのものには合致しないようです。と書いても、かなり分かりにくいと思うので具体例を書きます。

AddLanguage ja .ja
AddLanguage en .en
Accept-Language: ja-JP, en;q=0.8

この例では、AddLanguageがこの2つだけの場合で考えます。AddLanguage ja-JP .jpという設定が無いので、優先度が0.8のenが選択されて、index.html.enが出力されます。前述のやり方でja-JPja-JP-macを設定してるのは、この為です。

サブセットのあるロケールだけを指定した場合は、希望した言語が選択される場合がある
AddLanguage ja .ja
AddLanguage en .en
Accept-Language: ja-JP

この例でも、AddLanguageがこの2つだけの場合で考えます。AddLanguage ja-JP .jpという設定が無くLanguagePriorityではenが最優先なのでenが選択されると思いきや、jaが選択されます。

AddLanguage ja .ja
AddLanguage en .en
Accept-Language: ja-Foo, ja-Bar

こんな、あり得ないロケールであっても、ロケールja系のみしか指定されてないなら、LanguagePriorityに関わらずjaが選択されるみたい。

前述の挙動はForceLanguagePriorityの設定による
ForceLanguagePriority Prefer Fallback
#ForceLanguagePriority Prefer
#ForceLanguagePriority Fallback

これはPreferFallbackの、2つの場合でLanguagePriorityを使うかどうかを設定します。ForceLanguagePriority Prefer Fallbackの場合は、どちらの場合でもLanguagePriorityを使います。

Prefer
優先度の同じロケールが指定されている場合にLanguagePriorityを見て、優先されるロケールを選択する様になる。設定しなければ300 Multiple Choicesが返されます。
Fallback
サーバー側で用意してないロケールを指定した場合にLanguagePriorityを見て、優先されるロケールを選択する様になる。設定しなければ406 Not Acceptableが返されます。

mod_negotiation - Apache HTTP サーバ バージョン 2.2

Apacheドキュメントの挙動について書いてあるところ

コンテントネゴシエーション - Apache HTTP サーバ バージョン 2.2

かなり難しい表現だけど、どういうアルゴリズムロケール*3が選択されるか、解説してあります。

httpd.confにある設定の抜粋

#
# DefaultLanguage and AddLanguage allows you to specify the language of 
# a document. You can then use content negotiation to give a browser a 
# file in a language the user can understand.
#
# Specify a default language. This means that all data
# going out without a specific language tag (see below) will 
# be marked with this one. You probably do NOT want to set
# this unless you are sure it is correct for all cases.
#
# * It is generally better to not mark a page as 
# * being a certain language than marking it with the wrong
# * language!
#
# DefaultLanguage nl
#
# Note 1: The suffix does not have to be the same as the language
# keyword --- those with documents in Polish (whose net-standard
# language code is pl) may wish to use "AddLanguage pl .po" to
# avoid the ambiguity with the common suffix for perl scripts.
#
# Note 2: The example entries below illustrate that in some cases 
# the two character 'Language' abbreviation is not identical to 
# the two character 'Country' code for its country,
# E.g. 'Danmark/dk' versus 'Danish/da'.
#
# Note 3: In the case of 'ltz' we violate the RFC by using a three char
# specifier. There is 'work in progress' to fix this and get
# the reference data for rfc1766 cleaned up.
#
# Catalan (ca) - Croatian (hr) - Czech (cs) - Danish (da) - Dutch (nl)
# English (en) - Esperanto (eo) - Estonian (et) - French (fr) - German (de)
# Greek-Modern (el) - Hebrew (he) - Italian (it) - Japanese (ja)
# Korean (ko) - Luxembourgeois* (ltz) - Norwegian Nynorsk (nn)
# Norwegian (no) - Polish (pl) - Portugese (pt)
# Brazilian Portuguese (pt-BR) - Russian (ru) - Swedish (sv)
# Simplified Chinese (zh-CN) - Spanish (es) - Traditional Chinese (zh-TW)
#
AddLanguage ca .ca
AddLanguage cs .cz .cs
AddLanguage da .dk
AddLanguage de .de
AddLanguage el .el
AddLanguage en .en
AddLanguage eo .eo
AddLanguage es .es
AddLanguage et .et
AddLanguage fr .fr
AddLanguage he .he
AddLanguage hr .hr
AddLanguage it .it
AddLanguage ja .ja
AddLanguage ko .ko
AddLanguage ltz .ltz
AddLanguage nl .nl
AddLanguage nn .nn
AddLanguage no .no
AddLanguage pl .po
AddLanguage pt .pt
AddLanguage pt-BR .pt-br
AddLanguage ru .ru
AddLanguage sv .sv
AddLanguage zh-CN .zh-cn
AddLanguage zh-TW .zh-tw

#
# LanguagePriority allows you to give precedence to some languages
# in case of a tie during content negotiation.
#
# Just list the languages in decreasing order of preference. We have
# more or less alphabetized them here. You probably want to change this.
#
LanguagePriority en ca cs da de el eo es et fr he hr it ja ko ltz nl nn no pl pt pt-BR ru sv zh-CN zh-TW

#
# ForceLanguagePriority allows you to serve a result page rather than
# MULTIPLE CHOICES (Prefer) [in case of a tie] or NOT ACCEPTABLE (Fallback)
# [in case no accepted languages matched the available variants]
#
ForceLanguagePriority Prefer Fallback

httpd.confはこんな感じで、たぶんCentOSyumで入れたApacheのままになっていると思います。今気がついたけどDefaultLanguageコメントアウトされたままで、これをちゃんと設定したら、また挙動変わるのかも。

*1:type-map ファイルを使う

*2:言語タグというらしい

*3:variantと表現されてる