在应用程序内更改语言环境

207

我的用户可以在应用内更改语言环境(他们可能希望保持手机设置为英文,但以法语、荷兰语或任何其他语言阅读我的应用内容...)

为什么这在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" />

=> 每次更改语言环境时,菜单都会缩小!!!

因为我希望我的应用在 Android 1.5 上面对用户依然可用,所以我该怎么做?


我编辑了我的第一篇帖子,提供了一个创建问题的示例。我注意到实际上当我将<uses-sdk android:minSdkVersion="5" />这一行改为“3”时...问题确实出现了! - Hubert
1
您可以使用以下库,该库提供语言列表、设置屏幕的首选项,并覆盖应用程序中的语言:https://github.com/delight-im/Android-Languages - caw
同样的问题:https://dev59.com/CmAg5IYBdhLWcg3wpMWz - trante
我已经在另一个帖子中回答了这个问题,请查看这里 - Sindri Þór
我从你的问题本身找到了解决方案,谢谢。 - Deepak Rajput
显示剩余3条评论
6个回答

153

虽然原始问题并不是关于地区设置本身,但所有其他与地区相关的问题都在引用这个问题。这就是为什么我想在这里澄清问题。我将此问题作为自己的区域设置代码的起点,并发现该方法并不完全正确。它可以工作,但只有在任何配置更改(例如屏幕旋转)之前,而且只在特定的Activity中有效。经过一段时间的代码研究,我最终采用了以下方法:

我扩展了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都有自定义的语言环境,并且在旋转和其他事件中不会被重置。

我还花费了很多时间尝试使首选项更改立即应用,但没有成功:语言在Activity重新启动时正确更改,但数字格式和其他语言环境属性直到完全重新启动应用程序才会应用。

AndroidManifest.xml的更改

别忘记在AndroidManifest中的每个activity上添加android:configChanges="layoutDirection|locale",以及将android:name=".MyApplication"添加到<application>元素中。


1
你的首选活动是从主活动调用的吗?你可以将它放在onResume中...@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); } - Anthony Graglia
1
这正是我需要的,我曾经在MainActivity中调用updateConfiguration(),但当应用程序终止然后重新启动时,菜单不会更新。你的解决方案是正确的。 如果你想立即刷新字符串,我认为你仍然必须手动重置字符串。(例如重置按钮标签或重新创建菜单)。 - emeraldhieu
这个解决方案太棒了,点赞!onConfigurationChanged()起到了关键作用。谢谢! - AVIDeveloper
18
请注意,您绝不能修改Android提供给您的Configuration对象(confignewConfig)。您应该先使用 config = new Configuration(config); 复制一个副本。在找到问题所在之前,我花了一个小时来调试这段代码(它导致活动无休止地闪烁)。 - imgx64
1
当在Android 7+上从冷启动开始应用程序时,此技术存在故障,系统字体大小设置为大号。Google已更改了updateConfiguration()的工作方式,因此有时您的Activity将以2种不同的语言显示(特别是在对话框中)。更多信息请参见:https://dev59.com/C1kS5IYBdhLWcg3wiXbI - Mr-IDE
显示剩余9条评论

63

经过一晚上的睡眠,我在网上找到了答案 (使用以下命令进行简单的谷歌搜索 "getBaseContext().getResources().updateConfiguration(mConfig, getBaseContext().getResources().getDisplayMetrics());"),这就是答案:

链接文本 => 这个链接还展示了正在发生的屏幕截图

密度是问题所在,我需要在 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
根据谷歌的建议(遗留应用程序策略-第9点:http://developer.android.com/intl/fr/guide/practices/screens_support.html#strategies),我设置了android:anyDensity =“false”。那么请小心!请注意不要改变原意。 - Hubert
2
按钮上的文本在我更改语言环境后没有改变。我想知道是否有解决方法可以刷新所有包含字符串资源的小部件。现在我正在使用OnRestart()中的setText(getString(R.string.button_label))重新绘制文本。(当然,在重新启动应用程序后会更改。) - emeraldhieu
你可以调用重新创建活动的方法 activity.recreate() - To Kra

59
在Android M中,顶部的解决方案将无法工作。我已经编写了一个帮助类来解决这个问题,你应该从你的Application类和所有的Activities中调用它(我建议创建一个BaseActivity,然后让所有的Activities继承它)。
注意:这也支持正确的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应该从构造函数中调用,而不是从activity类的onCreate方法中调用,我犯了这个错误。 - Hitesh Sahu
2
这取决于你在程序中的实现方式。如果你已经重写了onConfigurationChanged(),那么你就不需要调用.updateConfig(),这就是为什么会出现错误的原因,因为它被调用了两次。如果你说只调用.setLocale()不起作用,那是因为你需要刷新你的UI,可以通过在活动或片段中调用recreate()方法或自己的方法来实现(即使它不太流畅,但我更喜欢前者,因为你永远不用担心问题)。 - usernotnull
3
//导入android.support.v7.view.ContextThemeWrapper; import android.view.ContextThemeWrapper;//正确的命名空间 - Mahdi Rostami
1
BaseActivity() 构造函数不应该调用 super() 吗? - LarsH
1
如果您在应用程序中更改了语言环境,但系统的语言环境与之不同,在Android M+上显示权限对话框时,使用的是哪种语言? - Don Box
显示剩余7条评论

10

这是我对 Andrey's 回答的评论,但我无法在评论中包含代码。

你的偏好活动是否是从主活动调用的?你可以将此放置在 on resume 中...

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

2
你的意思是如果我想在运行时更改语言,那么我必须完成该活动并重新启动该活动才能获得更改吗?是否有其他方法可以使其更改,因为每次完成活动并重新启动它都不好。 - Shreyash Mahajan
1
最好执行 activity.recreate() - To Kra
@trgradlia,我想使用上面的代码。请告诉我,langPreference是什么?我认为我们正在比较最近更改的语言和以前的语言。 - Mahen
1
Activity类有recreate()方法,因此在代码中使用recreate()代替refresh()。这将节省您在refresh()方法中的3行代码。 - Inzimam Tariq IT
1
@InzimamTariqIT,谢谢你的建议……答案已经发布了近7年,所以我会将其保留,并且让人们可以在评论中找到你的改进。 - Anthony Graglia

7

我不能使用android:anyDensity="true",因为游戏中的物体会被完全不同地定位...似乎这也能解决问题:

// 创建本地化
Locale locale2 = new Locale(loc); 
Locale.setDefault(locale2);
Configuration config2 = new Configuration();
config2.locale = locale2;
// 更新本地化 mContext.getResources().updateConfiguration(config2, null);

这个解决方案对我来说最好,只需使用以下代码: 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应该只在语言环境更改后调用一次。 - trante

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接