01 何が問題だったのか
Foundation の Locale.Region には、リージョンに関する情報を取得するための API がいくつか用意されていました。たとえば ISO に定義されたリージョン一覧を返す Locale.Region.isoRegions、自身を含むリージョンを返す containingRegion、自身を含む大陸を返す continent、自身に含まれるサブリージョンの一覧を返す subRegions などです。
extension Locale.Region {
public static var isoRegions: [Locale.Region]
public var containingRegion: Locale.Region? { get }
public var continent: Locale.Region? { get }
public var subRegions: [Locale.Region] { get }
}
しかし、これらの API はリージョンを「種類」で区別する手段を提供していませんでした。Unicode の territory containment(Unicode LDML #35)では、リージョンは「世界全体」「大陸」「サブ大陸」「テリトリー」「グルーピング」といったレベルに分類されますが、これらの種類で絞り込んだり、自身がどの種類に属するかを問い合わせたりすることはできません。
そのため、たとえば「Language & Region」のような設定 UI で、サポートしているリージョンを大陸ごとにグループ化した階層リストとして表示しようとすると、Locale.Region.isoRegions で得られるフラットな一覧から自前で分類を組み立てる必要があり、利用側に余計な負担がかかっていました。
加えて、Locale.Region.isoRegions が返す一覧には EU や Eurozone のような「グルーピング」に該当するリージョンが含まれていませんでした。これらは大陸を頂点とするツリー構造には収まらないため、種類による絞り込み手段がない状況では一覧に含めることができなかったためです。
02 どのように解決されるのか
Locale.Region に、リージョンの種類を表す Locale.Region.Category 型と、その種類で絞り込むための API が追加されます。あわせて、サブ大陸を取得する subcontinent プロパティも追加されます。
Locale.Region.Category
Category は Unicode の territory containment における分類レベルに対応する型で、world / continent / subcontinent / territory / grouping の 5 種類が static プロパティとして用意されます。
@available(FoundationPreview 6.2, *)
extension Locale.Region {
public struct Category: Codable, Sendable, Hashable, CustomDebugStringConvertible {
public static let world: Category
public static let continent: Category
public static let subcontinent: Category
public static let territory: Category
public static let grouping: Category
}
}
world は Locale.Region("001") のみに対応する最上位のカテゴリです。territory は国だけでなく Antarctica(コード "AQ")のような国に該当しないテリトリーも含みます。grouping は EU(コード "EU")や Eurozone(コード "EZ")のように、メンバーシップが明確に定義されたリージョンを表し、他のカテゴリで作られるツリー階層には属しません。
カテゴリでリージョンを絞り込む
種類で絞り込むための API として、Locale.Region.isoRegions(ofCategory:) と、インスタンスメソッドの subRegions(ofCategory:) が追加されます。また、自身のカテゴリを返す category プロパティと、自身を含むサブ大陸を返す subcontinent プロパティも追加されます。
@available(FoundationPreview 6.2, *)
extension Locale.Region {
public static func isoRegions(ofCategory category: Category) -> [Locale.Region]
public var category: Category? { get }
public func subRegions(ofCategory category: Category) -> [Locale.Region]
public var subcontinent: Locale.Region? { get }
}
これらを組み合わせることで、すべての大陸を列挙したり、ある大陸に属するサブ大陸やテリトリーだけを取り出したりできます。
let argentina = Locale.Region.argentina
_ = argentina.category // .territory
_ = argentina.subcontinent // "005" (South America)
let americas = Locale.Region("019")
_ = americas.category // .continent
_ = americas.subRegions(ofCategory: .subcontinent) // ["005", "013", "021", "029"]
_ = americas.subRegions(ofCategory: .territory) // Americas に属するすべてのテリトリー
_ = Locale.Region.isoRegions(ofCategory: .continent)
// ["002", "009", "019", "142", "150"](Africa, Oceania, Americas, Asia, Europe)
subRegions(ofCategory:) に自身より上位のカテゴリを渡した場合は、空の配列が返ります。一方で、自身の 1 つ下のレベルだけでなく、さらに下のレベルのカテゴリを渡すこともできます。たとえば大陸に対して .territory を渡せば、その大陸に属するすべてのテリトリーをまとめて取得できます。
argentina.subRegions(ofCategory: .world) // []
これにより、Locale.Region.isoRegions のフラットな一覧を自前で分類することなく、設定 UI などで大陸 → サブ大陸 → テリトリーといった階層的なリストを直接組み立てられます。
Locale.Region.isoRegions の挙動の変更
Locale.Region.isoRegions の返り値に、これまで含まれていなかった grouping カテゴリのリージョン(EU や Eurozone など)も含まれるようになります。grouping を除いた一覧が必要な場合は、Locale.Region.isoRegions(ofCategory:) を使って .continent や .territory などを指定すれば取得できます。
ISO リージョンの一覧はもともと固定されたものとしては保証されておらず、ロケール関連の他の API と同様に時期によって変わり得るため、この変更によって既存コードのリージョン一覧の中身が変化することがある点には注意が必要です。