Программный набор Locale
Мое приложение поддерживает 3 (скоро 4) языка. Поскольку несколько локалей довольно схожи, я хотел бы дать пользователю возможность изменить язык в моем приложении, например, итальянский человек может предпочесть испанский язык по-английски.
Есть ли способ для пользователя выбрать среди локалей, доступных для приложения, а затем изменить, какой язык используется? Я не вижу в этом проблемы установить локаль для каждого действия, поскольку это простая задача для выполнения в базовом классе.
Ответы
Ответ 1
Для людей, которые все еще ищут этот ответ, поскольку 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, вам нужно будет перезапустить ее, чтобы изменения вступили в силу.
РЕДАКТИРОВАТЬ 11 мая 2018
Начиная с поста @CookieMonster, у вас могут возникнуть проблемы с сохранением изменения локали в более высоких версиях API. Если это так, добавьте следующий код в базовое действие, чтобы обновлять языковой стандарт контекста при каждом создании действия:
@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)
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;
}
Если вы используете это, не забудьте сохранить язык в SharedPreferences, когда вы устанавливаете локаль с помощью setLocate(locale)
Ответ 2
Надеюсь, что эта помощь (in onResume):
Locale locale = new Locale("ru");
Locale.setDefault(locale);
Configuration config = getBaseContext().getResources().getConfiguration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
Ответ 3
У меня возникла проблема с программным обеспечением языкового программирования с помощью устройств
который имеет ОС Android 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
.
Ответ 4
Поскольку для текущего способа решения этой проблемы нет ответа, я пытаюсь дать инструкции для полного решения. Прошу прокомментировать, если что-то отсутствует или может быть сделано лучше.
Главная Информация
Во-первых, существуют некоторые библиотеки, которые хотят решить проблему, но все они устарели или отсутствуют некоторые функции:
Далее я думаю, что писать библиотеку не может быть хорошим/простым способом решить эту проблему, потому что делать нечего, и что нужно сделать, это изменить существующий код, чем использовать что-то полностью развязанное. Поэтому я составил следующие инструкции, которые должны быть полными.
Мое решение в основном основано на https://github.com/gunhansancar/ChangeLanguageExample (как уже было связано с localhost). Это лучший код, на который я нашел ориентацию. Некоторые замечания:
- При необходимости он предоставляет различные варианты изменения локали для Android N (и выше) и ниже
- Он использует метод
updateViews()
в каждом Activity для ручного обновления всех строк после изменения языкового стандарта (используя обычный getString(id)
), который не требуется в подходе, показанном ниже - Он поддерживает только языки, а не полные локали (которые также включают регионы (страны) и коды вариантов)
Я немного изменил его, разделив часть, которая сохраняется в выбранной локали (как можно было бы сделать это отдельно, как предложено ниже).
Решение
Решение состоит из следующих двух шагов:
- Постоянно изменять язык, используемый приложением
- Сделать приложение использовать пользовательский набор локалей, без перезапуска
Шаг 1: изменение языка
Используйте класс LocaleHelper
, основанный на gunhansancar LocaleHelper:
- Добавьте
ListPreference
в 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 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 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 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>
Шаг 2. Сделайте приложение использующим пользовательский язык.
Теперь настройте каждое действие, чтобы использовать пользовательский набор локалей. Самый простой способ добиться этого - иметь общий базовый класс для всех действий со следующим кодом (где важный код находится в 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
- Обнаружить изменение локали и воссоздать действие для обновления своих строк.
Примечания к этому решению
-
Воспроизведение активности не обновляет заголовок ActionBar (как уже отмечалось здесь: https://github.com/gunhansancar/ChangeLanguageExample/issues/1).
- Это может быть достигнуто просто с помощью
setTitle(R.string.mytitle)
в onCreate()
для каждого вида деятельности.
-
Он позволяет пользователю выбрать локаль по умолчанию для системы, а также стандартную локаль приложения (которую можно назвать, в данном случае "английский").
-
fr-rCA
поддерживаются только коды языков, fr-rCA
(страны) и коды вариантов (например, fr-rCA
). Для поддержки полных спецификаций языка может использоваться синтаксический анализатор, аналогичный тому, который используется в библиотеке Android-Языки (который поддерживает регион, но не коды вариантов).
- Если кто-то нашел или написал хороший парсер, добавьте комментарий, чтобы я мог включить его в решение.
Ответ 5
@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 22 AUG 2017. Лучше использовать этот подход.
Ответ 6
Добавьте вспомогательный класс со следующим методом:
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);
...
}
Ответ 7
простой и легкий
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" - код страны.
Ответ 8
/**
* 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
}
}
Ответ 9
Действительно от 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 для фрагментов, адаптеров и т.д., где вам нужен новый контекст.
Кредиты: Ярослав Березанский
Ответ 10
Представленные решения работают хорошо, но если вы планируете поддерживать дневную/ночную смену тем во время выполнения, вам также необходимо выполнить этот обходной путь.
Ответ 11
Добавить новую папку значений, как показано ниже.
![введите описание изображения здесь]()
значения-ru для России, а ценности - уз для узбекского языка. Вы должны установить языки. добавьте этот код в свой класс actvitiy
String languageToLoad = "uz"; // your language
Locale locale = new Locale(languageToLoad);
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
getBaseContext().getResources().updateConfiguration(config,
getBaseContext().getResources().getDisplayMetrics());
Ответ 12
Поместите этот код в свою деятельность
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();
}