WWDC Quick Look 💓 By SwiftGGTeam
Enhance your app’s multilingual experience

Enhance your app’s multilingual experience

元の動画を見る

Highlight

iOS 26 では Locale.preferredLanguages に代わって Locale.preferredLocales が導入されました。さらに UITextView には selectedRanges 配列が追加され、双方向テキストの Natural Selection に対応しています。


本セッションの要点

世界中で数十億人もの人々が、日々 2 つ以上の言語を切り替えながら生活しています。発表者の Omar 自身も iPhone のシステム言語は英語ですが、ポッドキャストを聴くときやニュースを読むとき、メッセージを送るときはアラビア語を使うそうです。iOS 26 以前は、デバイスに 2 つの言語を同時にサポートさせたい場合、「設定」アプリから手動で言語やキーボードを追加するしかありませんでした。しかし、ほとんどのユーザーはそんなことをしないので、結果的にアプリ側のコンテンツ推薦は常に的外れなものになっていました。

iOS 26 ではこの仕事を Siri に任せられるようになりました。オンデバイスのインテリジェンスがマルチリンガル利用パターンを認識し、キーボードの追加、システム言語の切り替え、コンテンツ推薦の差し替えを能動的に提案してくれます。この基盤の上で、Foundation はこれまで開発者が長年使ってきた Locale.preferredLanguages(BCP-47 文字列の配列を返す)を、Locale.preferredLocalesLocale オブジェクトの配列を返す)に置き換えました。これによりアプリは「アラビア語 - レバノン」のように地域情報を含んだ識別子を直接受け取れるようになり、スペル、日付フォーマット、通貨を正しく選択できます。あわせて Foundation には Gujarati、Marathi、Korean を含む 11 個のカレンダー識別子が新たに追加され、UITextView の双方向テキスト選択範囲も「単一の連続範囲」から「複数の不連続範囲」へとアップグレードされました。これによってアラビア語と英語が混在したテキストをドラッグ選択する際に、選択範囲の途中が抜け落ちてしまうという長年の不具合が解消されています。


詳細

Language Discovery: String から Locale へ。 旧 API が返すのは ["en", "ar"] のような文字列配列で、開発者は自分で BCP-47 をパースする必要がありました。新 API は Locale オブジェクトを直接返し、languageregionnumberingSystem などのプロパティを備えています。localizedString(forIdentifier:) でローカライズ済みの名称を取得することも可能です。Apple は Translate、Calendar、Apple Music の 3 つの内蔵アプリをすでに preferredLocales ベースに切り替えています。Translate ではよく使う言語をリストの先頭に固定して、長いリストをスクロールする必要をなくしています。Apple Music では歌詞翻訳の推薦ロジックに利用しています。

// Language discovery
let preferredLanguages = Locale.preferredLanguages
let preferredLocales = Locale.preferredLocales

ポイント:

  • Locale.preferredLanguages: 旧 API。[String] を返します。将来的に非推奨になる可能性があります。
  • Locale.preferredLocales: iOS 26 で新規追加。[Locale] を返し、言語と地域の両方を含みます。
  • 文字列形式での取得も依然として可能で、BCP-47 / ICU / CLDR を指定できます。

ユーザーの優先言語とアプリがサポートしている言語をマッチングする処理は、Translate のトップ固定ロジックの中核です(07:49)。

let preferredLocales = Locale.preferredLocales

// array of available Locale objects to translate from
let availableLocales = getAvailableLocalesForTranslatingFrom()

var matchedLocales: [Locale] = []

for locale in availableLocales {
    for preferredLocale in preferredLocales {
        if locale.language.isEquivalent(to:         preferredLocale.language) {
            matchedLocales.append(locale)
            break
        }
    }
}

ポイント:

  • 外側のループでアプリ対応 locale を、内側のループでユーザー優先 locale を回し、ヒットしたら break します。
  • Language.isEquivalent(to:) で言語の等価性を判定します。zh-Hans と zh-Hant を同源と見なすようなより緩い判定が必要なら hasCommonParent を使います。
  • ヒットした matchedLocales をそのままトップに固定し、残りの locale はアルファベット順で表示します。

Alternate Calendars: カレンダー識別子が 16 個から 27 個に。 11 個が新規追加され、Gujarati、Marathi、Korean などが含まれます。すべて Foundation の Calendar.Identifier 経由でアクセスでき、全プラットフォームで利用可能です。

Natural Selection: 双方向テキストの選択範囲が単一 range から range 配列に。 iOS 18 では UITextView.selectedRange は単一の NSRange でした。英語とアラビア語が混在するテキストでは、ストレージ上は論理順、画面上は視覚順となり両者は一致しません。左から右へドラッグして選択すると「視覚的なすき間」ができてしまいます(12:05)。これは単一の NSRange では、画面上で方向をまたぐ複数のテキスト断片を表現できないからです。iOS 26 では選択範囲がカーソルに追従して自然に移動するようになり、視覚的に不連続に見える選択範囲を、ストレージ上では複数の不連続な NSRange に分割して表現します(12:53)。macOS の NSTextView ではずっと前からこの設計でしたが、今回ようやく iOS が追いついた形です。

これまで selectedRange で文字を削除する実装をしていた場合、iOS 26 では誤削除が起きるため、selectedRanges を逆順に処理する必要があります(14:57)。

let ranges = textView.selectedRanges.reversed()
for range in ranges {
    textView.textStorage.deleteCharacters(in: range)
}

ポイント:

  • selectedRanges[NSRange] 型で、複数の不連続な区間を含み得ます。旧来の selectedRange は将来のバージョンで非推奨化される予定です。
  • .reversed() による逆順削除: 後ろから削除することで、前方にある range のインデックスが、より前方の削除操作によってずれてしまうのを防ぎます。
  • UITextViewDelegate も同期的にアップデートされており、shouldChangeTextInRangeseditMenuForTextInRanges などのコールバックがいずれも [NSRange] を受け取るようになっています。

TextKit2 が前提。 Natural Selection はデフォルトで有効ですが、textView.layoutManager にアクセスするとテキストエンジンが TextKit1 にフォールバックしてしまい、この機能を自動的に失います。新機能を維持するには textView.textLayoutManager を使用してください(17:09)。SwiftUI の Rich Text Editor も Natural Selection に対応しており、選択範囲の型は RangeSet<AttributedString.Index> です。詳しくは「Cook up a rich text experience in SwiftUI with AttributedString」を参照してください。

Writing Direction の動的判定。 従来、段落の書字方向は最初に入力した文字の方向で決まっていたため、Urdu で長文を書いても段落全体は LTR のままレイアウトされていました。iOS 26 ではコンテンツに応じて動的に調整されるようになり、英語の段落の中に Urdu の文章が増えてくると、システムが RTL に切り替えます(18:47)。Apple 製の TextView / TextField では自動的に有効になります。独自のテキストエンジンを実装している場合は、Language Introspector のサンプルコードを参照して自前で実装してください。


学びと活かしどころ

1. 言語セレクタを「よく使う言語をトップ固定」に作り変える。

  • なぜやる価値があるか: 長い言語リストは国際化アプリの痛点で、マルチリンガルユーザーは毎回スクロールして探す必要があります。トップ固定によってインタラクションコストを大幅に下げられます。
  • 始め方: Locale.preferredLocales でユーザーの優先言語を取得し、アプリがサポートする locale をループして isEquivalent でマッチングし、ヒットしたものをリストの先頭に置きます。Translate の実装が参考になります。

2. selectedRange の全呼び出し箇所を監査し、selectedRanges へ移行する。

  • なぜやる価値があるか: iOS 26 の双方向テキスト選択範囲はすでに不連続な区間になっています。旧来のコードのままだとアラビア語やヘブライ語のユーザーで誤削除や誤置換が発生します。
  • 始め方: textView.selectedRangeshouldChangeTextIneditMenuForText を検索し、ひとつずつ複数形の ranges に置き換えていきます。削除する際は .reversed() を忘れないようにします。

3. テキストビューが TextKit1 にフォールバックしていないかをチェックする。

  • なぜやる価値があるか: textView.layoutManager にアクセスすると、Natural Selection などの新機能が黙って無効化されます。エラーは出ず「挙動がおかしい」という形でしか表面化しません。
  • 始め方: グローバル検索で .layoutManager を探し、それぞれを .textLayoutManager に置き換えられるかを確認します。TextKit1 のままにする旧ロジックには // TextKit1 のコメントを残しておくと、後でクリーンアップしやすくなります。

4. 新規追加された 11 個の calendar identifier をカレンダー関連機能の選択肢に組み込む。

  • なぜやる価値があるか: Gujarati、Marathi、Korean などのカレンダーは、インドや韓国市場の実需要をカバーします。
  • 始め方: アプリ内の Calendar(identifier:) 呼び出し箇所を洗い出し、ユーザーへ選択肢として公開できるか確認します。UI は iOS 26 の Calendar アプリの「現地の言語で日と月を表示する」戦略を参考にします。

5. 独自テキストエンジンに新しい writing direction API を組み込む。

  • なぜやる価値があるか: iOS 26 から書字方向はコンテンツに応じて動的に判定されます。自前のエンジンが追従しないと不自然に映ります。
  • 始め方: Language Introspector のサンプルコードをダウンロードし、その中の API に従ってテキストが変化するたびに段落の方向を再計算します。

関連セッション

コメント

GitHub Issues · utterances