Zmiana ustawień regionalnych w samej aplikacji

197

Moi użytkownicy mogą zmieniać ustawienia regionalne w aplikacji (mogą chcieć zachować ustawienia telefonu w języku angielskim, ale czytać zawartość mojej aplikacji w języku francuskim, holenderskim lub innym języku ...)

Dlaczego działa to doskonale w wersji 1.5 / 1.6, ale NIE w wersji 2.0?

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch(item.getItemId()) {
    case 201:
        Locale locale2 = new Locale("fr"); 
        Locale.setDefault(locale2);
        Configuration config2 = new Configuration();
        config2.locale = locale2;
        getBaseContext().getResources().updateConfiguration(
            config2, getBaseContext().getResources().getDisplayMetrics());
        // loading data ...
        refresh();
        // refresh the tabs and their content
        refresh_Tab ();   
     break;
     case 201: etc...

Problem polega na tym, że MENU „kurczy się” coraz bardziej za każdym razem, gdy użytkownik przechodzi przez linie kodu powyżej ...

To menu się kurczy:

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    menu.add(0, 100, 1, "REFRESH").setIcon(android.R.drawable.ic_menu_compass);
    SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
        langMenu.add(1, 201, 0, "Nederlands");
        langMenu.add(1, 202, 0, "Français");
    menu.add(0, 250, 4, R.string.OptionMenu2).setIcon(android.R.drawable.ic_menu_send);
    menu.add(0, 300, 5, R.string.OptionMenu3).setIcon(android.R.drawable.ic_menu_preferences);
    menu.add(0, 350, 3, R.string.OptionMenu4).setIcon(android.R.drawable.ic_menu_more);
    menu.add(0, 400, 6, "Exit").setIcon(android.R.drawable.ic_menu_delete);

    return super.onCreateOptionsMenu(menu);
}

Co powinienem zrobić na poziomie API 5, aby znów działało?

TU JEST PEŁNY KOD, JEŚLI CHCESZ TESTOWAĆ TO:

import java.util.Locale;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SubMenu;
import android.widget.Toast;

public class Main extends Activity {
    /** Called when the activity is first created. */


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {

        SubMenu langMenu = menu.addSubMenu(0, 200, 2, "NL-FR").setIcon(android.R.drawable.ic_menu_rotate);
            langMenu.add(1, 201, 0, "Nederlands");
            langMenu.add(1, 202, 0, "Français");

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch(item.getItemId()){

        case 201:

            Locale locale = new Locale("nl"); 
            Locale.setDefault(locale);
            Configuration config = new Configuration();
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
            Toast.makeText(this, "Locale in Nederlands !", Toast.LENGTH_LONG).show();
            break;

        case 202:

            Locale locale2 = new Locale("fr"); 
            Locale.setDefault(locale2);
            Configuration config2 = new Configuration();
            config2.locale = locale2;
            getBaseContext().getResources().updateConfiguration(config2, getBaseContext().getResources().getDisplayMetrics());

            Toast.makeText(this, "Locale en Français !", Toast.LENGTH_LONG).show();
            break;  

        }
        return super.onOptionsItemSelected(item);
    }
}

I TU JEST MANIFEST:

<?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.cousinHub.ChangeLocale"
          android:versionCode="1"
          android:versionName="1.0">
        <application android:icon="@drawable/icon" android:label="@string/app_name">
            <activity android:name=".Main"
                      android:label="@string/app_name">
                <intent-filter>
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
                </intent-filter>
            </activity>
        </application>
        <uses-sdk android:minSdkVersion="3" /> 
    </manifest>

TO ZNALEZIONO:

<uses-sdk android:minSdkVersion="5" />

=> TO DZIAŁA WŁAŚNIE DOBRA ...

<uses-sdk android:minSdkVersion="3" />

=> Menu kurczy się przy każdej zmianie ustawień regionalnych !!!

ponieważ chcę, aby moja aplikacja była dostępna dla użytkowników w wersji 1.5, co powinienem zrobić?

Hubert
źródło
Co rozumiesz przez „kurczenie się”?
CommonsWare
za każdym razem staje się coraz mniejszy. powinienem użyć czegoś innego niż getBaseContext?
Hubert
Może to nie te linie tworzą problem, ponieważ kiedy robię bardzo podstawową aplikację, zmieniając tylko ustawienia regionalne, nie mam tego samego „zmniejszania się menu”. Pomyślałem, że może dlatego, że moja Aktywność była w rzeczywistości TabActivity, ale mimo to nie mogę odtworzyć problemu. Będę musiał zbadać dokładną przyczynę tego błędu ... nie szukaj dalej. Opublikuję odpowiedź tutaj, kiedy ją znajdę. Pozdrawiam, H.
Hubert
Zredagowałem swój pierwszy post, dając przykład, jak stworzyć problem. Zauważyłem w rzeczywistości, że kiedy zmieniam wiersz <uses-sdk android: minSdkVersion = "5" /> na "3" ... rzeczywiście pojawia się problem!
Hubert
1
Możesz użyć następującej biblioteki, która zawiera listę języków, preferencje ekranu ustawień i zastępuje język w aplikacji: github.com/delight-im/Android-Languages
caw

Odpowiedzi:

151

W oryginalnym pytaniu nie chodzi o samą lokalizację, wszystkie inne pytania dotyczące tej lokalizacji odnoszą się do tego pytania. Dlatego chciałem tutaj wyjaśnić tę kwestię. Użyłem tego pytania jako punktu wyjścia dla mojego własnego kodu zmiany ustawień regionalnych i odkryłem, że metoda nie jest dokładnie poprawna. Działa, ale tylko do czasu zmiany konfiguracji (np. Obrót ekranu) i tylko w tej konkretnej czynności. Przez jakiś czas bawiłem się kodem i skończyłem na następującym podejściu:

Rozszerzyłem aplikację android.app.Application i dodałem następujący kod:

public class MyApplication extends Application
{
    private Locale locale = null;

    @Override
    public void onConfigurationChanged(Configuration newConfig)
    {
        super.onConfigurationChanged(newConfig);
        if (locale != null)
        {
            newConfig.locale = locale;
            Locale.setDefault(locale);
            getBaseContext().getResources().updateConfiguration(newConfig, getBaseContext().getResources().getDisplayMetrics());
        }
    }

    @Override
    public void onCreate()
    {
        super.onCreate();

        SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);

        Configuration config = getBaseContext().getResources().getConfiguration();

        String lang = settings.getString(getString(R.string.pref_locale), "");
        if (! "".equals(lang) && ! config.locale.getLanguage().equals(lang))
        {
            locale = new Locale(lang);
            Locale.setDefault(locale);
            config.locale = locale;
            getBaseContext().getResources().updateConfiguration(config, getBaseContext().getResources().getDisplayMetrics());
        }
    }
}

Ten kod zapewnia, że ​​każde działanie będzie mieć ustawione niestandardowe ustawienia narodowe i nie zostanie zresetowane po rotacji i innych zdarzeniach.

Spędziłem również dużo czasu, próbując wprowadzić zmiany preferencji, aby zostały zastosowane natychmiast, ale nie powiodło się: język zmienił się poprawnie po ponownym uruchomieniu działania, ale formaty liczb i inne właściwości ustawień narodowych nie zostały zastosowane aż do pełnego ponownego uruchomienia aplikacji.

Zmiany w AndroidManifest.xml

Nie zapomnij dodać android:configChanges="layoutDirection|locale"do każdej aktywności w AndroidManifest, a także android:name=".MyApplication"do <application>elementu.

Andrey Novikov
źródło
1
Czy preferujesz aktywność wywoływaną z głównej aktywności? możesz umieścić to w życiorysie ... @Override chronione void onResume () {if (! (PreferenceManager.getDefaultSharedPreferences (getApplicationContext ()). getString ("listLanguage", "en") .equals (langPreference))) { odświeżać(); } super.onResume (); } private void refresh () {finish (); Intent myIntent = new Intent (Main.this, Main.class); startActivity (myIntent); }
trgraglia
1
Właśnie tego potrzebuję, zwykłem wywoływać updateConfiguration () w MainActivity, ale menu nie aktualizują się po zakończeniu aplikacji. Twoje rozwiązanie jest słuszne. Jeśli chcesz odświeżyć ciągi od razu, myślę, że nadal musisz ręcznie zresetować ciągi. (np. zresetuj etykietę przycisku lub utwórz ponownie menu).
szmaragd
Czy to zakłóca ustawienia regionalne systemu Android?
Kostadin
17
Pamiętaj, że nigdy nie wolno modyfikować obiektu konfiguracji, który system Android udostępnia ( configi newConfig). config = new Configuration(config);Najpierw powinieneś wykonać kopię . Właśnie spędziłem godzinę debugując ten kod, zanim odkryłem problem (powodował niekończące się flashowanie działania).
imgx64
1
Ta technika ma usterki w Androidzie 7+ podczas uruchamiania aplikacji od zimnego startu, przy systemowym rozmiarze czcionki ustawionym na duży. Google zmieniło sposób updateConfiguration()działania, więc czasami Twoja aktywność będzie wyświetlana w 2 różnych językach (szczególnie w oknach dialogowych). Więcej informacji: stackoverflow.com/questions/39705739/…
Mr-IDE
61

Po dobrze przespanej nocy znalazłem odpowiedź w Internecie (proste wyszukiwanie w Google w następującym wierszu „ getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());”), oto ona:

link text => ten link pokazuje także, screenshotsco się dzieje!

Problemem była gęstość , musiałem to mieć w pliku AndroidManifest.xml

<supports-screens
android:smallScreens="true"
android:normalScreens="true"
android:largeScreens="true"
android:anyDensity="true"
/>

Najważniejszy jest android: anyDensity = "true" .

Nie zapomnij dodać następujących elementów AndroidManifest.xmldo każdego działania (dla Androida 4.1 i niższych):

android:configChanges="locale"

Ta wersja jest potrzebna, gdy budujesz dla Androida 4.2 (API poziom 17) wyjaśnienie tutaj :

android:configChanges="locale|layoutDirection"
Hubert
źródło
3
Miałem Androida: anyDensity = "false" dzięki radom Google (Strategie for Legacy Applications - punkt 9: developer.android.com/intl/fr/guide/practices/… ). Bądź więc ostrożny!
Hubert
2
Teksty na przyciskach nie zmieniają się po zmianie ustawień regionalnych. Zastanawiam się, czy istnieje jakieś rozwiązanie, aby odświeżyć wszystkie widżety zawierające zasoby łańcuchowe. Teraz przerysowuję tekst za pomocą setText (getString (R.string.button_label)) w OnRestart (). (Oczywiście zmienia się po ponownym uruchomieniu aplikacji.)
emeraldhieu
możesz odtworzyć aktywność odtwarzaniaactivity.recreate()
Do Kra
59

W Androidzie M najlepsze rozwiązanie nie działa. Napisałem klasę pomocnika, aby naprawić to, co powinieneś wywołać z klasy aplikacji i wszystkich działań (sugerowałbym utworzenie BaseActivity, a następnie sprawienie, by wszystkie działania dziedziczyły po niej).

Uwaga : Będzie to również obsługiwać poprawnie kierunek układu RTL.

Klasa pomocnicza:

public class LocaleUtils {

    private static Locale sLocale;

    public static void setLocale(Locale locale) {
        sLocale = locale;
        if(sLocale != null) {
            Locale.setDefault(sLocale);
        }
    }

    public static void updateConfig(ContextThemeWrapper wrapper) {
        if(sLocale != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            Configuration configuration = new Configuration();
            configuration.setLocale(sLocale);
            wrapper.applyOverrideConfiguration(configuration);
        }
    }

    public static void updateConfig(Application app, Configuration configuration) {
        if (sLocale != null && Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            //Wrapping the configuration to avoid Activity endless loop
            Configuration config = new Configuration(configuration);
            // We must use the now-deprecated config.locale and res.updateConfiguration here,
            // because the replacements aren't available till API level 24 and 17 respectively.
            config.locale = sLocale;
            Resources res = app.getBaseContext().getResources();
            res.updateConfiguration(config, res.getDisplayMetrics());
        }
    }
}

Podanie:

public class App extends Application {
    public void onCreate(){
        super.onCreate();

        LocaleUtils.setLocale(new Locale("iw"));
        LocaleUtils.updateConfig(this, getBaseContext().getResources().getConfiguration());
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        LocaleUtils.updateConfig(this, newConfig);
    }
}

BaseActivity:

public class BaseActivity extends Activity {
    public BaseActivity() {
        LocaleUtils.updateConfig(this);
    }
}
Roberto B.
źródło
11
Należy zaakceptować odpowiedź. Upewnij się, że updateConfig nie powinien wywoływać z konstruktora, a nie z klasy onCreate klasy aktywności, popełniłem ten błąd ..
Hitesh Sahu
1
zależy to od tego, jak zaimplementowałeś to w swoim programie. Jeśli zastąpiłeś onConfigurationChanged (), nie musisz wywoływać .updateConfig () i dlatego otrzymujesz błąd, ponieważ jest on wywoływany dwukrotnie. jeśli mówisz, że tylko wywoływanie .setLocale () nie działa, to dlatego, że musisz odświeżyć swój interfejs użytkownika, albo przez wywołanie metody createate () dla działania lub własnej metody w działaniach / fragmentach (wolę to pierwsze, nawet jeśli nie jest płynna, ale nigdy nie będziesz się martwić o problemy)
RJFares
3
// import android.support.v7.view.ContextThemeWrapper; import android.view.ContextThemeWrapper; // poprawna przestrzeń nazw
Mehdi Rostami
1
Czy BaseActivity()konstruktor nie powinien zadzwonić super()?
LarsH,
1
Czy to rozwiązanie domyślnie obejmuje wprowadzenie <application android:name=".App">manifestu? Co powiesz na dodanie android:configChanges="layoutDirection|locale"do każdej aktywności w manifeście?
LarsH,
10

To jest mój komentarz do odpowiedzi Andreya, ale nie mogę dołączyć kodu w komentarzach.

Czy preferujesz aktywność wywoływaną z głównej aktywności? możesz umieścić to w CV ...

@Override
protected void onResume() {
    if (!(PreferenceManager.getDefaultSharedPreferences(
            getApplicationContext()).getString("listLanguage", "en")
            .equals(langPreference))) {
        refresh();
    }
    super.onResume();
}

private void refresh() {
    finish();
    Intent myIntent = new Intent(Main.this, Main.class);
    startActivity(myIntent);
}
trgraglia
źródło
2
Masz na myśli, że jeśli chcę zmienić język w czasie wykonywania, to muszę zakończyć tę aktywność i uruchomić ją ponownie, aby uzyskać zmiany? czy istnieje jakikolwiek inny sposób, aby to zmienić, ponieważ nie jest dobrze za każdym razem kończyć działanie i zaczynać od nowa.
Shreyash Mahajan
1
lepiej zrobićactivity.recreate()
Do Kra
@trgradlia, chcę użyć powyższego kodu. Powiedz mi, co to jest langPreference? Myślę, że porównujemy ostatnio zmieniony język i poprzedni język.
Mahen
1
Aktywność ma recreate()metodę, więc użyj recreate()zamiast refresh()w kodzie. Spowoduje to zapisanie 3 wiersza kodu w refresh()metodzie
Inzimam Tariq IT,
1
@InzimamTariqIT, dziękuję za wskazówkę ... Odpowiedź ma prawie 7 lat, więc zostawię ją taką, jaka jest, i ludzie mogą znaleźć twoje ulepszenie tutaj w komentarzach.
trgraglia
6

Nie mogłem użyć Androida: anyDensity = "true", ponieważ obiekty w mojej grze byłyby ustawione zupełnie inaczej ... wygląda na to, że to działa:

// tworzenie ustawień regionalnych
Ustawienia regionalne 2 = nowe ustawienia regionalne (loc); 
Locale.setDefault (locale2);
Konfiguracja config2 = nowa konfiguracja ();
config2.locale = locale2;

// aktualizacja ustawień regionalnych
mContext.getResources (). updateConfiguration (config2, null);
nikib3ro
źródło
To rozwiązanie jest dla mnie najlepsze, właśnie tego code Locale.setDefault(mLocale); Configuration config = getBaseContext().getResources().getConfiguration(); if (!config.locale.equals(mLocale)) { config.locale = mLocale; getBaseContext().getResources().updateConfiguration(config, null); }
użyłem
1

Jeśli chcesz wpłynąć na opcje menu umożliwiające natychmiastową zmianę ustawień regionalnych, musisz to zrobić.

//onCreate method calls only once when menu is called first time.
public boolean onCreateOptionsMenu(Menu menu) {
    super.onCreateOptionsMenu(menu);
    //1.Here you can add your  locale settings . 
    //2.Your menu declaration.
}
//This method is called when your menu is opend to again....
@Override
public boolean onMenuOpened(int featureId, Menu menu) {
    menu.clear();
    onCreateOptionsMenu(menu);
    return super.onMenuOpened(featureId, menu);
}
Ramesh Akula
źródło
Ale czy to rozwiązanie nie usuwa menu w każdym otwartym menu? Przypuszczam, że onMenuOpenednależy wywołać tylko raz, po zmianie ustawień regionalnych.
trante