Изменение локали в самом приложении

197

Мои пользователи могут изменить языковой стандарт в приложении (они могут захотеть сохранить настройки своего телефона на английском, но читать содержимое моего приложения на французском, голландском или любом другом языке ...)

Почему это работает прекрасно в 1.5 / 1.6, а НЕ в 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...

Проблема в том, что МЕНЮ «сжимается» все больше и больше каждый раз, когда пользователь проходит строки кода выше ...

Это меню, которое сокращается:

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

Что я должен сделать в API Level 5, чтобы эта работа снова?

Вот полный код, если вы хотите проверить это:

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

И вот это манифест:

<?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>

Это то, что я нашел:

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

=> Это работает просто отлично

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

=> Меню сжимается каждый раз, когда вы меняете локаль !!!

как я хочу, чтобы мое приложение было доступно для пользователей версии 1.5, что мне делать ??

Хьюберт
источник
Что вы подразумеваете под "сжимается"?
CommonsWare
он становится все меньше и меньше с каждым разом. Должен ли я использовать что-то еще, кроме getBaseContext?
Юбер
Может быть, не эти строки создают проблему, потому что, когда я делаю очень простое приложение, вносящее только изменения в Locale, у меня нет такого же «сокращения меню». Я подумал, что это может быть потому, что моя активность была на самом деле TabActivity, но даже с этим я не могу воссоздать проблему. Мне придется дополнительно выяснить, какова точная причина этой ошибки ... не ищите дальше. Я отправлю ответ здесь, когда найду его. Приветствия, Х.
Юбер
Я отредактировал свой первый пост, давая пример того, как создать проблему. И я заметил, что на самом деле, когда я меняю строку <using-sdk android: minSdkVersion = "5" /> на "3" ... действительно проблема возникает!
Юбер
1
Вы можете использовать следующую библиотеку, которая предоставляет список языков, предпочтения для экрана настроек и переопределяет язык в вашем приложении: github.com/delight-im/Android-Languages
caw

Ответы:

151

Исходный вопрос не совсем о самой локали, все остальные вопросы, связанные с локалью, относятся к этому. Вот почему я хотел уточнить эту проблему здесь. Я использовал этот вопрос в качестве отправной точки для своего собственного кода переключения локали и обнаружил, что метод не совсем правильный. Это работает, но только до какого-либо изменения конфигурации (например, поворот экрана) и только в этой конкретной деятельности. Некоторое время играя с кодом, я получил следующий подход:

Я расширил приложение android.app.Application и добавил следующий код:

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

Этот код гарантирует, что для каждого действия будет установлен пользовательский языковой стандарт, и он не будет сброшен при повороте и других событиях.

Я также потратил много времени, пытаясь сделать изменение предпочтений незамедлительным, но безуспешно: язык изменился правильно при перезапуске Activity, но числовые форматы и другие свойства локали не применялись до полного перезапуска приложения.

Изменения к AndroidManifest.xml

Не забудьте добавить android:configChanges="layoutDirection|locale"к каждому действию на AndroidManifest, а также android:name=".MyApplication"к <application>элементу.

Андрей Новиков
источник
1
Ваша предпочтительная деятельность вызывается из вашей основной деятельности? Вы можете поместить это в резюме ... @Override protected void onResume () {if (! (PreferenceManager.getDefaultSharedPreferences (getApplicationContext ()). getString ("listLanguage", "en") .equals (langPreference))) { обновить (); } super.onResume (); } private void refresh () {finish (); Intent myIntent = new Intent (Main.this, Main.class); startActivity (myIntent); }
trgraglia
1
Это на самом деле то, что мне нужно, я использовал для вызова updateConfiguration () в MainActivity, но меню не обновляется, когда приложение завершается, а затем запускается. Ваше решение правильно. Если вы хотите обновить строки немедленно, я думаю, что вы все равно должны сбросить строки вручную. (например, метка кнопки сброса или воссоздание меню).
Emeraldhieu
Это мешает локали системы Android?
Костадин
17
Обратите внимание, что вы никогда не должны изменять объект конфигурации, который Android предоставляет вам ( configи newConfig). Вы должны сделать копию с config = new Configuration(config);первым. Я просто потратил час на отладку этого кода, прежде чем обнаружил проблему (это вызывало бесконечную вспышку активности).
imgx64
1
Этот метод имеет глюки на Android 7+ при запуске приложения с «холодного старта», когда размер системного шрифта установлен на большой. Google изменил способ updateConfiguration()работы, поэтому иногда ваша активность будет отображаться на 2 разных языках (особенно в диалогах). Дополнительная информация: stackoverflow.com/questions/39705739/…
Mr-IDE
61

После хорошего сна я нашел ответ в Интернете (простой поиск Google в следующей строке " getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());"), вот он:

текст ссылки => эта ссылка также показывает, screenshotsчто происходит!

Плотность была проблема здесь , мне нужно было это в AndroidManifest.xml

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

Наиболее важным является android: anyDensity = "true" .

Не забудьте добавить следующее AndroidManifest.xmlдля каждого действия (для Android 4.1 и ниже):

android:configChanges="locale"

Эта версия необходима при сборке объяснения для Android 4.2 (уровень API 17) здесь :

android:configChanges="locale|layoutDirection"
Хьюберт
источник
3
У меня был android: anyDensity = "false" по совету Google (Стратегии для устаревших приложений - пункт 9: developer.android.com/intl/fr/guide/practices/… ). Будь осторожен тогда!
Юбер
2
Тексты на кнопках не меняются после смены локали. Интересно, есть ли решение обновить все виджеты, которые содержат строковые ресурсы. Теперь я перерисовываю текст с помощью setText (getString (R.string.button_label)) в OnRestart (). (Конечно, оно меняется после перезапуска приложения.)
emeraldhieu
Вы можете назвать воссоздание деятельностиactivity.recreate()
К кра
59

В Android M лучшее решение не будет работать. Я написал вспомогательный класс для исправления того, что вы должны вызывать из класса приложения и всех операций (я бы предложил создать BaseActivity, а затем сделать все действия наследуемыми от него.

Примечание : Это также будет правильно поддерживать направление расположения RTL.

Хелпер класс:

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

Заявка:

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);
    }
}
Роберто Б.
источник
11
Это должен быть принят ответ. Удостоверьтесь, что updateConfig должен вызываться из конструктора, а не из onCreate класса активности, я делал эту ошибку ..
Hitesh Sahu
1
это зависит от того, как вы реализовали это в своей программе. Если вы переопределили onConfigurationChanged (), вам не нужно вызывать .updateConfig (), и именно поэтому вы получаете ошибку, потому что она вызывается дважды. если вы говорите, что не работает только вызов .setLocale (), это потому, что вам нужно обновить свой пользовательский интерфейс, вызвав метод refreshate () для действия или свой собственный метод в действиях / фрагментах (я предпочитаю первый, даже если это не гладко, но вы никогда не будете беспокоиться о проблемах)
RJFares
3
// import android.support.v7.view.ContextThemeWrapper; import android.view.ContextThemeWrapper; // правильное пространство имен
Мехди Ростами,
1
Разве BaseActivity()конструктор не должен вызывать super()?
LarsH
1
Включает ли это решение неявно включение <application android:name=".App">манифеста? Как насчет добавления android:configChanges="layoutDirection|locale"к каждому действию в манифесте?
LarsH
10

Это мой комментарий к ответу Андрея, но я не могу включить код в комментарии.

Ваша предпочтительная деятельность вызывается из вашей основной деятельности? Вы могли бы поместить это в резюме ...

@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
источник
2
Вы имеете в виду, что если я хочу изменить язык во время выполнения, то мне нужно завершить это действие и снова запустить это действие, чтобы получить изменения? Есть ли другой способ изменить это, так как нехорошо каждый раз завершать упражнение и запускать его снова.
Шреяш Махаджан
1
лучше сделатьactivity.recreate()
Кра
@ trgradlia, я хочу использовать приведенный выше код. Скажите пожалуйста, что такое langPreference? Я думаю, что мы сравниваем недавно измененный язык и предыдущий язык.
Махен
1
У действия есть recreate()метод, поэтому используйте его recreate()вместо refresh()в вашем коде. Это сохранит ваши 3 строки кода в refresh()методе
Inzimam Tariq IT
1
@InzimamTariqIT, спасибо за совет ... Ответу почти 7 лет, поэтому я оставлю его как есть, и люди могут найти ваше улучшение здесь в комментариях.
trgraglia
6

Я не мог использовать android: anyDensity = "true", потому что объекты в моей игре были бы расположены совершенно по-другому ... кажется, это тоже помогает:

// создаем локаль
Locale locale2 = новая локаль (loc); 
Locale.setDefault (locale2);
Конфигурация config2 = новая конфигурация ();
config2.locale = locale2;

// обновляем локаль
mContext.getResources (). updateConfiguration (config2, null);
nikib3ro
источник
Это решение лучше для меня, только что использовал этоcode Locale.setDefault(mLocale); Configuration config = getBaseContext().getResources().getConfiguration(); if (!config.locale.equals(mLocale)) { config.locale = mLocale; getBaseContext().getResources().updateConfiguration(config, null); }
Huds0nHawk
1

Если вы хотите повлиять на параметры меню для немедленного изменения локали. Вам нужно сделать это следующим образом.

//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);
}
Рамеш Акула
источник
Но разве это решение не очищает меню при каждом открытии меню? Я полагаю, onMenuOpenedдолжен быть вызван только один раз, после изменения локали.
Трант