Establecer la configuración regional mediante progtwigción

Mi aplicación admite 3 (pronto 4) idiomas. Como varias configuraciones regionales son bastante similares, me gustaría dar al usuario la opción de cambiar la configuración regional en mi aplicación, por ejemplo, una persona italiana podría preferir el español al inglés.

¿Hay alguna manera para que el usuario seleccione entre las configuraciones regionales disponibles para la aplicación y luego cambie la configuración regional que se usa? No veo como un problema establecer la configuración regional para cada actividad, ya que es una tarea sencilla de realizar en una clase base.

Para las personas que aún buscan esta respuesta, dado que configuration.locale está en desuso, ahora puedes usar desde la API 24:

 configuration.setLocale(locale); 

Tenga en cuenta que minSkdVersion para este método es API 17.

Código de ejemplo completo:

 @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); } } 

No olvide que, si cambia la configuración regional con una actividad en ejecución, deberá reiniciarla para que los cambios surtan efecto.

EDICION 11 DE MAYO DE 2018

A partir de la publicación de @ CookieMonster, es posible que tengas problemas para mantener el cambio de configuración regional en versiones de API más altas. De ser así, agregue el siguiente código a su actividad base para que actualice la configuración regional del contexto en cada creación de la actividad:

 @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; } 

Si usa esto, no olvide guardar el idioma en SharedPreferences cuando establezca la configuración regional con setLocate(locale)

Espero esta ayuda (en onResume):

 Locale locale = new Locale("ru"); Locale.setDefault(locale); Configuration config = getBaseContext().getResources().getConfiguration(); config.locale = locale; getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics()); 
 @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); } } 

Solo use este método de ayuda para forzar una configuración regional específica.

UDPATE 22 AUG 2017. Mejor utilizar este enfoque .

Tuve un problema al configurar la configuración regional mediante progtwigción con dispositivos con Android OS N y superior . Para mí, la solución fue escribir este código en mi actividad base:

(Si no tiene una actividad base, entonces debe hacer estos cambios en todas sus actividades)

 @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; } 

Tenga en cuenta que aquí no es suficiente llamar

 createConfigurationContext(configuration) 

también necesita obtener el contexto que devuelve este método y luego establecer este contexto en el método attachBaseContext .

Como no hay 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 obsoletas o faltan algunas características:

Además, creo que escribir una biblioteca puede no ser una buena / manera fácil de resolver este problema porque no hay mucho que hacer, y lo que se tiene que hacer es cambiar el código existente más que 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 (ya enlazado por localhost ). Es el mejor código que encontré para orientarme. Algunas observaciones:

  • Según sea necesario, proporciona diferentes implementaciones para cambiar la configuración regional para Android N (y superior) y debajo
  • Utiliza un método updateViews() en cada actividad para actualizar manualmente todas las cadenas de caracteres después de cambiar la configuración regional (utilizando la habitual getString(id) ) que no es necesaria en el enfoque que se muestra a continuación.
  • Solo admite idiomas y configuraciones no completas (que también incluyen códigos de región (país) y variante)

Lo cambié un poco, desacoplando la parte que persiste en la configuración regional elegida (como uno podría querer hacer eso por separado, como se sugiere a continuación).

Solución

La solución consta de los siguientes dos pasos:

  • Cambia permanentemente la configuración regional que utilizará la aplicación
  • Hacer que la aplicación use el conjunto de configuración regional personalizada, sin reiniciar

Paso 1: cambiar la configuración regional

Use la clase LocaleHelper , basada en LocaleHelper de gunhansancar :

  • Agregue una ListPreference en un PreferenceFragment con los idiomas disponibles (debe mantenerse cuando los idiomas se agreguen 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 SettingsFragment como el siguiente:

 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 locales.xml enumere todas las configuraciones regionales con traducciones disponibles de la siguiente manera ( lista de códigos de configuración regional ):

   system   @string/system_locale  @string/default_locale  de   

En su PreferenceScreen puede usar la siguiente sección para permitir que el usuario seleccione los idiomas disponibles:

       

que usa las siguientes cadenas de strings.xml :

 General Language   Default (System setting) English German  

Paso 2: hacer que la aplicación use la configuración regional personalizada

Ahora configure cada actividad para usar el conjunto de configuración regional personalizada. 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 el código importante está en 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

  • Reemplazar attachBaseContext(Context base) para usar la configuración regional previamente conservada con LocaleHelper
  • Detectar un cambio de la configuración regional y recrear la Actividad para actualizar sus cadenas

Notas sobre esta solución

  • La recreación de una actividad no actualiza el título de la barra de acciones (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 método onCreate() 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”).

  • Solo los códigos de idioma, sin región (país) y códigos de variantes (como fr-rCA ) son compatibles hasta el momento. Para admitir las especificaciones de configuración regional completa, se puede usar un analizador similar al de la biblioteca de idiomas de Android (que admite región 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.

Agregue una clase auxiliar con el siguiente método:

 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()); } } } 

Y llámalo en tu actividad de inicio, como MainActivity.java :

 public void onCreate(Bundle savedInstanceState) { ... LanguageHelper.setAppLocale("fa", this); ... } 

simple y fácil

 Locale locale = new Locale("en", "US"); Resources res = getResources(); DisplayMetrics dm = res.getDisplayMetrics(); Configuration conf = res.getConfiguration(); conf.locale = locale; res.updateConfiguration(conf, dm); 

donde “en” es el código de idioma y “US” es el código de país.

  /** * 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 } } 

Pon este código en tu actividad

  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(); }