java - programacion - Android N cambia el idioma mediante programación
set language programmatically android (3)
Encontré un error realmente extraño que se reproduce solo en dispositivos Android N.
En el recorrido de mi aplicación, existe la posibilidad de cambiar el idioma. Aquí está el código que lo cambia.
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());
}
Este código funciona muy bien en la actividad de mi recorrido (con la llamada
recreate()
) pero en todas las actividades siguientes todos los recursos de String están mal.
La rotación de la pantalla lo arregla.
¿Qué puedo hacer con este problema?
¿Debo cambiar la configuración regional para Android N de manera diferente o es solo un error del sistema?
PD: Esto es lo que encontré.
En el primer inicio de MainActivity (que es después de mi recorrido)
Locale.getDefault()
es correcto pero los recursos están mal.
Pero en otras actividades me da una configuración regional incorrecta y recursos incorrectos de esta configuración regional.
Después de la pantalla de rotación (o quizás algún otro cambio de configuración)
Locale.getDefault()
es correcto.
Inspirado por varios códigos (es decir, nuestro equipo de (gritar personas)), había producido una versión mucho más simple.
La extensión
ContextWrapper
es innecesaria.
Primero, digamos que tiene 2 botones para 2 idiomas, EN y KH.
En onClick para los botones, guarde el código de idioma en
SharedPreferences
, luego llame al método
recreate()
la actividad.
Ejemplo:
@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();
}
Luego cree un método estático que devuelva
ContextWrapper
, tal vez en una clase Utils (porque eso es lo que hice, 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);
}
Finalmente, cargue el código de idioma de
SharedPreferences
en el método
attachBaseContext(Context newBase)
ALL ACTIVITY
.
@Override
protected void attachBaseContext(Context newBase) {
String lang_code = "en"; //load it from SharedPref
Context context = Utils.changeLang(newBase, lang_code);
super.attachBaseContext(context);
}
BONIFICACIÓN: para ahorrar el sudor de la palma en el teclado, creé una clase
LangSupportBaseActivity
que extiende la
Activity
y utilizo el último fragmento de código allí.
Y tengo todas las demás actividades extiende
LangSupportBaseActivity
.
Ejemplo:
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
}
Las respuestas anteriores me pusieron en el camino correcto pero dejaron un par de problemas
- En Android 7 y 9, podría cambiar a cualquier idioma que no sea el predeterminado de la aplicación. Cuando volví al idioma predeterminado de la aplicación, mostró el último idioma seleccionado, lo que no es sorprendente, ya que esto ha anulado el predeterminado (aunque, curiosamente, ¡esto no fue un problema en Android 8!).
- Para los idiomas RTL, no actualizó los diseños a RTL
Para arreglar el primer elemento, almacené la configuración regional predeterminada al iniciar la aplicación.
Nota Si su idioma predeterminado está configurado en "en", las configuraciones regionales de "enGB" o "enUS" deben coincidir con la configuración regional predeterminada (a menos que proporcione localizaciones separadas para ellas). Del mismo modo, en el siguiente ejemplo, si la configuración regional del teléfono del usuario es arLY (Libia árabe), entonces el defLanguage debe ser "ar", no "arLY"
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;
}
Para solucionar los problemas de RTL, extendí AppCompatActivity según los comentarios de Fragments en esta answer
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);
}
}
}
Okay. Finalmente logré encontrar una solución.
Primero debe saber que en 25 API
Resources.updateConfiguration(...)
está en desuso.
Entonces, en cambio, puedes hacer algo como esto:
1) Necesita crear su propio ContextWrapper que anulará todos los parámetros de configuración en baseContext.
Por ejemplo, este es ContextWrapper mío que cambia la configuración regional correctamente.
Presta atención al método
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) Esto es lo que debe hacer en su 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);
}
Nota:
Recuerde recrear su actividad si desea cambiar la configuración regional en su aplicación en algún lugar. Puede anular cualquier configuración que desee con esta solución.