Open10

LDML(Unicode Locale Data Markup Language)全部読む

SajiSaji

この Scrap について

この Scrap は Unicode が仕様を策定している Locale Data Markup Language 通称 LDML をひたすら読んでいくログとして作成されています。

https://www.unicode.org/reports/tr35/

LDML とは簡単に言うと Unicode が策定している仕様で、ロケールや国際化に関するあらゆるデータを記録するための統一的な XML フォーマットです。LDML についての詳細や、Unicode を中心とする仕様、データベース、ライブラリについては去年書いた以下の記事が参考になるかと思います。

https://zenn.dev/sajikix/articles/intl-advent-calendar-24-06

LDML を読む目的

去年 Intl を総浚いする Advent Calendar を書いたのですが、その後も Intl を追ったり、i18n 周りの仕様や議論を追っていく中で、LDML や CLDR を基本とした話が多く、LDML 自体をしっかり読んで理解しておく必要があるなと感じたためです。

LDML の構成

LDML はトップページにあるように Part 1 ~9 まででと 2 つの Appendix から構成されています。

  • Part 1: Core (languages, locales, basic structure)
  • Part 2: General (display names & transforms, etc.)
  • Part 3: Numbers (number & currency formatting)
  • Part 4: Dates (date, time, time zone formatting)
  • Part 5: Collation (sorting, searching, grouping)
  • Part 6: Supplemental (supplemental data)
  • Part 7: Keyboards (keyboard mappings)
  • Part 8: Person Names (person names)
  • Part 9: MessageFormat (message format)
  • Appendix A: Modifications
  • Appendix B: Acknowledgments

この Scrap ではおよそ 25 日(今日を抜くので 24 日)をかけて各 Part を読み進めていく予定です。

書くこと

この Scrap はあくまで読んだログなので基本的な定義や個人的に面白かった点や、わからなかった点などをまとめていきます。解説記事のように体系的にまとめることは目指していません。(これが終わって気力があれば解説記事も書くかもしれませんが)

同じように LDML を読みたい人や部分的に参照したい人(こっちの方が圧倒的に多数だと思いますが)の参考になればくらいで書く予定です。

SajiSaji

導入と適合性など

導入に関して

  • インターネットの登場で国際間の連携動作が必要になった
  • ロケール依存データにはシステム間で大きな差異が残ってる
  • ロケール依存データが違うと特に照合順序の違いなどで連携の問題となることがある
  • そのためにロケールデータをやり取りするXML形式であるLDMLを定めるよ
  • LDMLを利用してUnicode 共通ロケールデータリポジトリ(CLDR)は書かれているよ

適合性

LDMLやCLDRのデータの利用方法は制限されていないが、LDMLやCLDRデータへ準拠してると言いたい場合に何を満たすべきかは定めてる
具体的には

  • LDML
    • 準拠する部分を特定すること
    • 各セクションの説明に従って要素と属性を解釈する
  • CLDR
    • 使用するCLDRの種類を宣言する
    • CLDRのデータを上書きする場合とaltデータを利用する場合は宣言する

また、LDML.CLDRに一般的に適合してると主張する場合、例外としてあげられるもの以外は適合性を主張すると解釈される

CLDRのカスタマイズについて

一般的CLDR構造を変更することはできない。ただし、ロケール識別子などには挙動を拡張するメカニズムが用意されてる(私用言語コードやxxx拡張)ので、それを利用して拡張することはできる。また言語コードなどで非推奨な以前のものが使われてる場合は、languageAliasデータなどを変更する必要があるかも。

また、カスタマイズの宣言をする場合は以下も許される

  • データの省略
  • データの追加
  • データの上書き
    • alt属性があるのでそれを使うのが推奨っぽい

CLDRにはtestData もあるので、それも使える、カスタマイズした場合は一部skipする必要があるかも。

EBNFについて

仕様内で利用されるEBNFはW3C XML表記法利用されるEBNFの派生
微妙に表記法違うとこがあるので気をつけよう

SajiSaji

ロケールとは何か

ここでは「世界中の広範囲に渡って共有される傾向のある一連のユーザー設定を参照する識別子(ID)」と定めてる

  • 原文 : An identifier (id) that refers to a set of user preferences that tend to be shared across significant swaths of the world

ロケールは固定されたものではなく、ユーザーによって特定の設定を上書きできるべき。例えば「システムはアメリカだけど、ヤーポン法は使いたくない」とかできるべき。もっと抽象的に言えば「特定のユーザーについて記憶しておきたい、数多くの設定セット」の1つにすぎない。

またロケールデータは以下のような要因によって変化することがある

  • 国境の変更
  • 政府の遷移
  • 新しい標準の導入
  • etc

だからバージョン管理は必要

また、CLDRにあるデータは「考えられるすべてのサービスで使用される可能性のあるすべてのデータを表すもの」ではない。ロケール間の主な違いは言語によるもの(+国と地域)だが、使用されるロケールと言語の境界は結構曖昧。

SajiSaji

Unicode Language and Locale Identifiers

いわゆるみんなが慣れ親しんたen-USみたいなものの定義や他使用との関連、LDML/CLDR 内で別に利用される形式などを細かく定めたもの。

基本的に LDML では言語やロケール、地域、通貨などなどを区別するために BCP47 に基づいた識別子を用いる。BCP47 については去年書いたので省略
https://zenn.dev/sajikix/articles/intl-advent-calendar-24-02

↑ にもあるように、u 拡張と t 拡張があり、これらの拡張に関しては Unicode 側で仕様を持っている

Unicode Language Identifier

詳しくはEBNFを見て

ざっくりまとめ

  • -_セパレータとして許すような広い識別子のフォーマット
    • BCP47 は基本-しか許さないはず...
  • 各 sub タグについて
    • variant は重複しちゃいけない
    • 理論的には、IANA 登録プロセスを通じて unicode_language_subtag が 3 文字を超えることもありえるが、実際にはそのようなことは起こっていない
    • また unicode_language_subtag におけるundは省略 unicode_script_subtag がある場合に省略できるため、理論上 4 文字の unicode_script_subtag は存在しなくなる
      • なので ebnf でも= alpha{2,3} | alpha{5,8};になってる
    • どちらにしろこのような言語コードは BCP47 として有効じゃないので一般的な交換には向かない
      • じゃあ何に使うねん → 「翻字者の識別やフォントの ScriptLangTag 値などの特定のプロトコルを対象としてる」

例 : en-US, en_GB,uz-Cyrl など

SajiSaji

Unicode Locale Identifier

Unicode Locale Identifier = Unicode Language Identifier + 様々な拡張

詳しくはEBNFを見る

整形式(より整った形式)制約

  • 同じ singleton を持つ拡張は複数存在してはならない。
    • 例えば、-u-ca-buddhist-u-hc-h12 は不正
  • 同じukey または tkey は複数存在してはならない
    • 例えば、-u-ca-buddhist-ca-chinese は不正
  • tlang における variant サブタグのシーケンスは、重複を含んではいけない
  • private use 拡張 (-x-) は、他のすべての拡張の後に続く必要がある

厳密には Unicode Locale Identifier ≠ Unicode Language Identifier なんだけど、実際には Unicode Language Identifier としても機能することが多い。

そのためLDMLでは「locale」データという用語を、両方のタイプのデータを包含する緩い意味で使用している。

  • メモ : 多分世間的にも「locale」って言ったらUnicode Locale Identifier全体を指しそう(u拡張とかがどこまで有名か知らんが)

用語・表記ブレ

  • codeという用語が"subtag"の代わりに、"territory"が"region"の代わりに使用されることがある
  • primary language subtagはbase language code とも呼ばれる
    • 例 : enen-US の primary language subtag であり base language code でもある

推奨

  • すべての識別子フィールド値は大文字小文字を区別しない....が、BCP47の推奨事項に従うことが推奨される
  • -_のどちらのセパレータも許されるが、交換には-を使用することが推奨される

Unicode BCP 47 locale identifier

unicode_local_id(= unicode_language_id + extensions + privateuse) のうち、以下の追加制約を満たすと Unicode BCP 47 locale identifierと言える

  • BNFの区切り文字は、unicode_language_idとunicode_locale_idにおいては、[-]のみに制限
  • 最初のサブタグはunicode_language_subtag
    • script_subtagやrootサブタグ(und)とかはダメ

BCP 47 language tagとの関係

  • 整形式のUnicode BCP 47 locale identifier → 整形式のBCP 47 language tag は満たす
  • ただし、逆は成り立たない
    • extlang subtag、irregular subtag、または先頭の'x' subtagを含むBCP 47 language tagは、 整形式のUnicode BCP 47 locale identifierにはなりえない

Unicode CLDR locale identifier

unicode_local_id(= unicode_language_id + extensions + privateuse) のうち、以下の追加制約を満たすと Unicode CLDR locale identifier と言える

  • EBNFにおける区切り文字は、unicode language_idおよびunicode_locale_idにおいて、アンダースコア[*]のみに制限
  • unicode_language_id "und" は "root" に置き換えられる
  • 最初のサブタグは unicode_script_subtagではダメ

メモ : たまに見る _区切りのロケール

CLDRデータは下位互換性(以前のバージョンとの互換性)を保つために、このUnicode CLDR locale identifier形式を使用している

Canonical Unicode Locale Identifiers

以下を満たすと正規化された unicode_local_id と言える

  • anguage subtagで始まる(script subtagで始まるものは特殊な用途のみ)
  • 大文字小文字
    • unicode_language_id内のscript subtagはタイトルケース(例:Hant)
    • unicode_language_id内のregion subtagは大文字(例:DE)
    • その他のすべてのsubtagは小文字(例:en、fonipa)
  • 順序
    • すべてのvariantはアルファベット順(例:en-fonipa-scouseであり、en-scouse-fonipaではない)
    • すべての拡張はそのsingletonによってアルファベット順(例:en-t-xxx-u-yyyであり、en-u-yyy-t-xxxではない)
    • すべてのattributeはアルファベット順にソートされる。
    • すべてのufieldおよびtfieldは、それぞれの拡張内で、キーのアルファベット順にソートされる。
    • ufieldまたはtfieldの値"true"は削除される。

注意: これは BCP 47の Section 4.1 の順序ルールと異なる(variantにアルファベット順を要求しているため)

  • 理由に関しては原文読んでという感じ
  • ざっくり → BCP47でも順番は絶対じゃない + 利便性の観点

maximal form

以下の操作を行なったものを maximal form と呼ぶ

  • unicode_language_idおよび tlang(存在する場合)が、Likely SubtagsのAdd Likely Subtags操作によって変換
  • "und"を除外した場合

例 : ja-Kana-t-it → ja-Kana-JP-t-it-latn-it

  • t拡張のlatnは大文字じゃないことに注意

等価性

2つの unicode_local_id が等価であるとはmaximal form に変換した結果が同じであることを意味する

ただしsubtagが廃止になったりすると等価性が壊れる可能性がある(レアだけど)

SajiSaji

BCP47準拠

Unicode language and locale identifierは、BCP47 Language Tagsから設計とsubtagのレパートリーを継承してるが、 CLDRでのUnicode locale identifierの使用に関して、いくつかの拡張と制限がある。

  • 詳しくは本文
  • ざっくり
    • 完全に互換じゃなくてサポートされてない構文もある
    • 特定のコードにCLDRだけの意味をつけたりしてる
    • 下位互換性のためにBCP47準拠じゃない特定の構文を許可してる

つまり Unicode Locale Identifiers には2つのサブセットがある

  • Unicode BCP 47 locale identifier
  • Unicode CLDR locale identifier

これらは両方とも BCP 47 language tag との相互変換ができる

BCP 47 Language Tag Conversion

BCP 47 language tag → Unicode BCP 47 locale identifier

  • BCP 47 language tagはAnnex C. LocaleId Canonicalizationに従って、有効なUnicode BCP 47 locale identifierに変換できる
  • 結果は、canonical formのUnicode BCP 47 locale identifier
  • 変換は不可逆なこともある
  • 例 : iw-FX → he-FR

Unicode CLDR locale identifier → BCP 47 language tag (= Unicode BCP 47 locale identifier)

  • _-に変換
  • rootundに変換
  • 最初のsubtagがscript subtagの場合は、und-を先頭に追加
  • 例 : Latn_DE → und-Latn-DE

Unicode BCP 47 locale identifier → Unicode CLDR locale identifier

  • 区切り文字を-から_に変換
  • script,region,variant subtagがない場合はundrootに変換
  • 例 : und-u-cu-USD → root_u_cu_USD
切り捨てについて

BCP47のsection 4.1.1では、言語タグの長さとして少なくとも35文字のlanguage tagを許可することを要求ているが、拡張の使用を可能にするため、CLDRはUnicode locale identifierに対してその最小値を255に拡張している。

ただし、実際にはかなり短くできる。

SajiSaji

Language Identifier Field Definitions

Unicode language and locale identifiers で使用される各フィールドの定義

注意

  • 一部のprivate-use BCP 47フィールド値には、CLDRで特定の意味が与えられている
  • フィールド値はBCP47 subtag値に基づいている

unicode_language_subtag

別名 : Unicode base language code

基本

  • language.xmlファイル内のsubtagで定義されている
  • BCP47 subtag値で Type: languageとマークされたもの
ISO 639-3との関係

ISO 639-3ではより狭いセマンティクスには追加のコードが与えられる(広東語 → yue など)が、下位互換性のため、 Unicode language identifierは、これらのコードに対してより狭いセマンティクスの使用を保持する

  • cmnzh
  • yuezh

language subtagがCLDRのlanguageAlias要素のtype属性と一致する場合、代わりに置換値が使用される

  • 例 : <languageAlias type="swh" replacement="sw" /> があるので、 swhsw に置き換えられる
private use

Private Use Codeで除外としてリストされている私的使用コードには、Unicode 識別子で特定のセマンティクスが与えられることはないので安心して使って良い

長すぎるコードのマッピング

"eng-840"や"eng-USA"のような長すぎるコードを正しいコード"en-US"にマッピングする。そのためのlanguage/localeコードを正規化するためのデータを提供する。

特別なlaunguage subtags
  • mis : まだISO 639コードを持たない言語
  • mul : コンテンツは複数の言語を含んでいるか、複数の言語が同時に使用されている
  • zxx : コンテンツに言語情報がない(例:画像、記号など)。

unicode_script_subtag

別名 : Unicode script code

基本

  • script.xmlファイル内のsubtagで定義されている
  • BCP47 subtag値で Type: scriptとマークされたもの

特定のUnicode Script値に特定のセマンティクスを与える。(詳しくはUAX #24)

  • Zsye → テキストスタイルと絵文字スタイルの両方が利用可能な文字に対して、絵文字スタイルを優先
  • Zsym → テキストスタイルと絵文字スタイルの両方が利用可能な文字に対して、テキストスタイルを優先

unicode_region_subtag

別名 : Unicode region code, or a Unicode territory code

基本

  • region.xmlファイル内のsubtagで定義されている
  • BCP47 subtag値で Type: regionとマークされたもの

特定の subtag値に特定のセマンティクスを与える
→ 詳しくは原文

特別なregion subtags
  • UK : ISOで特別な地位を持ち、GBの代わりにドメイン名に使用されます。したがって、CLDRでは'GB'の代替(非正規化)形式として認識される
  • 001 : 世界全体を表す。現代標準アラビア語の"ar-001"など、標準化された形式を示すために使用される

unicode_variant_subtag

別名 : Unicode language variant code

基本

  • variant.xmlファイル内のsubtagで定義されている
  • BCP47 subtag値で Type: variantとマークされたもの
  • variant tagのシーケンスは重複してはいけない

その他・一般名称について

  • language subtagのみ(およびオプションでscript subtag)を持つlocale : 「language locale」と呼ばれる
  • languageとterritory subtagの両方を持つもの : 「territory locale」(またはcountry locale)と呼ばれる
SajiSaji

Special Codes

Unicode language and locale identifierで使用される特別なコードたち

Unknown or Invalid Identifiers

未知または無効なコードを示すために使用される特別なコード

これらのコードがUnicode identifierに関連するAPIで使用される場合、以下を意味する

  • 識別子が利用できなかったか、
  • ある時点で入力識別子値が無効または不正な形式であると判定された
Code Type Value Description in Referenced Standards
Language und Undetermined language, also used for “root”
Script Zzzz Code for uncoded script, Unknown [UAX24]
Region ZZ Unknown or Invalid Territory
Currency XXX The codes assigned for transactions where no currency is involved
Time Zone unk Unknown or Invalid Time Zone
Subdivision <region>zzzz Unknown or Invalid Subdivision

補足 : scriptまたはregionのみが既知の場合、locale IDはlanguage subtag部分として"und"を使用

Numeric Codes

ISOとUN はregionについて3文字コード↔︎数値コードのマッピングを定めている。ただじprivate-useのものはこのマッピングの対象外なので、追加で以下のマッピングを提供する。

Region UN/ISO Numeric ISO 3-Letter
AA 958 AAA
QM..QZ 959..972 QMM..QZZ
XA..XZ 973..998 XAA..XZZ
ZZ 999 ZZZ

また ISO 15924 では script についても数値コードとのマッピングを定めているが、あまり使われない

Private Use Codes

  • Private useコードは3つのグループに分類
    • defined : CLDRで特定のセマンティクスが与えられている
    • reserved : 将来の使用のために予約されている
    • excluded : 将来、特定のCLDRセマンティクスが与えられることは決してなくアプリケーションサイドで自由に使って良い
      • ただし、CLDRはXA、XB、XKなど、これらのコードの一部の使用において広く普及している業界慣行に従う場合がある

詳しくはを参照

SajiSaji

Special Script Codes

有効なScriptコードのうち、特別な処理が必要なもの

さらに分類があり

  • Compound : 複数のScriptを含む
  • Visual : 外見が異なるがそれ以外は単一のScriptを内包
  • Subset : 同等物は明確に定義されていないもの

  • Jpan : Compound / Hira + Kana + Hani
  • Hans : Subset / Hani のうち伝統的な(旧字)のみ
  • Hant : Subset / Hani のうち簡略化されたもののみ

Subsetバリアント

Subsetバリアントはあくまで近似でしか定義できず、同時Visualなものでもある(指してる文字は一緒)ので、例えばHansは

  • 繁体字のみの文字を使用しない
  • 簡体字と繁体字の両方で使用される文字については簡体字の形状を使用する

などが要求されている。

Visualバリアント

locale identifierで特定のレンダリングを要求するために使用できる

  • 例 : ar_Aran : ar_Arabデータを使用するが、Nastaliqスタイルのフォントを使用したい

ただしサポートされない場合もあるので注意

その他

特別なコードの一部は、 Mixed_Script_Detectionなどの他の仕様で使用される