Como no hay una respuesta completa para la forma actual de resolver este problema, trato de dar instrucciones para una solución completa. Comente si falta algo o podría hacerse mejor.
Información general
Primero, existen algunas bibliotecas que quieren resolver el problema pero todas parecen anticuadas o les faltan algunas características:
Además, creo que escribir una biblioteca podría no ser una forma buena / fácil de resolver este problema porque no hay mucho que hacer, y lo que hay que hacer es cambiar el código existente en lugar de usar algo completamente desacoplado. Por lo tanto, compuse las siguientes instrucciones que deberían estar completas.
Mi solución se basa principalmente en https://github.com/gunhansancar/ChangeLanguageExample (como ya está vinculado por localhost ). Es el mejor código que encontré para orientar. Algunas observaciones:
- Según sea necesario, proporciona diferentes implementaciones para cambiar la configuración regional para Android N (y superior) y posterior
- Utiliza un método
updateViews()
en cada Actividad para actualizar manualmente todas las cadenas después de cambiar la configuración regional (usando el habitual getString(id)
) que no es necesario en el enfoque que se muestra a continuación
- Solo admite idiomas y configuraciones regionales no completas (que también incluyen códigos de región (país) y variantes)
Lo cambié un poco, desacoplando la parte que persiste en la configuración regional elegida (ya que uno podría querer hacerlo por separado, como se sugiere a continuación).
Solución
La solución consta de los siguientes dos pasos:
- Cambiar permanentemente la configuración regional que utilizará la aplicación
- Haga que la aplicación use el conjunto de configuraciones regionales personalizadas, sin reiniciar
Paso 1: cambiar la configuración regional
Use la clase LocaleHelper
, basada en LocaleHelper de gunhansancar :
- Agregue un
ListPreference
en a PreferenceFragment
con los idiomas disponibles (debe mantenerse cuando los idiomas deben agregarse más adelante)
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;
}
}
Crea un me SettingsFragment
gusta de la siguiente manera:
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);
}
}
Cree un recurso que locales.xml
enumere todas las configuraciones regionales con las traducciones disponibles de la siguiente manera ( lista de códigos locales ):
<!-- 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>
En su PreferenceScreen
puede usar la siguiente sección para permitir al usuario seleccionar los idiomas disponibles:
<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>
que utiliza las siguientes cadenas de 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>
Paso 2: haga que la aplicación use la configuración regional personalizada
Ahora configure cada actividad para usar el conjunto de configuraciones regionales personalizadas. La forma más fácil de lograr esto es tener una clase base común para todas las actividades con el siguiente código (donde está el código importante attachBaseContext(Context base)
y 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();
}
}
}
Lo que hace es
- Anular
attachBaseContext(Context base)
para usar la configuración regional que anteriormente persistía conLocaleHelper
- Detecte un cambio de la configuración regional y vuelva a crear la Actividad para actualizar sus cadenas.
Notas sobre esta solución
Volver a crear una actividad no actualiza el título de ActionBar (como ya se observó aquí: https://github.com/gunhansancar/ChangeLanguageExample/issues/1 ).
- Esto se puede lograr simplemente teniendo un
setTitle(R.string.mytitle)
en el onCreate()
método de cada actividad.
Permite al usuario elegir la configuración regional predeterminada del sistema, así como la configuración regional predeterminada de la aplicación (que puede denominarse, en este caso, "inglés").
fr-rCA
Hasta ahora, solo se admiten códigos de idioma, ninguna región (país) y códigos de variantes (como ). Para admitir las especificaciones locales completas, se puede utilizar un analizador similar al de la biblioteca de idiomas de Android (que admite regiones pero no códigos de variantes).
- Si alguien encuentra o ha escrito un buen analizador, agregue un comentario para que pueda incluirlo en la solución.