我的应用支持 3 种(很快 4 种)语言。由于几个语言环境非常相似,我想为用户提供在我的应用程序中更改语言环境的选项,例如,意大利人可能更喜欢西班牙语而不是英语。
有没有办法让用户在应用程序可用的语言环境中进行选择,然后更改使用的语言环境?我不认为为每个活动设置语言环境是一个问题,因为在基类中执行它是一项简单的任务。
我的应用支持 3 种(很快 4 种)语言。由于几个语言环境非常相似,我想为用户提供在我的应用程序中更改语言环境的选项,例如,意大利人可能更喜欢西班牙语而不是英语。
有没有办法让用户在应用程序可用的语言环境中进行选择,然后更改使用的语言环境?我不认为为每个活动设置语言环境是一个问题,因为在基类中执行它是一项简单的任务。
希望这有帮助(在 onResume 中):
Locale locale = new Locale("ru");
Locale.setDefault(locale);
Configuration config = getBaseContext().getResources().getConfiguration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
对于仍在寻找此答案的人,由于configuration.locale
API 24 已弃用,您现在可以使用:
configuration.setLocale(locale);
考虑到此方法的 minSkdVersion 是 API 17。
完整示例代码:
@SuppressWarnings("deprecation")
private void setLocale(Locale locale){
SharedPrefUtils.saveLocale(locale); // optional - Helper method to save the selected language to SharedPreferences in case you might need to attach to activity context (you will need to code this)
Resources resources = getResources();
Configuration configuration = resources.getConfiguration();
DisplayMetrics displayMetrics = resources.getDisplayMetrics();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
configuration.setLocale(locale);
} else{
configuration.locale=locale;
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N){
getApplicationContext().createConfigurationContext(configuration);
} else {
resources.updateConfiguration(configuration,displayMetrics);
}
}
不要忘记,如果您使用正在运行的 Activity 更改语言环境,则需要重新启动它才能使更改生效。
编辑 2018 年 5 月 11 日
从 @CookieMonster 的帖子中可以看出,您可能会在较高 API 版本中保持语言环境更改时遇到问题。如果是这样,请将以下代码添加到您的 Base Activity(BaseActivity 扩展 AppCompatActivity / 其他活动),以便您在每次创建 Activity 时更新上下文区域设置:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(updateBaseContextLocale(base));
}
private Context updateBaseContextLocale(Context context) {
String language = SharedPrefUtils.getSavedLanguage(); // Helper method to get saved language from SharedPreferences
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
return updateResourcesLocale(context, locale);
}
return updateResourcesLocaleLegacy(context, locale);
}
@TargetApi(Build.VERSION_CODES.N_MR1)
private Context updateResourcesLocale(Context context, Locale locale) {
Configuration configuration = new Configuration(context.getResources().getConfiguration());
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
如果您使用它,请不要忘记在设置语言环境时将语言保存到 SharedPreferencessetLocale(locale)
编辑 2020 年 4 月 7 日
您可能在 Android 6 和 7 中遇到问题,这是由于处理夜间模式时 androidx 库中的问题所致。为此,您还需要applyOverrideConfiguration
在基本活动中覆盖并更新配置的区域设置,以防创建新的区域设置。
示例代码:
@Override
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
// update overrideConfiguration with your locale
setLocale(overrideConfiguration) // you will need to implement this
}
super.applyOverrideConfiguration(overrideConfiguration);
}
我在使用具有Android OS N 及更高版本的设备以编程方式设置语言环境时遇到问题。对我来说,解决方案是在我的基本活动中编写此代码:
(如果您没有基本活动,那么您应该在所有活动中进行这些更改)
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(updateBaseContextLocale(base));
}
private Context updateBaseContextLocale(Context context) {
String language = SharedPref.getInstance().getSavedLanguage();
Locale locale = new Locale(language);
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResourcesLocale(context, locale);
}
return updateResourcesLocaleLegacy(context, locale);
}
@TargetApi(Build.VERSION_CODES.N)
private Context updateResourcesLocale(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private Context updateResourcesLocaleLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
请注意,在这里调用是不够的
createConfigurationContext(configuration)
您还需要获取此方法返回的上下文,然后在attachBaseContext
方法中设置此上下文。
由于当前解决此问题的方法没有完整的答案,因此我尝试提供完整解决方案的说明。如果缺少某些内容或可以做得更好,请发表评论。
首先,存在一些想要解决问题的库,但它们似乎都已过时或缺少某些功能:
此外,我认为编写一个库可能不是解决这个问题的好方法/简单方法,因为没有太多工作要做,而且要做的是更改现有代码而不是使用完全解耦的东西。因此,我编写了以下应该完整的说明。
我的解决方案主要基于https://github.com/gunhansancar/ChangeLanguageExample(已链接到localhost)。这是我发现的最好的代码。一些备注:
updateViews()
在更改语言环境(使用通常getString(id)
)后手动更新所有字符串,这在下面显示的方法中是不必要的我对其进行了一些更改,将保留所选语言环境的部分解耦(因为有人可能希望单独执行此操作,如下所示)。
解决方案包括以下两个步骤:
使用LocaleHelper
基于gunhansancar 的 LocaleHelper 的类:
ListPreference
在 a 中添加PreferenceFragment
可用语言(必须在以后添加语言时维护)import android.annotation.TargetApi;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.preference.PreferenceManager;
import java.util.Locale;
import mypackage.SettingsFragment;
/**
* Manages setting of the app's locale.
*/
public class LocaleHelper {
public static Context onAttach(Context context) {
String locale = getPersistedLocale(context);
return setLocale(context, locale);
}
public static String getPersistedLocale(Context context) {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString(SettingsFragment.KEY_PREF_LANGUAGE, "");
}
/**
* Set the app's locale to the one specified by the given String.
*
* @param context
* @param localeSpec a locale specification as used for Android resources (NOTE: does not
* support country and variant codes so far); the special string "system" sets
* the locale to the locale specified in system settings
* @return
*/
public static Context setLocale(Context context, String localeSpec) {
Locale locale;
if (localeSpec.equals("system")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
locale = Resources.getSystem().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
locale = Resources.getSystem().getConfiguration().locale;
}
} else {
locale = new Locale(localeSpec);
}
Locale.setDefault(locale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return updateResources(context, locale);
} else {
return updateResourcesLegacy(context, locale);
}
}
@TargetApi(Build.VERSION_CODES.N)
private static Context updateResources(Context context, Locale locale) {
Configuration configuration = context.getResources().getConfiguration();
configuration.setLocale(locale);
configuration.setLayoutDirection(locale);
return context.createConfigurationContext(configuration);
}
@SuppressWarnings("deprecation")
private static Context updateResourcesLegacy(Context context, Locale locale) {
Resources resources = context.getResources();
Configuration configuration = resources.getConfiguration();
configuration.locale = locale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
configuration.setLayoutDirection(locale);
}
resources.updateConfiguration(configuration, resources.getDisplayMetrics());
return context;
}
}
创建SettingsFragment
如下所示:
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceFragment;
import android.preference.PreferenceManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import mypackage.LocaleHelper;
import mypackage.R;
/**
* Fragment containing the app's main settings.
*/
public class SettingsFragment extends PreferenceFragment implements SharedPreferences.OnSharedPreferenceChangeListener {
public static final String KEY_PREF_LANGUAGE = "pref_key_language";
public SettingsFragment() {
// Required empty public constructor
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_settings, container, false);
return view;
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
switch (key) {
case KEY_PREF_LANGUAGE:
LocaleHelper.setLocale(getContext(), PreferenceManager.getDefaultSharedPreferences(getContext()).getString(key, ""));
getActivity().recreate(); // necessary here because this Activity is currently running and thus a recreate() in onResume() would be too late
break;
}
}
@Override
public void onResume() {
super.onResume();
// documentation requires that a reference to the listener is kept as long as it may be called, which is the case as it can only be called from this Fragment
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
}
按以下方式创建一个资源locales.xml
,列出所有具有可用翻译的语言环境(语言环境代码列表):
<!-- Lists available locales used for setting the locale manually.
For now only language codes (locale codes without country and variant) are supported.
Has to be in sync with "settings_language_values" in strings.xml (the entries must correspond).
-->
<resources>
<string name="system_locale" translatable="false">system</string>
<string name="default_locale" translatable="false"></string>
<string-array name="locales">
<item>@string/system_locale</item> <!-- system setting -->
<item>@string/default_locale</item> <!-- default locale -->
<item>de</item>
</string-array>
</resources>
在您的PreferenceScreen
中,您可以使用以下部分让用户选择可用的语言:
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="@string/preferences_category_general">
<ListPreference
android:key="pref_key_language"
android:title="@string/preferences_language"
android:dialogTitle="@string/preferences_language"
android:entries="@array/settings_language_values"
android:entryValues="@array/locales"
android:defaultValue="@string/system_locale"
android:summary="%s">
</ListPreference>
</PreferenceCategory>
</PreferenceScreen>
它使用以下字符串strings.xml
:
<string name="preferences_category_general">General</string>
<string name="preferences_language">Language</string>
<!-- NOTE: Has to correspond to array "locales" in locales.xml (elements in same orderwith) -->
<string-array name="settings_language_values">
<item>Default (System setting)</item>
<item>English</item>
<item>German</item>
</string-array>
现在设置每个活动以使用自定义区域设置。实现这一点的最简单方法是使用以下代码为所有活动创建一个通用基类(其中重要代码位于attachBaseContext(Context base)
和中onResume()
):
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import mypackage.LocaleHelper;
import mypackage.R;
/**
* {@link AppCompatActivity} with main menu in the action bar. Automatically recreates
* the activity when the locale has changed.
*/
public class MenuAppCompatActivity extends AppCompatActivity {
private String initialLocale;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initialLocale = LocaleHelper.getPersistedLocale(this);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_settings:
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(LocaleHelper.onAttach(base));
}
@Override
protected void onResume() {
super.onResume();
if (initialLocale != null && !initialLocale.equals(LocaleHelper.getPersistedLocale(this))) {
recreate();
}
}
}
它的作用是
attachBaseContext(Context base)
以使用以前坚持的语言环境LocaleHelper
重新创建 Activity 不会更新 ActionBar 的标题(如已在此处观察到的:https ://github.com/gunhansancar/ChangeLanguageExample/issues/1 )。
setTitle(R.string.mytitle)
的方法中简单地实现。onCreate()
它允许用户选择系统默认语言环境,以及应用程序的默认语言环境(可以命名,在本例中为“英语”)。
目前仅支持语言代码,不fr-rCA
支持地区(国家)和变体代码(如 )。为了支持完整的语言环境规范,可以使用类似于Android-Languages 库中的解析器(支持区域但不支持变体代码)。
@SuppressWarnings("deprecation")
public static void forceLocale(Context context, String localeCode) {
String localeCodeLowerCase = localeCode.toLowerCase();
Resources resources = context.getApplicationContext().getResources();
Configuration overrideConfiguration = resources.getConfiguration();
Locale overrideLocale = new Locale(localeCodeLowerCase);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
overrideConfiguration.setLocale(overrideLocale);
} else {
overrideConfiguration.locale = overrideLocale;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
context.getApplicationContext().createConfigurationContext(overrideConfiguration);
} else {
resources.updateConfiguration(overrideConfiguration, null);
}
}
只需使用此辅助方法来强制特定的语言环境。
UDPATE 2017 年 8 月 22 日。更好地使用这种方法。
有一个超级简单的方法。
在 BaseActivity、Activity 或 Fragment 中覆盖 attachBaseContext
override fun attachBaseContext(context: Context) {
super.attachBaseContext(context.changeLocale("tr"))
}
延期
fun Context.changeLocale(language:String): Context {
val locale = Locale(language)
Locale.setDefault(locale)
val config = this.resources.configuration
config.setLocale(locale)
return createConfigurationContext(config)
}
使用以下方法添加辅助类:
public class LanguageHelper {
public static final void setAppLocale(String language, Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
Resources resources = activity.getResources();
Configuration configuration = resources.getConfiguration();
configuration.setLocale(new Locale(language));
activity.getApplicationContext().createConfigurationContext(configuration);
} else {
Locale locale = new Locale(language);
Locale.setDefault(locale);
Configuration config = activity.getResources().getConfiguration();
config.locale = locale;
activity.getResources().updateConfiguration(config,
activity.getResources().getDisplayMetrics());
}
}
}
并在您的启动活动中调用它,例如MainActivity.java
:
public void onCreate(Bundle savedInstanceState) {
...
LanguageHelper.setAppLocale("fa", this);
...
}
适用于 API16 到 API28 只需将此方法放在以下位置:
Context newContext = context;
Locale locale = new Locale(languageCode);
Locale.setDefault(locale);
Resources resources = context.getResources();
Configuration config = new Configuration(resources.getConfiguration());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
config.setLocale(locale);
newContext = context.createConfigurationContext(config);
} else {
config.locale = locale;
resources.updateConfiguration(config, resources.getDisplayMetrics());
}
return newContext;
使用以下方法在您的所有活动中插入此代码:
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(localeUpdateResources(base, "<-- language code -->"));
}
或在需要新上下文的片段、适配器等上调用 localeUpdateResources。
致谢:雅罗斯拉夫·别列赞斯基
我发现这个androidx.appcompat:appcompat:1.1.0
错误也可以通过简单地调用来getResources()
修复applyOverrideConfiguration()
@Override public void
applyOverrideConfiguration(Configuration cfgOverride)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP &&
Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
// add this to fix androidx.appcompat:appcompat 1.1.0 bug
// which happens on Android 6.x ~ 7.x
getResources();
}
super.applyOverrideConfiguration(cfgOverride);
}
简单易行
Locale locale = new Locale("en", "US");
Resources res = getResources();
DisplayMetrics dm = res.getDisplayMetrics();
Configuration conf = res.getConfiguration();
conf.locale = locale;
res.updateConfiguration(conf, dm);
其中“en”是语言代码,“US”是国家代码。
/**
* Requests the system to update the list of system locales.
* Note that the system looks halted for a while during the Locale migration,
* so the caller need to take care of it.
*/
public static void updateLocales(LocaleList locales) {
try {
final IActivityManager am = ActivityManager.getService();
final Configuration config = am.getConfiguration();
config.setLocales(locales);
config.userSetLocale = true;
am.updatePersistentConfiguration(config);
} catch (RemoteException e) {
// Intentionally left blank
}
}
对于那些尝试了一切但没有工作的人。请检查如果您设置darkmode
了AppCompatDelegate.setDefaultNightMode
且系统不暗,则在Andorid 7.0Configuration.setLocale
以上将无法工作。
在您的每个活动中添加此代码以解决此问题:
override fun applyOverrideConfiguration(overrideConfiguration: Configuration?) {
if (overrideConfiguration != null) {
val uiMode = overrideConfiguration.uiMode
overrideConfiguration.setTo(baseContext.resources.configuration)
overrideConfiguration.uiMode = uiMode
}
super.applyOverrideConfiguration(overrideConfiguration)
}
自 2020 年起,语言管理变得简单!你所要做的就是:
Activity.applyOverrideConfiguration
Locale.setDefault
您必须从活动构造函数中调用它们,因为您只能调用applyOverrideConfiguration
一次,并且系统会很早就调用它。
请注意应用程序包,当使用应用程序包时,Google 会自动按语言资源拆分您的 APK。在此处查看新的 API 和解决方法。
我创建了一个助手类来帮助你。在我的实现G.app
中是应用程序上下文。此外,我需要从应用程序上下文访问资源,因此我使用Res
该类,这是可选的,但我也提供了它的代码。
public BaseActivity(){
LanguageUtility.init(this);
}
public void changeLanguage(Local local){
// you must recreat your activity after you call this
LanguageUtillity.setDefaultLanguage(local, this);
}
public class LanguageUtility {
private static Configuration configuration;
public static void setDefaultLanguage(Locale locale, Context context) {
Locale.setDefault(locale);
context.getSharedPreferences("LocaleSettings", Context.MODE_PRIVATE)
.edit()
.putString("language", locale.getLanguage())
.putString("country", locale.getCountry())
.putString("variant", locale.getVariant())
.apply();
configuration = createConfiguration(context);
Res.updateContext();
}
/**
* Used to update your app context in case you cache it.
*/
public static Context createConfigurationContext(Context context) {
return context.createConfigurationContext(getConfiguration(context));
}
public static void init(Activity activity) {
activity.applyOverrideConfiguration(LanguageUtility.getConfiguration(G.app));
// you can't access sharedPrefferences from activity constructor
// with activity context, so I used the app context.
Locale.setDefault(getLocale(G.app));
}
@NotNull
private static Configuration getConfiguration(Context context) {
if (configuration == null) {
configuration = createConfiguration(context);
}
return configuration;
}
@NotNull
private static Configuration createConfiguration(Context context) {
Locale locale = getLocale(context);
Configuration configuration = new Configuration();
configuration.setLocale(locale);
LanguageUtility.configuration = configuration;
return configuration;
}
@NotNull
private static Locale getLocale(Context context) {
Locale aDefault = Locale.getDefault();
SharedPreferences preferences =
context.getSharedPreferences("LocaleSettings", Context.MODE_PRIVATE);
String language = preferences.getString("language", aDefault.getLanguage());
String country = preferences.getString("country", aDefault.getCountry());
String variant = preferences.getString("variant", aDefault.getVariant());
return new Locale(language, country, variant);
}
}
一个可选的 Res 类。
public class Res {
@SuppressLint("StaticFieldLeak")
public static Context appLocalContext = LanguageUtility.createConfigurationContext(G.app);
public static void updateContext() {
appLocalContext = LanguageUtility.createConfigurationContext(G.app);
}
public static String getString(@StringRes int id, Object... formatArgs) {
return appLocalContext.getResources().getString(id, formatArgs);
}
public static int getColor(@ColorRes int id) {
return G.app.getColor(id);
}
}
在 BaseActivity -> onCreate() 和 BaseFragment -> OnCreateView() 上调用此方法
在 API 22、23、24、25、26、27、28、29 上测试...最新版本
fun Context.updateLang() {
val resources = resources
val config = Configuration(resources.configuration)
config.setLocale(PreferenceManager(this).getAppLanguage()) // language from preference
val dm = resources.displayMetrics
createConfigurationContext(config)
resources.updateConfiguration(config, dm)
}
将此代码放入您的活动中
if (id==R.id.uz)
{
LocaleHelper.setLocale(MainActivity.this, mLanguageCode);
//It is required to recreate the activity to reflect the change in UI.
recreate();
return true;
}
if (id == R.id.ru) {
LocaleHelper.setLocale(MainActivity.this, mLanguageCode);
//It is required to recreate the activity to reflect the change in UI.
recreate();
}