Совсем недавно context.getResources (). updateConfiguration () устарел в Android API 25, и рекомендуется использовать context. createConfigurationContext () вместо этого.
Кто-нибудь знает, как createConfigurationContext можно использовать для переопределения языкового стандарта системы Android?
прежде, чем это будет сделано:
Configuration config = getBaseContext().getResources().getConfiguration();
config.setLocale(locale);
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
android
locale
right-to-left
Бассель Мурджан
источник
источник
Ответы:
Вдохновленный каллиграфией , я создал оболочку контекста. В моем случае мне нужно перезаписать системный язык, чтобы предоставить пользователям моего приложения возможность изменить язык приложения, но это можно настроить с помощью любой логики, которую вам нужно реализовать.
import android.annotation.TargetApi; import android.content.Context; import android.content.ContextWrapper; import android.content.res.Configuration; import android.os.Build; import java.util.Locale; public class MyContextWrapper extends ContextWrapper { public MyContextWrapper(Context base) { super(base); } @SuppressWarnings("deprecation") public static ContextWrapper wrap(Context context, String language) { Configuration config = context.getResources().getConfiguration(); Locale sysLocale = null; if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { sysLocale = getSystemLocale(config); } else { sysLocale = getSystemLocaleLegacy(config); } if (!language.equals("") && !sysLocale.getLanguage().equals(language)) { Locale locale = new Locale(language); Locale.setDefault(locale); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { setSystemLocale(config, locale); } else { setSystemLocaleLegacy(config, locale); } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { context = context.createConfigurationContext(config); } else { context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics()); } return new MyContextWrapper(context); } @SuppressWarnings("deprecation") public static Locale getSystemLocaleLegacy(Configuration config){ return config.locale; } @TargetApi(Build.VERSION_CODES.N) public static Locale getSystemLocale(Configuration config){ return config.getLocales().get(0); } @SuppressWarnings("deprecation") public static void setSystemLocaleLegacy(Configuration config, Locale locale){ config.locale = locale; } @TargetApi(Build.VERSION_CODES.N) public static void setSystemLocale(Configuration config, Locale locale){ config.setLocale(locale); } }
и чтобы внедрить вашу оболочку, в каждое действие добавьте следующий код:
@Override protected void attachBaseContext(Context newBase) { super.attachBaseContext(MyContextWrapper.wrap(newBase,"fr")); }
ОБНОВЛЕНИЕ 22/12/2020 После реализации библиотеки материалов Android ContextThemeWrapper для поддержки темного режима настройка языка будет нарушена, и настройка языка будет потеряна. После нескольких месяцев царапин на голове проблема была решена путем добавления следующего кода в метод Activity и Fragment onCreate.
Context context = MyContextWrapper.wrap(this/*in fragment use getContext() instead of this*/, "fr"); getResources().updateConfiguration(context.getResources().getConfiguration(), context.getResources().getDisplayMetrics());
ОБНОВЛЕНИЕ 19.10.2018 Иногда после изменения ориентации или приостановки / возобновления активности объект конфигурации сбрасывается до конфигурации системы по умолчанию, и в результате мы видим, что приложение отображает английский текст «en», даже если мы обернули контекст французской локалью «fr» . Поэтому рекомендуется никогда не сохранять объект Context / Activity в глобальной переменной действий или фрагментов.
кроме того, создайте и используйте следующее в MyBaseFragment или MyBaseActivity:
public Context getMyContext(){ return MyContextWrapper.wrap(getContext(),"fr"); }
Эта практика предоставит вам 100% -ное решение без ошибок.
источник
Наверное так:
Configuration overrideConfiguration = getBaseContext().getResources().getConfiguration(); overrideConfiguration.setLocales(LocaleList); Context context = createConfigurationContext(overrideConfiguration); Resources resources = context.getResources();
Бонус: статья в блоге, использующая createConfigurationContext ()
источник
LocaleList
Я создал это, вдохновленный каллиграфией, Мурджаном и мной.
сначала вы должны создать подкласс Application:
public class MyApplication extends Application { private Locale locale = null; @Override public void onCreate() { super.onCreate(); SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); Configuration config = getBaseContext().getResources().getConfiguration(); String lang = preferences.getString(getString(R.string.pref_locale), "en"); String systemLocale = getSystemLocale(config).getLanguage(); if (!"".equals(lang) && !systemLocale.equals(lang)) { locale = new Locale(lang); Locale.setDefault(locale); setSystemLocale(config, locale); updateConfiguration(config); } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (locale != null) { setSystemLocale(newConfig, locale); Locale.setDefault(locale); updateConfiguration(newConfig); } } @SuppressWarnings("deprecation") private static Locale getSystemLocale(Configuration config) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return config.getLocales().get(0); } else { return config.locale; } } @SuppressWarnings("deprecation") private static void setSystemLocale(Configuration config, Locale locale) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { config.setLocale(locale); } else { config.locale = locale; } } @SuppressWarnings("deprecation") private void updateConfiguration(Configuration config) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { getBaseContext().createConfigurationContext(config); } else { getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics()); } } }
тогда вам нужно установить это в свой тег приложения AndroidManifest.xml:
<application ... android:name="path.to.your.package.MyApplication" >
и добавьте это в свой тег активности AndroidManifest.xml.
<activity ... android:configChanges="locale" >
обратите внимание, что pref_locale - это строковый ресурс, подобный этому:
<string name="pref_locale">fa</string>
а жесткий код "en" - это язык по умолчанию, если pref_locale не установлен
источник
Здесь нет 100% рабочего решения. Вам нужно использовать оба
createConfigurationContext
иapplyOverrideConfiguration
. В противном случае, даже если вы заменитеbaseContext
каждое действие новой конфигурацией, активность все равно будет использоватьсяResources
изContextThemeWrapper
со старой местностью.Итак, вот мое решение, которое работает до API 29:
Создайте подкласс своего
MainApplication
класса от:abstract class LocalApplication : Application() { override fun attachBaseContext(base: Context) { super.attachBaseContext( base.toLangIfDiff( PreferenceManager .getDefaultSharedPreferences(base) .getString("langPref", "sys")!! ) ) } }
Также каждый
Activity
из:abstract class LocalActivity : AppCompatActivity() { override fun attachBaseContext(newBase: Context) { super.attachBaseContext( PreferenceManager .getDefaultSharedPreferences(base) .getString("langPref", "sys")!! ) } override fun applyOverrideConfiguration(overrideConfiguration: Configuration) { super.applyOverrideConfiguration(baseContext.resources.configuration) } }
Добавить
LocaleExt.kt
со следующими функциями расширения:const val SYSTEM_LANG = "sys" const val ZH_LANG = "zh" const val SIMPLIFIED_CHINESE_SUFFIX = "rCN" private fun Context.isAppLangDiff(prefLang: String): Boolean { val appConfig: Configuration = this.resources.configuration val sysConfig: Configuration = Resources.getSystem().configuration val appLang: String = appConfig.localeCompat.language val sysLang: String = sysConfig.localeCompat.language return if (SYSTEM_LANG == prefLang) { appLang != sysLang } else { appLang != prefLang || ZH_LANG == prefLang } } fun Context.toLangIfDiff(lang: String): Context = if (this.isAppLangDiff(lang)) { this.toLang(lang) } else { this } @Suppress("DEPRECATION") fun Context.toLang(toLang: String): Context { val config = Configuration() val toLocale = langToLocale(toLang) Locale.setDefault(toLocale) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { config.setLocale(toLocale) val localeList = LocaleList(toLocale) LocaleList.setDefault(localeList) config.setLocales(localeList) } else { config.locale = toLocale } return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { config.setLayoutDirection(toLocale) this.createConfigurationContext(config) } else { this.resources.updateConfiguration(config, this.resources.displayMetrics) this } } /** * @param toLang - two character representation of language, could be "sys" - which represents system's locale */ fun langToLocale(toLang: String): Locale = when { toLang == SYSTEM_LANG -> Resources.getSystem().configuration.localeCompat toLang.contains(ZH_LANG) -> when { toLang.contains(SIMPLIFIED_CHINESE_SUFFIX) -> Locale.SIMPLIFIED_CHINESE Build.VERSION.SDK_INT >= Build.VERSION_CODES.N -> Locale(ZH_LANG, "Hant") else -> Locale.TRADITIONAL_CHINESE } else -> Locale(toLang) } @Suppress("DEPRECATION") private val Configuration.localeCompat: Locale get() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { this.locales.get(0) } else { this.locale }
Добавьте к своим
res/values/arrays.xml
поддерживаемым языкам в массиве:<string-array name="lang_values" translatable="false"> <item>sys</item> <!-- System default --> <item>ar</item> <item>de</item> <item>en</item> <item>es</item> <item>fa</item> ... <item>zh</item> <!-- Traditional Chinese --> <item>zh-rCN</item> <!-- Simplified Chinese --> </string-array>
Хочу отметить:
config.setLayoutDirection(toLocale);
для изменения направления макета при использовании локалей RTL, таких как арабский, персидский и т. Д."sys"
в коде есть значение, которое означает «наследовать язык системы по умолчанию».ContextWraper
, просто установите новый контекст, возвращаемый изcreateConfigurationContext
как baseContextcreateConfigurationContext
вы должны передать конфигурацию с нуля и только сLocale
установленными свойствами. Для этой конфигурации не должно быть никаких других свойств. Потому что, если мы установим некоторые другие свойства для этой конфигурации ( например, ориентацию ), мы переопределим это свойство навсегда, и наш контекст больше не изменит это свойство ориентации, даже если мы повернем экран.recreate
активности, когда пользователь выбирает другой язык, потому что applicationContext останется со старой локалью и может обеспечить неожиданное поведение. Поэтому прислушайтесь к изменению предпочтений и вместо этого перезапустите всю задачу приложения:fun Context.recreateTask() { this.packageManager .getLaunchIntentForPackage(context.packageName) ?.let { intent -> val restartIntent = Intent.makeRestartActivityTask(intent.component) this.startActivity(restartIntent) Runtime.getRuntime().exit(0) } }
источник
recreateTask(Context context)
метод не работает должным образом, поскольку я все еще вижу макет без каких-либо изменений.Вот решение @ bassel-mourjan с небольшим количеством доброты котлина :):
import android.annotation.TargetApi import android.content.ContextWrapper import android.os.Build import java.util.* @Suppress("DEPRECATION") fun ContextWrapper.wrap(language: String): ContextWrapper { val config = baseContext.resources.configuration val sysLocale: Locale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { this.getSystemLocale() } else { this.getSystemLocaleLegacy() } if (!language.isEmpty() && sysLocale.language != language) { val locale = Locale(language) Locale.setDefault(locale) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { this.setSystemLocale(locale) } else { this.setSystemLocaleLegacy(locale) } } return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { val context = baseContext.createConfigurationContext(config) ContextWrapper(context) } else { baseContext.resources.updateConfiguration(config, baseContext.resources.displayMetrics) ContextWrapper(baseContext) } } @Suppress("DEPRECATION") fun ContextWrapper.getSystemLocaleLegacy(): Locale { val config = baseContext.resources.configuration return config.locale } @TargetApi(Build.VERSION_CODES.N) fun ContextWrapper.getSystemLocale(): Locale { val config = baseContext.resources.configuration return config.locales[0] } @Suppress("DEPRECATION") fun ContextWrapper.setSystemLocaleLegacy(locale: Locale) { val config = baseContext.resources.configuration config.locale = locale } @TargetApi(Build.VERSION_CODES.N) fun ContextWrapper.setSystemLocale(locale: Locale) { val config = baseContext.resources.configuration config.setLocale(locale) }
И вот как вы его используете:
override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(ContextWrapper(newBase).wrap(defaultLocale.language)) }
источник
val config = baseContext.resources.configuration
очень-очень неправильная. Из-за этого у вас будет много ошибок. Вместо этого вам нужно создать новую конфигурацию. Смотрите мой ответ.здесь есть простое решение с contextWrapper: Android N изменяет язык программно. Обратите внимание на метод reconate ()
источник
Попробуй это:
Configuration config = getBaseContext().getResources().getConfiguration(); config.setLocale(locale); context.createConfigurationContext(config);
источник