ユースケース — 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)。
en、en-US、ja、zh-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 ヘッダ
→ 登録済みのデフォルトロケール最初に登録済みロケールへマッチしたシグナルが採用されます。
実装
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 カタログまたは以前の呼び出し)の値が採用されます。 - 最後の呼び出しが勝つ — 同じロケールに対して複数回呼び出した場合、衝突したキーは最後の呼び出しの値で上書きされます。
- 未登録ロケールも安全 —
frやdeのように seed に無いロケールは、最初の呼び出しで新規登録されます。bundle が一度も供給しないキーは設定済みのデフォルトロケール(op.WithDefaultLocale)にフォールバックするので、カタログ全体を翻訳し直さずに部分的なオーバレイだけを出荷しても安全です。
bundle 形式は ICU MessageFormat です(LocaleBundleFromMap を通すことで構築時の検証が入ります)。
確認
# discovery が登録済みロケールを公開
curl -s http://localhost:8080/.well-known/openid-configuration | jq .ui_locales_supported
# ["en", "ja", "fr"]優先順序を手動で確認:
| リクエスト | 描画されるロケール |
|---|---|
| シグナルなし | fr(登録済みデフォルト) |
Accept-Language: es | fr(es 未登録 → フォールバック) |
Accept-Language: ja-JP, ja;q=0.9 | ja |
?ui_locales=ja en(RFC 5646 リスト) | 最初のマッチ = ja |
?ui_locales=ja + cookie が fr | ja(パラメータが cookie より優先) |
翻訳対象
| 表示面 | bundle キー例 |
|---|---|
| ログインフォームのラベル | login.identifier、login.password、login.submit |
| 同意プロンプト | consent.title、consent.scope.profile.description |
| エラーページ | error.invalid_request_uri、error.unsupported_response_type |
| ログアウト確認 | logout.confirm |
キーは表示面に追従します。新しいキーは minor リリースで追加され、bundle の構造が変わるリリースではリリースノートに記載されます。
カスタム UI が引き継ぐ場合
op.WithSPAUI(...) または op.WithConsentUI(...) を使うと テンプレート側が i18n の責務 を持ちます。ライブラリはロケールネゴシエーションを引き続き行い、interaction context 経由でロケールを公開しますが、seed bundle はビルトインテンプレート専用です。
続きはこちら
- カスタム同意 UI — bundle キーでは足りなくなったら。
- SPA / カスタム interaction — フル SPA は自前で i18n を持つ。