web-dev-qa-db-fra.com

Android N change de langue par programmation

J'ai trouvé un bug vraiment bizarre qui est reproduit uniquement sur les appareils Android N.

En tournée de mon application, il y a une possibilité de changer de langue. Voici le code qui le change. 

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

Ce code fonctionne très bien dans l'activité de ma tournée (avec l'appel recreate()) mais dans toutes les activités suivantes, toutes les ressources String sont fausses. La rotation de l'écran le corrige. Que puis-je faire avec ce problème? Devrais-je changer les paramètres régionaux pour Android N différemment ou c'est juste un bogue du système?

P.S. Voici ce que j'ai trouvé. Au premier démarrage de MainActivity (qui est après ma visite), Locale.getDefault() est correct mais les ressources sont fausses. Mais dans d'autres activités, cela me donne une mauvaise localisation et de mauvaises ressources provenant de cette localisation. Après l'écran de rotation (ou peut-être un autre changement de configuration), Locale.getDefault() est correct.

42
Kuva

D'accord. Finalement, j'ai réussi à trouver une solution. 

Tout d’abord, vous devez savoir que dans 25 API Resources.updateConfiguration(...) est obsolète. Au lieu de cela, vous pouvez faire quelque chose comme ceci:

1) Vous devez créer votre propre ContextWrapper qui remplacera tous les paramètres de configuration dans baseContext. Par exemple, c’est le mien ContextWrapper qui modifie les paramètres régionaux correctement. Faites attention à la méthode 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) Voici ce que vous devriez faire dans votre 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);
}

Remarque:

N'oubliez pas de recréer votre activité si vous souhaitez modifier les paramètres régionaux dans votre App quelque part. Vous pouvez remplacer n'importe quelle configuration souhaitée à l'aide de cette solution.

84
Kuva

Inspiré par divers codes (nos frères de Stackoverflow (crier garçons)), j’avais produit une version beaucoup plus simple. L'extension ContextWrapper est inutile.

D'abord, supposons que vous avez 2 boutons pour 2 langues, EN et KH. Dans le onClick pour les boutons enregistrer le code de langue dans SharedPreferences, puis appelez la méthode activity recreate().

Exemple:

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

Créez ensuite une méthode statique qui retourne ContextWrapper, peut-être dans une classe Utils (c'est ce que j'ai fait, 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);
}

Enfin, chargez le code de langue de SharedPreferences dans la méthode ALL ACTIVITY'S attachBaseContext(Context newBase).

@Override
protected void attachBaseContext(Context newBase) {
    String lang_code = "en"; //load it from SharedPref
    Context context = Utils.changeLang(newBase, lang_code);
    super.attachBaseContext(context);
}

BONUS: Pour éviter les problèmes de Palm sur le clavier, j'ai créé une classe LangSupportBaseActivity qui étend la Activity et utilise le dernier bloc de code. Et j'ai toutes les autres activités étend LangSupportBaseActivity.

Exemple:

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
}
15
thyzz

Les réponses ci-dessus m'ont mis sur la bonne voie, mais ont laissé quelques problèmes

  1. Sur Android 7 et 9, je pourrais facilement passer à une langue autre que celle par défaut de l'application. Lorsque je suis revenu à la langue par défaut de l'application, la dernière langue sélectionnée était affichée, ce qui n'a rien de surprenant, car elle a remplacé la langue par défaut (même si, curieusement, ce n'était pas un problème sur Android 8!).
  2. Pour les langues RTL, les mises en page ne sont pas mises à jour vers RTL.

Pour réparer le premier élément, j'ai enregistré les paramètres régionaux par défaut au début de l'application. 

Remarque Si la langue par défaut est "en", les paramètres régionaux "enGB" ou "enUS" doivent correspondre à la langue par défaut (à moins que vous ne fournissiez des localisations distinctes). De même, dans l'exemple ci-dessous, si les paramètres régionaux du téléphone de l'utilisateur sont arLY (Arabic Libya), le defLanguage doit être "ar" et non "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;
}

Pour résoudre les problèmes de RTL, j'ai étendu AppCompatActivity conformément aux commentaires de Fragments dans cette 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);
    }
  }
}
0
QuantumTiger