How to Get the Current Device Language in Swift

How to Get the Current Device Language in Swift

Written by

in

Quick answer: Use Locale.preferredLanguages.first to get the user’s top preferred system language code (e.g., ‘en-US’ or ‘ja’). If you need the language currently active inside your app’s bundle, read Bundle.main.preferredLocalizations.first instead.

iOS separates the user’s preferred operating system language, their regional formatting settings, and the actual localization loaded by your app’s main bundle. Fetching the wrong property leads to subtle bugs, such as displaying the wrong language or mistaking region formats (like currency and date styles) for the UI language.

Here is how to inspect system preferences, determine the active app localization, and format language codes into human-readable strings.

Why this happens

Developers often query Locale.current.identifier or Locale.current.languageCode expecting it to return the language the user sees on their screen. However, Locale.current reflects the device’s Region configuration (Settings > General > Language & Region > Region), not necessarily their primary system language.

For example, if an expat living in the United States sets their iPhone system language to Japanese while keeping the United States as their region:

  • Locale.current.identifier returns en_US.
  • Locale.current.region?.identifier returns US.
  • The system interface renders in Japanese (ja).

Additionally, there is a distinct difference between the device system language and the app runtime language:

  1. System Preferred Languages (Locale.preferredLanguages): The ordered priority list of languages configured by the user in iOS Settings. The first item represents their primary device interface language.
  2. Bundle Preferred Localizations (Bundle.main.preferredLocalizations): The language your app actually displays. If a user prefers German (de), but your app only ships with English (en) and Spanish (es) localization files, iOS falls back to one of your supported localizations. Querying system settings in this scenario would claim the user is viewing German when your UI is actually displaying English.

How to fix it

Depending on your use case, choose the approach below that matches your requirements.

1. Get the primary device language

If you want to send analytics to your server or determine what language the user has chosen for iOS overall, query Locale.preferredLanguages. This returns an array of BCP 47 language identifiers sorted by user preference.

import Foundation

func getDeviceLanguage() -> String {
    // Returns the top language code, e.g., "en-US", "fr-CA", or "ja-JP"
    guard let preferredLanguage = Locale.preferredLanguages.first else {
        return "en"
    }
    return preferredLanguage
}

let systemLanguage = getDeviceLanguage()
print("Device language identifier: \(systemLanguage)")

If you only need the base ISO 639-1 language subtag (e.g., "en" instead of "en-US"), extract the base language identifier using Locale:

import Foundation

func getBaseDeviceLanguageCode() -> String {
    guard let preferred = Locale.preferredLanguages.first else {
        return "en"
    }

    let locale = Locale(identifier: preferred)
    if #available(iOS 16, *) {
        return locale.language.languageCode?.identifier ?? "en"
    } else {
        return locale.languageCode ?? "en"
    }
}

let baseCode = getBaseDeviceLanguageCode()
print("Base language code: \(baseCode)") // e.g. "en", "ja", "fr"

2. Get the active app localization

If you are dynamically loading assets, fetching localized API responses, or confirming what strings your app bundle is currently displaying, inspect Bundle.main.preferredLocalizations.

preferredLocalizations matches the user’s Locale.preferredLanguages against the .lproj folders bundled inside your app binary and returns the matching order.

import Foundation

func getAppLanguage() -> String {
    // Returns the actual language your app is rendering
    guard let appLanguage = Bundle.main.preferredLocalizations.first else {
        return "en"
    }
    return appLanguage
}

let activeLocalization = getAppLanguage()
print("App is running in: \(activeLocalization)") // e.g., "en" or "de"

If the user’s top system language is fr-FR, but your app only provides Base.lproj and en.lproj, Bundle.main.preferredLocalizations.first returns "en", avoiding mismatches between server data and local UI text.


3. Convert language codes to human-readable names

If you need to show the language name in your UI (for example, in an in-app language picker or account settings screen), avoid hardcoding lookup tables. Instead, use Locale.current.localizedString(forIdentifier:) or localizedString(forLanguageCode:).

import Foundation

func getDisplayName(for identifier: String, displayInTargetLanguage: Bool = false) -> String? {
    // Use the target language's own locale for native names (e.g., "Deutsch" for "de")
    // Or use the current system locale to display in the user's active language (e.g., "German")
    let formattingLocale = displayInTargetLanguage ? Locale(identifier: identifier) : Locale.current

    return formattingLocale.localizedString(forIdentifier: identifier)?.capitalized
}

// Example: User running in English
let englishDisplay = getDisplayName(for: "es", displayInTargetLanguage: false)
print(englishDisplay ?? "Unknown") // Output: "Spanish"

let nativeDisplay = getDisplayName(for: "es", displayInTargetLanguage: true)
print(nativeDisplay ?? "Unknown") // Output: "Español"

How to prevent it

  • Never rely on Locale.current.identifier for UI text decisions: Keep region formatting (dates, currencies, units) separate from language detection.
  • Check Bundle.main when making localized API requests: If you pass the user’s device language directly to your API, the API might return German while the app’s local labels remain in English.
  • Account for dialect codes: Modern iOS returns full dialect codes like zh-Hans-CN or pt-BR. Ensure your parsing logic handles hyphens and scripts rather than assuming all codes are strictly two characters.
  • Support Per-App Language settings: On iOS 13 and later, users can change an individual app’s language in iOS Settings > [Your App] > Preferred Language. Using Bundle.main.preferredLocalizations.first automatically honors this setting without custom overrides.

FAQ

What is the difference between Locale.current and Locale.preferredLanguages?

Locale.current reflects the active region configuration (such as measurement units, calendar systems, and numeric formatting). Locale.preferredLanguages lists the languages the user has added to their preferred order under iOS Settings > General > Language & Region.

Why does Locale.preferredLanguages return a region suffix like ‘en-US’?

Starting in iOS 9, Apple began returning region-qualified BCP 47 language identifiers (such as en-GB or fr-CA) to allow applications to distinguish regional spelling and vocabulary differences. You can extract the pure language prefix using Locale(identifier: code).languageCode.

How do I support per-app language selection in iOS?

Apple provides native per-app language settings automatically as long as your project includes multiple localized .strings or .xcstrings catalogs. When a user changes the language for your app in the system Settings app, Bundle.main.preferredLocalizations will automatically update upon app launch.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.