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

Enhance your app’s multilingual experience

Watch original video

Highlight

iOS 26 replaces Locale.preferredLanguages with Locale.preferredLocales, and gives UITextView a selectedRanges array so Natural Selection works for bidirectional text.


Core content

Billions of people switch between two or more languages every day. Omar’s own iPhone runs in English, but he listens to podcasts, reads news, and texts in Arabic. Before iOS 26, serving both languages on one device meant going into Settings and adding a language and a keyboard by hand. Most users never do this, so app recommendations are always wrong.

iOS 26 hands the job to Siri: on-device intelligence spots multilingual habits and offers to add a keyboard, switch the system language, or change content recommendations. To match this new capability, Foundation replaces the long-serving Locale.preferredLanguages (which returned an array of BCP-47 strings) with Locale.preferredLocales (an array of Locale objects). Apps now get region-aware identifiers like “Arabic-Lebanon” and can pick the right spelling, date format, and currency. Foundation also adds 11 new calendar identifiers, including Gujarati, Marathi, and Korean. UITextView upgrades its bidirectional selection from a single contiguous range to multiple non-contiguous ranges, fixing the old bug where dragging across mixed Arabic and English left a gap in the middle of the selection.


Details

Language Discovery: from String to Locale. The old API returned a string array like ["en", "ar"], and the developer had to parse BCP-47. The new API returns Locale objects with language, region, numberingSystem, and other properties, and localizedString(forIdentifier:) gives you a localized name. Apple has already moved Translate, Calendar, and Apple Music to preferredLocales. Translate now pins the user’s common languages to the top of the picker instead of burying them in a long list, and Apple Music uses it to suggest lyric translations.

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

Key points:

  • Locale.preferredLanguages: the old API, returns [String], may be deprecated in the future.
  • Locale.preferredLocales: new in iOS 26, returns [Locale] with both language and region.
  • The string form is still available, and you can request BCP-47, ICU, or CLDR.

Matching the user’s preferred languages against the languages your app supports is the heart of Translate’s pin-to-top logic (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
        }
    }
}

Key points:

  • The outer loop walks the locales the app supports, the inner loop walks the user’s preferred locales, and a match breaks out.
  • Language.isEquivalent(to:) tests language equivalence. For looser matching (treating zh-Hans and zh-Hant as related), use hasCommonParent.
  • The matched locales go to the top of the list; the rest stay in alphabetical order.

Alternate Calendars: identifiers grow from 16 to 27. The 11 new identifiers include Gujarati, Marathi, and Korean. All are reachable through Calendar.Identifier in Foundation, and they work on every platform.

Natural Selection: bidirectional selection becomes a range array. In iOS 18, UITextView.selectedRange is a single NSRange. When English and Arabic are mixed, the text is stored in logical order but rendered in visual order, and the two do not match. Dragging from left to right leaves a visual gap (12:05) because a single NSRange cannot cover several runs that switch direction on screen. iOS 26 lets the selection follow the caret naturally and stores what looks like a discontinuous selection as multiple non-contiguous NSRanges (12:53). NSTextView on macOS has worked this way for years; iOS finally catches up.

If your old code used selectedRange to delete characters, on iOS 26 it will delete the wrong text. You must switch to selectedRanges and delete in reverse order (14:57):

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

Key points:

  • selectedRanges is [NSRange] and can hold several non-contiguous runs. The old selectedRange will be deprecated in a future release.
  • .reversed() deletes from the end first, so earlier range indices are not shifted by earlier deletions.
  • UITextViewDelegate is updated in step: shouldChangeTextInRanges, editMenuForTextInRanges, and friends now take [NSRange].

TextKit2 is required. Natural Selection is on by default, but touching textView.layoutManager falls the text engine back to TextKit1 and silently turns the feature off. To keep the new behavior, use textView.textLayoutManager (17:09). The SwiftUI Rich Text Editor also supports Natural Selection, with selections typed as RangeSet<AttributedString.Index>. See “Cook up a rich text experience in SwiftUI with AttributedString” for details.

Writing direction is now dynamic. A paragraph used to take its writing direction from its first run of text, so a long block of Urdu in an English-led paragraph still laid out left-to-right. iOS 26 picks the direction from the content as it changes: as full Urdu sentences appear inside an English paragraph, the system switches to RTL (18:47). Apple’s TextView and TextField get this for free. For custom text engines, follow the Language Introspector sample code to implement it yourself.


Takeaways

1. Turn the language picker into a “common languages on top” picker.

  • Why it matters: a long language list is a sore spot for international apps, and multilingual users have to scroll every time. Pinning common ones to the top cuts the cost of each interaction.
  • How to start: read Locale.preferredLocales, walk the locales the app supports, match with isEquivalent, and put the hits at the top of the list. Use Translate as a reference.

2. Audit every call site of selectedRange and migrate to selectedRanges.

  • Why it matters: bidirectional selection on iOS 26 is already non-contiguous, and old code will delete or replace the wrong text for Arabic and Hebrew users.
  • How to start: search for textView.selectedRange, shouldChangeTextIn, and editMenuForText, swap each one for the plural form, and remember to call .reversed() when deleting.

3. Check whether your text views have been pushed back to TextKit1.

  • Why it matters: touching textView.layoutManager silently disables Natural Selection and other new features. There is no error; the symptom is just “wrong behavior”.
  • How to start: grep for .layoutManager, decide whether each call can move to .textLayoutManager, and mark the ones that must stay on TextKit1 with a // TextKit1 comment so you can come back later.

4. Add the 11 new calendar identifiers to any feature that exposes a calendar choice.

  • Why it matters: Gujarati, Marathi, Korean, and the rest cover real needs in India and Korea.
  • How to start: list every Calendar(identifier:) call site, decide which ones expose the choice to users, and follow iOS 26 Calendar’s “show day and month names in the local language” approach in the UI.

5. Adopt the new writing direction API in your custom text engine.

  • Why it matters: from iOS 26 on, writing direction is decided by content. A custom engine that does not adopt this will look stiff.
  • How to start: download the Language Introspector sample code and use its API to recompute paragraph direction on every text change.

Comments

GitHub Issues · utterances