web-dev-qa-db-ja.com

Android Nプログラムで言語を変更

Android Nデバイスでのみ再現される本当に奇妙なバグを見つけました。

私のアプリのツアーでは、言語を変更する可能性があります。変更するコードは次のとおりです。

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

このコードは、ツアーのアクティビティ(recreate()呼び出し)でうまく機能しますが、次のすべてのアクティビティでは、すべての文字列リソースが間違っています。画面の回転により修正されます。この問題で何ができますか? Android Nのロケールを変更する必要がありますか、それとも単なるシステムバグですか?

追伸ここに私が見つけたものがあります。 MainActivityの最初の開始時(ツアーの後)Locale.getDefault()は正しいが、リソースが間違っている。しかし、他のアクティビティでは、このロケールから間違ったロケールと誤ったリソースを取得します。回転画面(またはおそらく他の構成変更)Locale.getDefault()が正しい後。

45
Kuva

OK。最後に、解決策を見つけることができました。

最初に、25 API Resources.updateConfiguration(...)が非推奨になっていることを知っておく必要があります。そのため、代わりに次のようなことができます。

1)baseContextのすべての構成パラメーターをオーバーライドする独自のContextWrapperを作成する必要があります。たとえば、これはロケールを正しく変更する私のContextWrapperです。 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)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);
}

注:

アプリのロケールをどこかで変更する場合は、アクティビティを再作成することを忘れないでください。このソリューションを使用して、必要な構成をオーバーライドできます。

90
Kuva

さまざまなコード(つまり、Stackoverflowチーム(大声で叫ぶ))に触発されて、私はもっと単純なバージョンを作成しました。 ContextWrapper拡張子は不要です。

まず、ENとKHの2つの言語用の2つのボタンがあるとします。ボタンのonClickで、言語コードをSharedPreferencesに保存してから、アクティビティrecreate()メソッドを呼び出します。

例:

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

次に、おそらくUtilsクラスでContextWrapperを返す静的メソッドを作成します(これは私がやったことです、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);
}

最後に、言語コードをSharedPreferencesからALL ACTIVITY'SattachBaseContext(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);
}

ボーナス:キーボードのPalm汗を節約するために、LangSupportBaseActivityを拡張するActivityクラスを作成し、そこで最後のコードチャンクを使用します。そして、私は他のすべてのアクティビティがLangSupportBaseActivityを拡張しています。

例:

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

上記の答えは私を正しい方向に導きましたが、いくつかの問題を残しました

  1. Android 7および9では、アプリのデフォルト以外の言語に喜んで変更できました。アプリのデフォルト言語に戻すと、最後に選択した言語が表示されました-これはデフォルトをオーバーライドしているため、驚くことではありません(興味深いことに、これはAndroid 8では問題ではありませんでした!)。
  2. RTL言語では、レイアウトをRTLに更新しませんでした

最初の項目を修正するために、アプリの起動時にデフォルトのロケールを保存しました。

デフォルト言語が「en」に設定されている場合、「enGB」または「enUS」のロケールは両方ともデフォルトロケールに一致する必要があります(指定しない限り)彼らのために別々のローカライズ)。同様に、以下の例では、ユーザーの電話ロケールがarly(アラビア語リビア)の場合、defLanguageは「arLY」ではなく「ar」である必要があります。

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

RTLの問題を修正するために、この answer のフラグメントコメントに従ってAppCompatActivityを拡張しました。

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