記事概要

HTTPアクセスからユーザーが設定している言語を取得する方法を解説すること、またそれに伴った周辺技術仕様のざっくりとした解説。
想定した用途は現在のアクセス言語の表示など。

HTTPにおけるユーザーの言語設定

Accept-Languageヘッダーにてクライアント側の優先順序リストの値が送られてくる。
https://tools.ietf.org/html/rfc7231#section-5.3.5
各ブラウザもこういった規範に従った値を送ってきているはず。

Accept-Language ヘッダー について

具体的な値の例

chrome, firefox: ja,en-US;q=0.9,en;q=0.8
safari: ja-jp
※ 確認した環境: Mac Mojave 10.14.6 chrome:78.0.3904.70 safari:12.1.2 (14607.3.9) firefox:70.0.1
日本語を最優先言語に設定している。

実際送ってくるブラウザがいるのか調べていないけど、 * , zh-cmn-Hans などもある。
ja,en-US;q=0.9,en;q=0.8 に含まれる値には、

  • ja , en-US , en: language tag(言語タグ)
  • q=0.9 , q=0.8 : quality value(品質値)

があります。
APIでアクセスしてくるクライアントが独自HTTPクライアントなど、値がない場合もあり得ます。
ここにどういう値が含まれうるのか?を調べたいと思いますが、複数の仕様が絡み合ってくる為まずそこから理解する必要があります。

Accept-Languageヘッダーの取りうる値

Accept-Languageヘッダーはmozillaのページからもリンクされている RFC7231 セクション 5.3.5: Accept-Languageの記述に従っています。safariは一個の言語しか送ってこないなどブラウザによって実装の差異はあるようですが、このルールには基づいていそうです。
そこに記載されている仕様は下記です。

Accept-Language = 1#( language-range [ weight ] )
language-range  =
          <language-range, see [RFC4647], Section 2.1>

初見では意味が分からないかと思います。
内容の要約を書くと、language-range に weight オプションを渡したもののリストで表します。Language Range(言語範囲)はRFC4647 Section 2.1を見てね。 って感じです。
この仕様自体がABNFという記法で書かれており、仕様の書き方の解説がRFC2616 Section 2に載っています。
上記のリンクはABNF記法それ自体の解説ではなくてこのRFC内で書かれる仕様に対する記述ルールの説明です。

次の数項で記法を解説します。正確な記述はリンク先を参照してください。

name = definition

= の左が定義の名前、右がその定義を示す。
Accept-Language が名前、 1#( language-range [ weight ] ) が定義となります。

# rule

<n>#<m> element で表されるリスト形式。コンマで区切られたelementを最低n回、最大m個の要素が出現します。
空白や空要素を含む。
1#( ) で最低一個の language-range [ weight ] が含まれることを示します。

[rule]

[] で囲まれた内容はオプションの要素。
language-range には weight オプションが含まれることを示します。

*rule

<n>*<m>element で表される繰り返し。
後述する Language Range(言語範囲) の仕様に出てきます。
elementが最低n回、最大m個出現します。

ALPHA

大文字と小文字のアルファベットを表します。

UPALPHA        = <any US-ASCII uppercase letter "A".."Z">
LOALPHA        = <any US-ASCII lowercase letter "a".."z">
ALPHA          = UPALPHA | LOALPHA

DIGIT

0から9の数値を表します。

DIGIT          = <any US-ASCII digit "0".."9">

Language Range(言語範囲)

RFC4647 Section 2.1に記載されています。
ASCII英数字、もしくは * で構成され、ハイフンで区切られた文字列。大文字小文字は区別されず、Language Tag(言語タグ)のsubtagで構成されます。
言語範囲の仕様は下記のABNFで表されます。

language-range   = (1*8ALPHA *("-" 1*8alphanum)) / "*"
alphanum         = ALPHA / DIGIT

* の場合、全ての言語を表します。
Language Tag(言語タグ)はこの * 以外の値のことを指す、と思って問題ないと思います。

1*8ALPHA は先述の *rule で、1~8文字のalphanumを表し、それが複数個ハイフンで結合されたものあるいは単一のものが (1*8ALPHA *("-" 1*8alphanum)) となります。
ALPHA は大文字あるいは小文字のアルファベット、 DIGIT は数字です。この二つはRFC2616 Section 2.2に記載されています。
Accept-Languageヘッダーにおける言語範囲で使われているのは重み付けをして優先度リストにしたものです。
後述のQuality Valuesで重み付けされ、その値を含みます。

Quality Values(品質値)

品質値の仕様は下記のABNFで表されます。

weight = OWS ";" OWS "q=" qvalue
qvalue = ( "0" [ "." 0*3DIGIT ] )
       / ( "1" [ "." 0*3("0") ] )

OWSoptional whitespace で必須ではない空白を表し、 先頭に ; をつけた q="qvalue" で表されます。
qvalue は例えば 0.8 0.001 1 など、0~1の小数点3桁までの値で表されます。
Accept-Languageヘッダーにおける品質値はこれで、値が省略された時は1(最優先)、0の時は受け入れ不可な言語と見なされます。
ja,en-US;q=0.9,en;q=0.8 というリストであれば、ja(日本語)が最優先、en-US(アメリカの英語)が次、en(英語)がその次…と言った具合に指定されています。

ここで指定されている jaen という値がいわゆる言語コードと呼ばれる値で、 US は国コードと呼ばれたりします。
これらを組み合わせた値を言語タグと呼びます。

Language Tag(言語タグ)について

W3Cの記事 HTMLとXMLにおける言語タグを参考にしています。
元々はRFC5646で説明されている内容で、非常に丁寧な日本語で説明されています。

言語タグの仕様は下記のABNFで表されます。

 Language-Tag  = langtag             ; normal language tags
               / privateuse          ; private use tag
               / grandfathered       ; grandfathered tags

言語タグはHTMLのlang属性などにも使用され、そのような場合の為に様々な値を取り得ます。
また、言語タグを構成するそれぞれの要素をsubtag(サブタグ)と呼んでいます。
今回対象にしたいのはlangtagのみです。

 langtag       = language
                 ["-" script]
                 ["-" region]
                 *("-" variant)
                 *("-" extension)
                 ["-" privateuse]
 language      = 2*3ALPHA            ; shortest ISO 639 code
                 ["-" extlang]       ; sometimes followed by
                                     ; extended language subtags
               / 4ALPHA              ; or reserved for future use
               / 5*8ALPHA            ; or registered language subtag

 extlang       = 3ALPHA              ; selected ISO 639 codes
                 *2("-" 3ALPHA)      ; permanently reserved

 script        = 4ALPHA              ; ISO 15924 code

 region        = 2ALPHA              ; ISO 3166-1 code
               / 3DIGIT              ; UN M.49 code

 variant       = 5*8alphanum         ; registered variants
               / (DIGIT 3alphanum)

 extension     = singleton 1*("-" (2*8alphanum))

                                     ; Single alphanumerics
                                     ; "x" reserved for private use
 singleton     = DIGIT               ; 0 - 9
               / %x41-57             ; A - W
               / %x59-5A             ; Y - Z
               / %x61-77             ; a - w
               / %x79-7A             ; y - z

RFCの記載ではこうなっていますが、W3Cの記事の方が分かりやすくて、そちらによると言語タグは下記に分類されています。

言語-拡張言語-用字-領域-変種-拡張-私用

それぞれがハイフンで結合された値で言語タグが成り立ちます。

  • language : 言語
  • extlang : 拡張言語
  • script : 用字
  • region : 領域
  • variant : 変種
  • extension : 拡張
  • privateuse : 私用

language : 言語

いわゆる言語コード( jaen )は language に含まれ、二文字か三文字の大文字小文字を区別しないアルファベットで表されます。
全ての言語タグのサブタグはIANAが管理しており、レジストリによって管理されています。
language, extlang, script, region, variant のそれぞれのtypeのタグが見つかると思います。
このうち、言語のサブタグに使われるのがISO 639によって定義される値を参照します。

extlang : 拡張言語

拡張言語はjsl(Japanese Sign Language)として日本の手話を表したり、 arq(Algerian Arabic)としてアラビア語アルジェリア方言を表したりします。
しかしそれぞれにPreferred-Valueという値が設定されており、基本的に拡張言語の使用は推奨されていません。

script : 用字

用字とは文章を書く際に用いる字や語の分類です。漢字表記やひらがな表記などを区別します。
例えばひらがなとカタカナで表示される場合は Hrkt や、音声録音であることを表す為に Zxxx が指定されます。
RFC 4646で導入されましたが、最初の共著者からも推奨されないことが述べられているようです。

region : 領域

いわゆる国コード( JPUS )は region に含まれ、二文字のアルファベットか三文字の数値から表されます。
アルファベットの値はISO 3166-1 alpha-2で定義され、UN M.49(ISO 3166-1 numeric)で数字の値が定義されています。
数字は030(東アジア)など、alpha-2で表されない地域を扱います。

その他

variant など、その他もあまり使われることがありません。今回の記事では省略します。

言語タグの取りうる値

言語タグの取りうる値としては、8文字以内の数値あるいはアルファベットがハイフンで区切られた文字列で表されます。

まとめ

Accept-Languageヘッダーの値は、言語範囲の優先度リストです。
リストの値は , で区切られ、 言語タグ(ja-JPなど);品質値(q={[0~1]}) で表されます。

この記事の内容について

書かれた内容は新しいRFCなどによって変わる可能性があります。
最新の仕様を確認の上、参考にして頂ければ幸いです。