Java Android N 以编程方式更改语言
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/39705739/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Android N change language programmatically
提问by Kuva
I found really weird bug that is reproduced only on Android N devices.
我发现了仅在 Android N 设备上重现的非常奇怪的错误。
In tour of my app there is a possibility to change language. Here is the code that changes it.
在浏览我的应用程序时,可以更改语言。这是更改它的代码。
public void update(Locale locale) {
Locale.setDefault(locale);
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
LocaleList localeList = new LocaleList(locale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
configuration.setLocale(locale);
} else if (BuildUtils.isAtLeast17Api()){
configuration.setLocale(locale);
} else {
configuration.locale = locale;
}
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
This code works great in activity of my tour ( with recreate()
call) but in all next activities all String resources are wrong. Screen rotation fixes it. What can i do with this problem? Should i change locale for Android N differently or it's just system bug?
这段代码在我的巡回演出活动中效果很好(带recreate()
通话),但在所有接下来的活动中,所有字符串资源都是错误的。屏幕旋转修复它。遇到这个问题我该怎么办?我应该以不同的方式更改 Android N 的语言环境还是只是系统错误?
P.S. Here's what i found. At first start of MainActivity (which is after my tour) Locale.getDefault()
is correct but resources are wrong. But in other activities it gives me wrong Locale and wrong resources from this locale. After rotation screen (or perhaps some other configuration change) Locale.getDefault()
is correct.
PS这是我发现的。MainActivity 的第一次启动(在我的巡回演出之后)Locale.getDefault()
是正确的,但资源是错误的。但是在其他活动中,它给了我错误的语言环境和来自该语言环境的错误资源。旋转屏幕(或其他一些配置更改)后Locale.getDefault()
是正确的。
采纳答案by Kuva
Ok. Finally i managed to find a solution.
好的。最后我设法找到了解决方案。
First you should know that in 25 API Resources.updateConfiguration(...)
is deprecated. So instead you can do something like this:
首先你应该知道 25 APIResources.updateConfiguration(...)
已被弃用。因此,您可以执行以下操作:
1) You need to create your own ContextWrapper that will override all configuration params in baseContext. For example this is mine ContextWrapper that changes Locale correctly. Pay attention on context.createConfigurationContext(configuration)
method.
1) 您需要创建自己的 ContextWrapper,它将覆盖 baseContext 中的所有配置参数。例如,这是我的 ContextWrapper 可以正确更改区域设置。注意context.createConfigurationContext(configuration)
方法。
public class ContextWrapper extends android.content.ContextWrapper {
public ContextWrapper(Context base) {
super(base);
}
public static ContextWrapper wrap(Context context, Locale newLocale) {
Resources res = context.getResources();
Configuration configuration = res.getConfiguration();
if (BuildUtils.isAtLeast24Api()) {
configuration.setLocale(newLocale);
LocaleList localeList = new LocaleList(newLocale);
LocaleList.setDefault(localeList);
configuration.setLocales(localeList);
context = context.createConfigurationContext(configuration);
} else if (BuildUtils.isAtLeast17Api()) {
configuration.setLocale(newLocale);
context = context.createConfigurationContext(configuration);
} else {
configuration.locale = newLocale;
res.updateConfiguration(configuration, res.getDisplayMetrics());
}
return new ContextWrapper(context);
}
}
2) Here's what you should do in your BaseActivity:
2)这是您应该在 BaseActivity 中执行的操作:
@Override
protected void attachBaseContext(Context newBase) {
Locale newLocale;
// .. create or get your new Locale object here.
Context context = ContextWrapper.wrap(newBase, newLocale);
super.attachBaseContext(context);
}
Note:
笔记:
Remember to recreate your activity if you want to change Locale in your App somewhere. You can override any configuration you want using this solution.
如果您想在应用程序中的某处更改区域设置,请记住重新创建您的活动。您可以使用此解决方案覆盖所需的任何配置。
回答by thyzz
Inspired by various codes (i.e: our Stackoverflow team (shout out people)), I had produced a much simpler version. The ContextWrapper
extension is unnecessary.
受各种代码的启发(即:我们的 Stackoverflow 团队(大喊大叫)),我制作了一个更简单的版本。所述ContextWrapper
扩展是不需要的。
First let's say you have 2 buttons for 2 languages, EN and KH. In the onClick for the buttons save the language code into SharedPreferences
, then call the activity recreate()
method.
首先,假设您有 2 个按钮用于 2 种语言,EN 和 KH。在按钮的 onClick 中将语言代码保存到 中SharedPreferences
,然后调用活动recreate()
方法。
Example:
例子:
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.btn_lang_en:
//save "en" to SharedPref here
break;
case R.id.btn_lang_kh:
//save "kh" to SharedPref here
break;
default:
break;
}
getActivity().recreate();
}
Then create a static method that returns ContextWrapper
, perhaps in a Utils class (coz that's what I did, lul).
然后创建一个返回的静态方法ContextWrapper
,可能在 Utils 类中(因为这就是我所做的,lul)。
public static ContextWrapper changeLang(Context context, String lang_code){
Locale sysLocale;
Resources rs = context.getResources();
Configuration config = rs.getConfiguration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sysLocale = config.getLocales().get(0);
} else {
sysLocale = config.locale;
}
if (!lang_code.equals("") && !sysLocale.getLanguage().equals(lang_code)) {
Locale locale = new Locale(lang_code);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
config.setLocale(locale);
} else {
config.locale = locale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
context = context.createConfigurationContext(config);
} else {
context.getResources().updateConfiguration(config, context.getResources().getDisplayMetrics());
}
}
return new ContextWrapper(context);
}
Finally, load the language code from SharedPreferences
in ALL ACTIVITY'SattachBaseContext(Context newBase)
method.
最后,从加载的语言代码SharedPreferences
中的所有活动'SattachBaseContext(Context newBase)
方法。
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
BONUS: To save palm sweat on keyboard, I created a LangSupportBaseActivity
class that extends the Activity
and use the last chunk of code there. And I have all other activities extends LangSupportBaseActivity
.
奖励:为了节省键盘上的汗水,我创建了一个LangSupportBaseActivity
类来扩展Activity
并使用那里的最后一段代码。我有所有其他活动扩展LangSupportBaseActivity
。
Example:
例子:
public class LangSupportBaseActivity extends Activity{
...blab blab blab so on and so forth lines of neccessary code
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
}
public class HomeActivity extends LangSupportBaseActivity{
...blab blab blab
}
回答by QuantumTiger
The above answers set me on the right track but left a couple of issues
以上答案让我走上了正确的道路,但留下了几个问题
- On android 7 and 9 I could happily change to any language other than the app default. When I changed back to the app default language it showed the last language selected - not surprising as this has overridden the default (although interestingly this wasn't an issue on Android 8!).
- For RTL languages it didn't update the layouts to RTL
- 在 android 7 和 9 上,我可以很高兴地更改为应用程序默认以外的任何语言。当我改回应用默认语言时,它显示了最后选择的语言——这并不奇怪,因为这覆盖了默认语言(尽管有趣的是,这在 Android 8 上不是问题!)。
- 对于 RTL 语言,它没有将布局更新为 RTL
To fix the first item I stored the default locale on app start.
为了修复第一项,我在应用程序启动时存储了默认语言环境。
NoteIf your default language is set to "en" then locales of "enGB" or "enUS" both need to match the default locale (unless you provide seperate localisations for them). Similarly in the example below if the user's phone locale is arEG (Arabic Egypt) then the defLanguage needs to be "ar" not "arEG"
注意如果您的默认语言设置为“en”,则“enGB”或“enUS”的语言环境都需要匹配默认语言环境(除非您为它们提供单独的本地化)。同样在下面的示例中,如果用户的电话区域设置是 arEG(阿拉伯埃及语),则 defLanguage 需要是“ar”而不是“arEG”
private Locale defLocale = Locale.getDefault();
private Locale locale = Locale.getDefault();
public static myApplication myApp;
public static Resources res;
private static String defLanguage = Locale.getDefault().getLanguage() + Locale.getDefault().getCountry();
private static sLanguage = "en";
private static final Set<String> SUPPORTEDLANGUAGES = new HashSet<>(Arrays.asList(new String[]{"en", "ar", "arEG"}));
@Override
protected void attachBaseContext(Context base) {
if (myApp == null) myApp = this;
if (base == null) super.attachBaseContext(this);
else super.attachBaseContext(setLocale(base));
}
@Override
public void onCreate() {
myApp = this;
if (!SUPPORTEDLANGUAGES.contains(test)) {
// The default locale (eg enUS) is not in the supported list - lets see if the language is
if (SUPPORTEDLANGUAGES.contains(defLanguage.substring(0,2))) {
defLanguage = defLanguage.substring(0,2);
}
}
}
private static void setLanguage(String sLang) {
Configuration baseCfg = myApp.getBaseContext().getResources().getConfiguration();
if ( sLang.length() > 2 ) {
String s[] = sLang.split("_");
myApp.locale = new Locale(s[0],s[1]);
sLanguage = s[0] + s[1];
}
else {
myApp.locale = new Locale(sLang);
sLanguage = sLang;
}
}
public static Context setLocale(Context ctx) {
Locale.setDefault(myApp.locale);
Resources tempRes = ctx.getResources();
Configuration config = tempRes.getConfiguration();
if (Build.VERSION.SDK_INT >= 24) {
// If changing to the app default language, set locale to the default locale
if (sLanguage.equals(myApp.defLanguage)) {
config.setLocale(myApp.defLocale);
// restored the default locale as well
Locale.setDefault(myApp.defLocale);
}
else config.setLocale(myApp.locale);
ctx = ctx.createConfigurationContext(config);
// update the resources object to point to the current localisation
res = ctx.getResources();
} else {
config.locale = myApp.locale;
tempRes.updateConfiguration(config, tempRes.getDisplayMetrics());
}
return ctx;
}
To fix the RTL issues I extended AppCompatActivity as per Fragments comments in this answer
为了解决 RTL 问题,我根据此答案中的Fragments 评论扩展了 AppCompatActivity
public class myCompatActivity extends AppCompatActivity {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(myApplication.setLocale(base));
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= 17) {
getWindow().getDecorView().setLayoutDirection(myApplication.isRTL() ?
View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
}
}
}
回答by Ali Nadalizadeh
Since Android 7.0+ some parts of my app didn't change their language anymore. Even with the new methods proposed above. Updating of both application and activity context helped me. Here is a Kotlin example of Activity subclass overrides:
自 Android 7.0+ 以来,我的应用程序的某些部分不再更改其语言。即使使用上面提出的新方法。应用程序和活动上下文的更新对我有帮助。这是 Activity 子类覆盖的 Kotlin 示例:
private fun setApplicationLanguage(newLanguage: String) {
val activityRes = resources
val activityConf = activityRes.configuration
val newLocale = Locale(newLanguage)
activityConf.setLocale(newLocale)
activityRes.updateConfiguration(activityConf, activityRes.displayMetrics)
val applicationRes = applicationContext.resources
val applicationConf = applicationRes.configuration
applicationConf.setLocale(newLocale)
applicationRes.updateConfiguration(applicationConf,
applicationRes.displayMetrics)
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
setApplicationLanguage("fa");
}
Note: updateConfiguration is deprecated but anyway, createConfigurationContext for each Activity, left some strings unchanged.
注意:updateConfiguration 已被弃用,但无论如何,为每个活动创建配置上下文,保留一些字符串不变。
回答by Oleksandr Albul
Changing locale programatically in Android app is quite a pain. I have spent lot of time to find working solution, that currently works in production.
在 Android 应用程序中以编程方式更改语言环境非常痛苦。我花了很多时间来寻找目前在生产中有效的解决方案。
You need to override context in every Activity
but also in your Application
class, otherwise you will end up with mixed languages in ui.
您需要在每个Activity
但也在您的Application
班级中覆盖上下文,否则您最终将在 ui 中使用混合语言。
So here is mine solution which works up to API 29:
所以这是我的解决方案,它适用于 API 29:
Subclass your MainApplication
class from:
MainApplication
从以下子类化您的类:
abstract class LocalApplication : Application() {
override fun attachBaseContext(base: Context) {
super.attachBaseContext(
base.toLangIfDiff(
PreferenceManager
.getDefaultSharedPreferences(base)
.getString("langPref", "sys")!!
)
)
}
}
Also every Activity
from:
还有每个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)
}
}
Add LocaleExt.kt
with next extension functions:
添加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
}
Add to your res/values/arrays.xml
your supported languages in array:
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>
Here is key points:
以下是关键点:
- Use
config.setLayoutDirection(toLocale);
to change layout direction when you use RTL locales like Arabic, Persian, etc. "sys"
in the code is a value that means "inherit system default language".- Here "langPref" is a key of preference where you put user current language.
- There is no need to recreate the context if it already uses needed locale.
- There is no need for
ContextWraper
as posted here, just set new context returned fromcreateConfigurationContext
as baseContext - This is very important! When you call
createConfigurationContext
you should pass configuration crated from scratchand only withLocale
property set. There shouldn't be any other property set to this configuration. Because if we set some other properties for this config (orientationfor example), we override that property forever, and our context no longer change this orientationproperty even if we rotate the screen. - It is not enough only to
recreate
activity when user selects a different language, because applicationContext will remain with old locale and it could provide unexpected behaviour. So listen to preference change and restart whole application task instead:
- 用于
config.setLayoutDirection(toLocale);
在使用 RTL 语言环境(如阿拉伯语、波斯语等)时更改布局方向。 "sys"
代码中的值表示“继承系统默认语言”。- 这里的“langPref”是您放置用户当前语言的首选键。
- 如果上下文已经使用了所需的语言环境,则无需重新创建上下文。
- 不需要
ContextWraper
像这里发布的那样,只需设置从createConfigurationContext
作为 baseContext返回的新上下文 - 这是非常重要的!当你打电话时,
createConfigurationContext
你应该从头开始传递配置,并且只传递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)
}
}