Use platform LocaleManager if possible

This commit is contained in:
topjohnwu 2024-07-10 21:50:54 -07:00
parent 6b81716440
commit 7173693d1b
15 changed files with 294 additions and 162 deletions

View File

@ -2,7 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<application android:localeConfig="@xml/locale_config">
<activity
android:name=".ui.MainActivity"
android:exported="true"

View File

@ -16,7 +16,7 @@ import android.os.Build
import android.os.Build.VERSION.SDK_INT
import androidx.core.os.ProcessCompat
import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.utils.currentLocale
import java.util.Locale
import java.util.TreeSet
class CmdlineListItem(line: String) {
@ -102,7 +102,7 @@ class AppProcessInfo(
companion object {
private val comparator = compareBy<AppProcessInfo>(
{ it.label.lowercase(currentLocale) },
{ it.label.lowercase(Locale.ROOT) },
{ it.info.packageName }
)
}

View File

@ -14,18 +14,16 @@ import com.topjohnwu.magisk.core.Const
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.ktx.activity
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.MediaStoreUtils
import com.topjohnwu.magisk.core.utils.availableLocales
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.DialogSettingsAppNameBinding
import com.topjohnwu.magisk.databinding.DialogSettingsDownloadPathBinding
import com.topjohnwu.magisk.databinding.DialogSettingsUpdateChannelBinding
import com.topjohnwu.magisk.databinding.set
import com.topjohnwu.magisk.utils.TextHolder
import com.topjohnwu.magisk.utils.asText
import com.topjohnwu.magisk.view.MagiskDialog
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import com.topjohnwu.magisk.core.R as CoreR
// --- Customization
@ -35,38 +33,28 @@ object Customization : BaseSettingsItem.Section() {
}
object Language : BaseSettingsItem.Selector() {
private val names: Array<String> get() = LocaleSetting.available.names
private val tags: Array<String> get() = LocaleSetting.available.tags
override var value
get() = index
get() = tags.indexOf(Config.locale)
set(value) {
index = value
Config.locale = entryValues[value]
Config.locale = tags[value]
}
override val title = CoreR.string.language.asText()
private var entries = emptyArray<String>()
private var entryValues = emptyArray<String>()
private var index = -1
override fun entries(res: Resources) = names
override fun descriptions(res: Resources) = names
}
override fun entries(res: Resources) = entries
override fun descriptions(res: Resources) = entries
override fun onPressed(view: View, handler: Handler) {
if (entries.isNotEmpty())
super.onPressed(view, handler)
}
suspend fun loadLanguages(scope: CoroutineScope) {
scope.launch {
availableLocales().let { (names, values) ->
entries = names
entryValues = values
val selectedLocale = currentLocale.getDisplayName(currentLocale)
index = names.indexOfFirst { it == selectedLocale }.let { if (it == -1) 0 else it }
notifyPropertyChanged(BR.description)
}
object LanguageSystem : BaseSettingsItem.Blank() {
override val title = CoreR.string.language.asText()
override val description: TextHolder
get() {
val locale = LocaleSetting.instance.appLocale
return locale?.getDisplayName(locale)?.asText() ?: CoreR.string.system_default.asText()
}
}
}
object Theme : BaseSettingsItem.Blank() {

View File

@ -1,6 +1,10 @@
package com.topjohnwu.magisk.ui.settings
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.provider.Settings
import android.view.View
import android.widget.Toast
import androidx.core.content.pm.ShortcutManagerCompat
@ -16,6 +20,7 @@ import com.topjohnwu.magisk.core.isRunningAsStub
import com.topjohnwu.magisk.core.ktx.activity
import com.topjohnwu.magisk.core.ktx.toast
import com.topjohnwu.magisk.core.tasks.HideAPK
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.databinding.bindExtra
import com.topjohnwu.magisk.events.AddHomeIconEvent
import com.topjohnwu.magisk.events.AuthEvent
@ -30,12 +35,6 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
it.put(BR.handler, this)
}
init {
viewModelScope.launch {
Language.loadLanguages(this)
}
}
private fun createItems(): List<BaseSettingsItem> {
val context = AppContext
val hidden = context.packageName != BuildConfig.APP_PACKAGE_NAME
@ -43,7 +42,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
// Customization
val list = mutableListOf(
Customization,
Theme, Language
Theme, if (LocaleSetting.useLocaleManager) LanguageSystem else Language
)
if (isRunningAsStub && ShortcutManagerCompat.isRequestPinShortcutSupported(context))
list.add(AddShortcut)
@ -98,6 +97,7 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
SystemlessHosts -> createHosts()
Hide, Restore -> withInstallPermission(andThen)
AddShortcut -> AddHomeIconEvent().publish()
LanguageSystem -> launchAppLocaleSettings(view.activity)
else -> andThen()
}
}
@ -112,6 +112,12 @@ class SettingsViewModel : BaseViewModel(), BaseSettingsItem.Handler {
}
}
private fun launchAppLocaleSettings(activity: Activity) {
val intent = Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
intent.data = Uri.fromParts("package", activity.packageName, null)
activity.startActivity(intent)
}
private fun openUrlIfNecessary(view: View) {
UpdateChannelUrl.refresh()
if (UpdateChannelUrl.isEnabled && UpdateChannelUrl.value.isBlank()) {

View File

@ -16,7 +16,6 @@ import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.data.magiskdb.PolicyDao
import com.topjohnwu.magisk.core.ktx.getLabel
import com.topjohnwu.magisk.core.model.su.SuPolicy
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.databinding.MergeObservableList
import com.topjohnwu.magisk.databinding.RvItem
import com.topjohnwu.magisk.databinding.bindExtra
@ -30,6 +29,7 @@ import com.topjohnwu.magisk.view.TextItem
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Locale
class SuperuserViewModel(
private val db: PolicyDao
@ -92,7 +92,7 @@ class SuperuserViewModel(
policies.addAll(map)
}
policies.sortWith(compareBy(
{ it.appName.lowercase(currentLocale) },
{ it.appName.lowercase(Locale.ROOT) },
{ it.packageName }
))
itemsPolicies.update(policies)

View File

@ -14,7 +14,7 @@ import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.google.android.material.circularreveal.CircularRevealCompat
import com.google.android.material.circularreveal.CircularRevealWidget
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.utils.LocaleSetting
import kotlin.math.hypot
object MotionRevealHelper {
@ -63,7 +63,9 @@ object MotionRevealHelper {
it.interpolator = FastOutSlowInInterpolator()
it.addListener(onStart = { show() }, onEnd = { if (revealInfo.radius != 0f) hide() })
val rtlMod = if (currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL) 1f else -1f
val rtlMod =
if (LocaleSetting.instance.currentLocale.layoutDirection == View.LAYOUT_DIRECTION_RTL)
1f else -1f
val maxX = revealInfo.centerX - marginEnd - measuredWidth / 2f
val targetX = if (revealInfo.radius == 0f) 0f else maxX * rtlMod
val moveX = ObjectAnimator.ofFloat(this, View.TRANSLATION_X, targetX)

View File

@ -2,6 +2,7 @@ package com.topjohnwu.magisk.core
import android.app.Activity
import android.app.Application
import android.app.LocaleManager
import android.content.ComponentCallbacks2
import android.content.Context
import android.content.ContextWrapper
@ -13,12 +14,11 @@ import android.system.Os
import androidx.profileinstaller.ProfileInstaller
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.base.UntrackedActivity
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.NetworkObserver
import com.topjohnwu.magisk.core.utils.ProcessLifecycle
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.ShellInit
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.setConfig
import com.topjohnwu.magisk.view.Notifications
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.internal.UiThreadHandler
@ -31,6 +31,9 @@ import timber.log.Timber
import java.lang.ref.WeakReference
import kotlin.system.exitProcess
lateinit var AppApkPath: String
private set
object AppContext : ContextWrapper(null),
Application.ActivityLifecycleCallbacks, ComponentCallbacks2 {
@ -52,7 +55,7 @@ object AppContext : ContextWrapper(null),
}
override fun onConfigurationChanged(newConfig: Configuration) {
resources.setConfig(newConfig)
LocaleSetting.instance.updateResource(resources)
}
override fun onActivityResumed(activity: Activity) {
@ -79,7 +82,6 @@ object AppContext : ContextWrapper(null),
} else {
base.packageResourcePath
}
refreshLocale()
resources.patch()
val shellBuilder = Shell.Builder.create()
@ -97,6 +99,11 @@ object AppContext : ContextWrapper(null),
// Pre-heat the shell ASAP
Shell.getShell(null) {}
if (SDK_INT >= 34 && isRunningAsStub) {
// Send over the locale config manually
val lm = getSystemService(LocaleManager::class.java)
lm.overrideLocaleConfig = LocaleSetting.localeConfig
}
Notifications.setup()
ProcessLifecycle.init(this)
NetworkObserver.init(this)

View File

@ -6,7 +6,7 @@ import com.topjohnwu.magisk.core.di.ServiceLocator
import com.topjohnwu.magisk.core.ktx.writeTo
import com.topjohnwu.magisk.core.repository.DBConfig
import com.topjohnwu.magisk.core.repository.PreferenceConfig
import com.topjohnwu.magisk.core.utils.refreshLocale
import com.topjohnwu.magisk.core.utils.LocaleSetting
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.runBlocking
@ -135,7 +135,7 @@ object Config : PreferenceConfig, DBConfig {
get() = localePrefs
set(value) {
localePrefs = value
refreshLocale()
LocaleSetting.instance.setLocale(value)
}
var zygisk by dbSettings(Key.ZYGISK, false)

View File

@ -6,22 +6,18 @@ import android.content.ComponentName
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import android.util.DisplayMetrics
import com.topjohnwu.magisk.StubApk
import com.topjohnwu.magisk.core.ktx.unwrap
import com.topjohnwu.magisk.core.utils.syncLocale
lateinit var AppApkPath: String
import com.topjohnwu.magisk.core.utils.LocaleSetting
fun Resources.addAssetPath(path: String) = StubApk.addAssetPath(this, path)
fun Resources.patch(): Resources {
if (isRunningAsStub)
addAssetPath(AppApkPath)
syncLocale()
LocaleSetting.instance.updateResource(this)
return this
}
@ -40,16 +36,6 @@ fun Context.wrap(): Context {
}
}
fun createNewResources(): Resources {
val asset = AssetManager::class.java.newInstance()
val config = Configuration(AppContext.resources.configuration)
val metrics = DisplayMetrics()
metrics.setTo(AppContext.resources.displayMetrics)
val res = Resources(asset, metrics, config)
res.addAssetPath(AppApkPath)
return res
}
fun Class<*>.cmp(pkg: String) =
ComponentName(pkg, Info.stub?.classToComponent?.get(name) ?: name)

View File

@ -6,7 +6,7 @@ import com.topjohnwu.magisk.ProviderInstaller
import com.topjohnwu.magisk.core.BuildConfig
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.Info
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.core.utils.LocaleSetting
import okhttp3.Cache
import okhttp3.ConnectionSpec
import okhttp3.Dns
@ -68,7 +68,7 @@ fun createOkHttpClient(context: Context): OkHttpClient {
builder.addInterceptor { chain ->
val request = chain.request().newBuilder()
request.header("User-Agent", "Magisk/${BuildConfig.APP_VERSION_CODE}")
request.header("Accept-Language", currentLocale.toLanguageTag())
request.header("Accept-Language", LocaleSetting.instance.currentLocale.toLanguageTag())
chain.proceed(request.build())
}

View File

@ -6,7 +6,6 @@ import android.content.*
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.drawable.AdaptiveIconDrawable
@ -19,8 +18,8 @@ import android.view.View
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.core.content.getSystemService
import com.topjohnwu.magisk.core.utils.LocaleSetting
import com.topjohnwu.magisk.core.utils.RootUtils
import com.topjohnwu.magisk.core.utils.currentLocale
import com.topjohnwu.magisk.utils.APKInstall
import com.topjohnwu.superuser.internal.UiThreadHandler
import java.io.File
@ -54,9 +53,7 @@ fun ApplicationInfo.getLabel(pm: PackageManager): String {
runCatching {
if (labelRes > 0) {
val res = pm.getResourcesForApplication(this)
val config = Configuration()
config.setLocale(currentLocale)
res.updateConfiguration(config, res.displayMetrics)
LocaleSetting.instance.updateResource(res)
return res.getString(labelRes)
}
}

View File

@ -1,7 +1,6 @@
package com.topjohnwu.magisk.core.ktx
import androidx.collection.SparseArrayCompat
import com.topjohnwu.magisk.core.utils.currentLocale
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@ -17,6 +16,7 @@ import java.lang.reflect.Field
import java.text.DateFormat
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.Locale
import java.util.zip.ZipEntry
import java.util.zip.ZipInputStream
@ -98,13 +98,13 @@ fun Long.toTime(format: DateFormat) = format.format(this).orEmpty()
val timeFormatStandard by lazy {
SimpleDateFormat(
"yyyy-MM-dd'T'HH.mm.ss",
currentLocale
Locale.ROOT
)
}
val timeDateFormat: DateFormat by lazy {
DateFormat.getDateTimeInstance(
DateFormat.DEFAULT,
DateFormat.DEFAULT,
currentLocale
Locale.ROOT
)
}

View File

@ -0,0 +1,183 @@
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.app.LocaleConfig
import android.app.LocaleManager
import android.content.ContextWrapper
import android.content.res.Resources
import android.os.Build
import android.os.LocaleList
import androidx.annotation.RequiresApi
import com.topjohnwu.magisk.core.AppApkPath
import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.base.relaunch
import com.topjohnwu.magisk.core.isRunningAsStub
import org.xmlpull.v1.XmlPullParser
import java.util.Locale
interface LocaleSetting {
// The locale that is manually overridden, null if system default
val appLocale: Locale?
// The current active locale used in the application
val currentLocale: Locale
fun setLocale(tag: String)
fun updateResource(res: Resources)
private class Api23Impl : LocaleSetting {
private val systemLocale: Locale = Locale.getDefault()
override var currentLocale: Locale = systemLocale
override var appLocale: Locale? = null
init {
setLocale(Config.locale)
}
override fun setLocale(tag: String) {
val locale = when {
tag.isEmpty() -> null
else -> Locale.forLanguageTag(tag)
}
currentLocale = locale ?: systemLocale
appLocale = locale
Locale.setDefault(currentLocale)
updateResource(AppContext.resources)
AppContext.foregroundActivity?.relaunch()
}
@Suppress("DEPRECATION")
override fun updateResource(res: Resources) {
val config = res.configuration
config.setLocale(currentLocale)
res.updateConfiguration(config, null)
}
}
@RequiresApi(24)
private class Api24Impl : LocaleSetting {
private val systemLocaleList = LocaleList.getDefault()
private var currentLocaleList: LocaleList = systemLocaleList
override var appLocale: Locale? = null
override val currentLocale: Locale get() = currentLocaleList[0]
init {
setLocale(Config.locale)
}
override fun setLocale(tag: String) {
val localeList = when {
tag.isEmpty() -> null
else -> LocaleList.forLanguageTags(tag)
}
currentLocaleList = localeList ?: systemLocaleList
appLocale = localeList?.get(0)
LocaleList.setDefault(currentLocaleList)
updateResource(AppContext.resources)
AppContext.foregroundActivity?.relaunch()
}
@Suppress("DEPRECATION")
override fun updateResource(res: Resources) {
val config = res.configuration
config.setLocales(currentLocaleList)
res.updateConfiguration(config, null)
}
}
@RequiresApi(33)
private class Api33Impl : LocaleSetting {
private val lm: LocaleManager = AppContext.getSystemService(LocaleManager::class.java)
override val appLocale: Locale?
get() = lm.applicationLocales.let { if (it.isEmpty) null else it[0] }
override val currentLocale: Locale
get() = appLocale ?: lm.systemLocales[0]
// These following methods should not be used
override fun setLocale(tag: String) {}
override fun updateResource(res: Resources) {}
}
class AppLocaleList(
val names: Array<String>,
val tags: Array<String>
)
@SuppressLint("NewApi")
companion object {
val available: AppLocaleList by lazy {
val names = ArrayList<String>()
val tags = ArrayList<String>()
names.add(AppContext.getString(R.string.system_default))
tags.add("")
if (Build.VERSION.SDK_INT >= 34) {
// Use platform LocaleConfig parser
val config = localeConfig
val list = config.supportedLocales ?: LocaleList.getEmptyLocaleList()
names.ensureCapacity(list.size() + 1)
tags.ensureCapacity(list.size() + 1)
for (i in 0 until list.size()) {
val locale = list[i]
names.add(locale.getDisplayName(locale))
tags.add(locale.toLanguageTag())
}
} else {
// Manually parse locale_config.xml
val parser = AppContext.resources.getXml(R.xml.locale_config)
while (true) {
when (parser.next()) {
XmlPullParser.START_TAG -> {
if (parser.name == "locale") {
val tag = parser.getAttributeValue(0)
val locale = Locale.forLanguageTag(tag)
names.add(locale.getDisplayName(locale))
tags.add(tag)
}
}
XmlPullParser.END_DOCUMENT -> break
}
}
}
AppLocaleList(names.toTypedArray(), tags.toTypedArray())
}
@get:RequiresApi(34)
val localeConfig: LocaleConfig by lazy {
val context = if (isRunningAsStub) {
val pkgInfo = AppContext.packageManager.getPackageArchiveInfo(AppApkPath, 0)!!
object : ContextWrapper(AppContext) {
override fun getApplicationInfo() = pkgInfo.applicationInfo
}
} else {
AppContext
}
LocaleConfig.fromContextIgnoringOverride(context)
}
val useLocaleManager get() =
if (isRunningAsStub) Build.VERSION.SDK_INT >= 34
else Build.VERSION.SDK_INT >= 33
val instance: LocaleSetting by lazy {
// Initialize available locale list
available
if (useLocaleManager) {
Api33Impl()
} else if (Build.VERSION.SDK_INT <= 23) {
Api23Impl()
} else {
Api24Impl()
}
}
}
}

View File

@ -1,88 +0,0 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.core.utils
import android.annotation.SuppressLint
import android.content.res.Configuration
import android.content.res.Resources
import com.topjohnwu.magisk.core.AppContext
import com.topjohnwu.magisk.core.Config
import com.topjohnwu.magisk.core.R
import com.topjohnwu.magisk.core.base.relaunch
import com.topjohnwu.magisk.core.createNewResources
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.Locale
var currentLocale: Locale = Locale.getDefault()
@SuppressLint("ConstantLocale")
val defaultLocale: Locale = Locale.getDefault()
private var cachedLocales: Pair<Array<String>, Array<String>>? = null
suspend fun availableLocales() = cachedLocales ?:
withContext(Dispatchers.Default) {
val compareId = R.string.app_changelog
// Create a completely new resource to prevent cross talk over active configs
val res = createNewResources()
fun changeLocale(locale: Locale) {
res.configuration.setLocale(locale)
res.updateConfiguration(res.configuration, res.displayMetrics)
}
val locales = ArrayList<String>().apply {
// Add default locale
add("en")
// Add some special locales
add("zh-TW")
add("pt-BR")
// Then add all supported locales
addAll(Resources.getSystem().assets.locales)
}.map {
Locale.forLanguageTag(it)
}.distinctBy {
changeLocale(it)
res.getString(compareId)
}.sortedWith { a, b ->
a.getDisplayName(a).compareTo(b.getDisplayName(b), true)
}
changeLocale(defaultLocale)
val defName = res.getString(R.string.system_default)
val names = ArrayList<String>(locales.size + 1)
val values = ArrayList<String>(locales.size + 1)
names.add(defName)
values.add("")
locales.forEach { locale ->
names.add(locale.getDisplayName(locale))
values.add(locale.toLanguageTag())
}
(names.toTypedArray() to values.toTypedArray()).also { cachedLocales = it }
}
fun Resources.setConfig(config: Configuration) {
config.setLocale(currentLocale)
updateConfiguration(config, null)
}
fun Resources.syncLocale() = setConfig(configuration)
fun refreshLocale() {
val localeConfig = Config.locale
currentLocale = when {
localeConfig.isEmpty() -> defaultLocale
else -> Locale.forLanguageTag(localeConfig)
}
Locale.setDefault(currentLocale)
AppContext.resources.syncLocale()
AppContext.foregroundActivity?.relaunch()
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="ar" />
<locale android:name="ast" />
<locale android:name="az" />
<locale android:name="be" />
<locale android:name="bg" />
<locale android:name="bn" />
<locale android:name="ca" />
<locale android:name="cs" />
<locale android:name="de" />
<locale android:name="el" />
<locale android:name="en" />
<locale android:name="es" />
<locale android:name="et" />
<locale android:name="fa" />
<locale android:name="fr" />
<locale android:name="hi" />
<locale android:name="hr" />
<locale android:name="hu" />
<locale android:name="in" />
<locale android:name="it" />
<locale android:name="iw" />
<locale android:name="ja" />
<locale android:name="ka" />
<locale android:name="kk" />
<locale android:name="ko" />
<locale android:name="lt" />
<locale android:name="mk" />
<locale android:name="ml" />
<locale android:name="nb" />
<locale android:name="nl" />
<locale android:name="pa" />
<locale android:name="pl" />
<locale android:name="pt-BR" />
<locale android:name="pt-PT" />
<locale android:name="ro" />
<locale android:name="ru" />
<locale android:name="sk" />
<locale android:name="sq" />
<locale android:name="sr" />
<locale android:name="sv" />
<locale android:name="sw" />
<locale android:name="ta" />
<locale android:name="th" />
<locale android:name="tr" />
<locale android:name="uk" />
<locale android:name="vi" />
<locale android:name="zh-CN" />
<locale android:name="zh-TW" />
</locale-config>