Skip to content

ユースケース — i18n / ロケールネゴシエーション

ui_locales とは

OIDC Core 1.0 §3.1.2.1 は /authorize 要求に ui_locales パラメータを定めています。RP が空白区切りの BCP 47 言語タグ(RFC 5646 — 例: ja en-US)を送ると、OP は自分が翻訳を持っている範囲で最優先言語を選んでログイン / 同意 UI を描画します。

RP が ui_locales を送らない場合でも、OP 自身が発行するロケール cookie や Accept-Language HTTP ヘッダ(RFC 9110)からユーザの希望を拾うことができ、最終的にはあらかじめ登録したデフォルトロケールへフォールバックします。

このページで触れる仕様
  • OpenID Connect Core 1.0 — §3.1.2.1(ui_locales リクエストパラメータ)
  • RFC 5646 — Tags for Identifying Languages(BCP 47)
  • RFC 9110 — HTTP Semantics, §12.5.4(Accept-Language
用語の補足
  • BCP 47 — IETF の言語タグ規格(RFC 5646)。enen-USjazh-Hant-TW のように「言語 - 任意のスクリプト - 任意の地域」で記述します。OIDC の ui_locales パラメータは、優先順位順に並べた BCP 47 タグを空白区切りで載せます。
  • Accept-Language — ブラウザがユーザの言語選好を q= quality factor 付きで通知する標準 HTTP リクエストヘッダ(ja-JP, ja;q=0.9, en;q=0.5)。RP が ui_locales で言語を固定しない場合、OP はこちらにフォールバックできます。
  • ICU MessageFormat — 文字列連結では扱いきれない複数形・性別・数値 / 日付フォーマットをロケール対応で扱える、bundle 値用のフォーマット記法({count, plural, one {1 件} other {# 件}})です。

優先順序

OP は組み込みの同意 / ログイン画面の言語を、優先順序に従って決定します:

ui_locales リクエストパラメータ
  → ロケール cookie
    → Accept-Language ヘッダ
      → 登録済みのデフォルトロケール

最初に登録済みロケールへマッチしたシグナルが採用されます。

ソース: examples/16-i18n-locale

実装

go
import "github.com/libraz/go-oidc-provider/op"

frBundle, _ := op.LocaleBundleFromMap("fr", map[string]string{
  "login.identifier": "Identifiant",
  "login.password":   "Mot de passe",
  /* ... */
})

brandJa, _ := op.LocaleBundleFromMap("ja", map[string]string{
  "consent.title": "Acme へのアクセス許可",  // 1 キーだけ上書き
})

op.New(
  /* 必須オプション */
  op.WithDefaultLocale("fr"),
  op.WithLocale(frBundle),  // 新規ロケールを登録
  op.WithLocale(brandJa),   // seed の ja bundle に 1 キーだけ重ねる
)

ライブラリは 英語(en日本語(ja の seed bundle を同梱しています。op.WithLocale は置き換えではなく 層を重ねる 形で動作するため、組み込み側は変更したいキーだけを供給すれば足り、それ以外は seed の翻訳がそのまま使われます。

マージのルール

  • キー単位でマージ — 各 op.WithLocale 呼び出しは、同じロケールに対する以前の bundle の上にキー単位で重ねられます。供給されなかったキーは下層(seed カタログまたは以前の呼び出し)の値が採用されます。
  • 最後の呼び出しが勝つ — 同じロケールに対して複数回呼び出した場合、衝突したキーは最後の呼び出しの値で上書きされます。
  • 未登録ロケールも安全frde のように seed に無いロケールは、最初の呼び出しで新規登録されます。bundle が一度も供給しないキーは設定済みのデフォルトロケール(op.WithDefaultLocale)にフォールバックするので、カタログ全体を翻訳し直さずに部分的なオーバレイだけを出荷しても安全です。

bundle 形式は ICU MessageFormat です(LocaleBundleFromMap を通すことで構築時の検証が入ります)。

確認

sh
# discovery が登録済みロケールを公開
curl -s http://localhost:8080/.well-known/openid-configuration | jq .ui_locales_supported
# ["en", "ja", "fr"]

優先順序を手動で確認:

リクエスト描画されるロケール
シグナルなしfr(登録済みデフォルト)
Accept-Language: esfr(es 未登録 → フォールバック)
Accept-Language: ja-JP, ja;q=0.9ja
?ui_locales=ja en(RFC 5646 リスト)最初のマッチ = ja
?ui_locales=ja + cookie が frja(パラメータが cookie より優先)

翻訳対象

表示面bundle キー例
ログインフォームのラベルlogin.identifierlogin.passwordlogin.submit
同意プロンプトconsent.titleconsent.scope.profile.description
エラーページerror.invalid_request_urierror.unsupported_response_type
ログアウト確認logout.confirm

キーは表示面に追従します。新しいキーは minor リリースで追加され、bundle の構造が変わるリリースではリリースノートに記載されます。

カスタム UI が引き継ぐ場合

op.WithSPAUI(...) または op.WithConsentUI(...) を使うと テンプレート側が i18n の責務 を持ちます。ライブラリはロケールネゴシエーションを引き続き行い、interaction context 経由でロケールを公開しますが、seed bundle はビルトインテンプレート専用です。

続きはこちら