For most code, Locale.getDefault() is the answer. Android sets the process default locale when the app starts and again on every configuration change, from the first locale of the app's resource configuration, so it reflects a per-app language on Android 13+ as well as one applied by AppCompat on older versions.
import java.util.Locale
val locale = Locale.getDefault()
locale.language // "pt"
locale.toLanguageTag() // "pt-BR", a BCP 47 tag
locale.displayName // the locale's name in the current language
The locale your resources were resolved for sits in the configuration. Configuration.locales holds the user's preferred languages, and the system moves the best match among your app's translations to the front, so index 0 is the language your strings come from:
val resourceLocale = resources.configuration.locales[0] // API 24+
// Below API 24: ConfigurationCompat.getLocales(resources.configuration)[0]
LocaleList.getDefault() returns the full preference list in order; Locale.getDefault() is guaranteed to be in it but not necessarily first, which is how you can tell that the user's first choice is a language you do not ship.
Whether the user picked a language for your app specifically:
import androidx.appcompat.app.AppCompatDelegate
val appLocales = AppCompatDelegate.getApplicationLocales()
val appLanguage = appLocales[0]?.toLanguageTag()
// null: no per-app language, the app follows the system
The device language, as opposed to the app language, when a per-app language is active:
import android.app.LocaleManager
import android.content.res.Resources
import android.os.Build
val systemLocale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.getSystemService(LocaleManager::class.java).systemLocales[0]
} else {
Resources.getSystem().configuration.locales[0]
}
LocaleManager.getSystemLocales() returns the system locales ignoring app-specific overrides. Below Android 13 the override exists only inside AppCompat, which applies it to AppCompatActivity contexts, so the system Resources object still carries the device configuration.
Pitfalls:
resources through an AppCompatActivity context. The application context does not see the AppCompat per-app locale.toLanguageTag() (zh-Hant-TW) rather than toString() (zh_TW_#Hant) when sending the language to a server or comparing it with a list of supported tags.Locale.getDefault() can be changed by any code in the process through Locale.setDefault(); the configuration value cannot.References: LocaleManager, Locale and Per-app language preferences.
Published Oct 9, 2026 · Markdown version