Android:如何在不改变当前语言环境的情况下获取特定语言环境中的字符串

51

用例: 记录用户显示的错误消息。

然而,您不希望在日志中出现依赖于用户设备语言环境的消息。 另一方面,您也不想仅为了技术日志记录而更改用户设备的语言环境。 这是否可以实现? 我在stackoverflow上找到了几个潜在的解决方案:

然而,它们都会导致更改我的设备语言环境(直到下一次配置更改)。

无论如何,我的当前解决方法是这样的:

public String getStringInDefaultLocale(int resId) {
    Resources currentResources = getResources();
    AssetManager assets = currentResources.getAssets();
    DisplayMetrics metrics = currentResources.getDisplayMetrics();
    Configuration config = new Configuration(
            currentResources.getConfiguration());
    config.locale = DEFAULT_LOCALE;
    /*
     * Note: This (temporiarily) changes the devices locale! TODO find a
     * better way to get the string in the specific locale
     */
    Resources defaultLocaleResources = new Resources(assets, metrics,
            config);
    String string = defaultLocaleResources.getString(resId);
    // Restore device-specific locale
    new Resources(assets, metrics, currentResources.getConfiguration());
    return string;
}

坦白说,我不喜欢这种方法。它不够高效,并且考虑到并发等问题,可能会导致某些视图显示在“错误”的语言环境下。
那么,有什么想法吗?也许可以使用ResourceBundle,就像标准Java一样实现这个功能?

你好,你有找到解决方案吗?我也有类似的用例。 - Nitish
不,我仍在使用问题中提到的解决方法。 - schnatterer
为什么不使用特定的基于英语的字符串来记录日志?R.string.dev_error_clicking_button_b。如果您不将其翻译成其他语言(并且不需要这样做),则默认情况下会得到此字符串。 - Vincent D.
@VincentD。谢谢你的想法,但是你如何知道这些字符串没有被翻译?我正在寻找一种通用机制来记录以某种方式显示给用户的所有内容。 - schnatterer
5个回答

54

您可以使用此功能进行API +17

@NonNull
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public static String getStringByLocal(Activity context, int id, String locale) {
    Configuration configuration = new Configuration(context.getResources().getConfiguration());
    configuration.setLocale(new Locale(locale));
    return context.createConfigurationContext(configuration).getResources().getString(id);
}

更新(1):如何支持旧版本。

@NonNull
public static String getStringByLocal(Activity context, int resId, String locale) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
        return getStringByLocalPlus17(context, resId, locale);
    else
        return getStringByLocalBefore17(context, resId, locale);
}

@NonNull
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static String getStringByLocalPlus17(Activity context, int resId, String locale) {
    Configuration configuration = new Configuration(context.getResources().getConfiguration());
    configuration.setLocale(new Locale(locale));
    return context.createConfigurationContext(configuration).getResources().getString(resId);
}

private static String getStringByLocalBefore17(Context context,int resId, String language) {
    Resources currentResources = context.getResources();
    AssetManager assets = currentResources.getAssets();
    DisplayMetrics metrics = currentResources.getDisplayMetrics();
    Configuration config = new Configuration(currentResources.getConfiguration());
    Locale locale = new Locale(language);
    Locale.setDefault(locale);
    config.locale = locale;
/*
 * Note: This (temporarily) changes the devices locale! TODO find a
 * better way to get the string in the specific locale
 */
    Resources defaultLocaleResources = new Resources(assets, metrics, config);
    String string = defaultLocaleResources.getString(resId);
    // Restore device-specific locale
    new Resources(assets, metrics, currentResources.getConfiguration());
    return string;
}

更新 (2): 请查看此文章


我可以验证在不改变当前语言环境的情况下,这个功能可行。终于解决了! 至少对于API 17+是这样的。 - schnatterer
@schnatterer 您可以检查构建版本以支持旧版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) return getStringByLocal(context, resId, locale); else return getStringByLocalBefore17(context, resId, locale); - Khaled Lela
我更喜欢传递Locale对象而不是字符串作为参数,因为Locale具有流行语言环境的预定义常量(例如Locale.US),并且还提供了方便的构造函数来帮助您避免规范中的错误。 - Dmitry K
如果上下文是一个Fragment,如何使用这个方法? - Shubham Nanche
我进行了测试,当因缺少语言而不存在翻译时,代码会返回主要区域设置的翻译。 - Alberto M
显示剩余3条评论

6
感谢@Khaled Lela的答案,我已经编写了一个Kotlin扩展:
fun Context.getStringByLocale(@StringRes stringRes: Int, locale: Locale, vararg formatArgs: Any): String {
    val configuration = Configuration(resources.configuration)
    configuration.setLocale(locale)
    return createConfigurationContext(configuration).resources.getString(stringRes, *formatArgs)
}

我正在直接在ActivityFragment或与Context相关的类中使用,只需简单地调用: getStringByLocale(R.string.my_text, Locale.UK) 或带有参数的: getStringByLocale(R.string.my_other_text, Locale.RU, arg1, arg2, arg3)

4
您可以将默认语言环境的所有字符串保存在全局类中的 Map 中,例如当启动应用程序时执行的 Application 类:
public class DualLocaleApplication extends Application {

    private static Map<Integer, String> defaultLocaleString;

    public void onCreate() {
        super.onCreate();
        Resources currentResources = getResources();
        AssetManager assets = currentResources.getAssets();
        DisplayMetrics metrics = currentResources.getDisplayMetrics();
        Configuration config = new Configuration(
                currentResources.getConfiguration());
        config.locale = Locale.ENGLISH;
        new Resources(assets, metrics, config);
        defaultLocaleString = new HashMap<Integer, String>();
        Class<?> stringResources = R.string.class;
        for (Field field : stringResources.getFields()) {
            String packageName = getPackageName();
            int resId = getResources().getIdentifier(field.getName(), "string", packageName);
            defaultLocaleString.put(resId, getString(resId));
        }
        // Restore device-specific locale
        new Resources(assets, metrics, currentResources.getConfiguration());
    }

    public static String getStringInDefaultLocale(int resId) {
        return defaultLocaleString.get(resId);
    }

}

这个解决方案并不是最优的,但您不会遇到并发问题。


感谢提出另一种解决方案!这是一个权衡:没有并发问题,但会增加内存使用和应用程序启动速度变慢。然而,对于我的特定用例,大部分时间我不需要资源(只需要记录错误/异常)。因此,在每次启动时创建一个映射并将其保留在内存中似乎有点过于费力了。 - schnatterer
1
这将取决于您实际用于这些错误/异常的字符串资源数量。如果子集很小,也许您可以忍受它。另一种方法(解决方法:D)是将这些资源临时保存在tmp文件中,并根据需要访问它们。使用此方法不会增加额外的内存使用量,并且可以满足您的需求,我想。 - Juan Sánchez
你在寻找解决方案时真的很有创意!为此点赞 ;) 感谢你的努力 - 不过,我仍然希望有人能提供一个非权宜之计的解决方案。 - schnatterer

0

对于 API +17,我们可以使用以下内容:

public static String getDefaultString(Context context, @StringRes int stringId){
    Resources resources = context.getResources();
    Configuration configuration = new Configuration(resources.getConfiguration());
    Locale defaultLocale = new Locale("en");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        LocaleList localeList = new LocaleList(defaultLocale);
        configuration.setLocales(localeList);
        return context.createConfigurationContext(configuration).getString(stringId);
    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1){
        configuration.setLocale(defaultLocale);
        return context.createConfigurationContext(configuration).getString(stringId);
    }
    return context.getString(stringId);
}

-2

试试这个:

  Configuration conf = getResources().getConfiguration();
  conf.locale = new Locale("ar"); // locale I used here is Arabic

  Resources resources = new Resources(getAssets(), getResources().getDisplayMetrics(), conf);
  /* get localized string */
  String appName = resources.getString(R.string.app_name);

我是否漏掉了什么,或者你的方法和问题中的方法没有区别?如果你执行new Resources(getAssets(), getResources().getDisplayMetrics(), getResources().getConfiguration()).getString(R.string.app_name);,字符串仍然会是阿拉伯语,即使你的设备语言之前是不同的!我正在寻找以下用例的解决方案:你的设备语言是中文,但日志应该打印默认(例如英文)消息。 - schnatterer
2
注意调用 new Resources 会改变 AssetManager 的配置。这会在以后与代码无关的部分造成问题。请参见这个问题回答,了解它可能引起的问题。 - Jade

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