管理动态本地化资源

时间:2016-03-25 02:06:56

标签: java android localization locale

为了这个问题,想象一下我的应用程序可以帮助用户练习外语。

他们按一个按钮开始文字转语音介绍,其中说:

<string name="repeat_after_me" translatable="true">Repeat after me</string>

此字符串将以正常方式进行本地化,根据设备区域设置从相应的res/values-lang/strings.xml文件中提取字符串。

在介绍之后,应用程序需要在他们当前希望学习的语言/语言环境中说出任意一个字符串。这就是问题所在。

假设Text to Speech从一个简单的方法开始,例如:

private void startLearning(Locale learningLocale)

伪代码:

TTS.speak(getString(R.string.repeat_after_me)

接下来是:

TTS.speak(getRandomLearningString(learningLocale))

其中:

String getRandomLearningString(Locale learningLocale) {
// return a random string here
}

以上是我坚持如何最好地引用xml资源的地方,其中包含用户正在学习的语言的“字符串数组”(为了随机选择一个)。

<string-array name="en_EN" translatable="false">
    <item>"Where is the nearest hospital?"</item>
    <item>"What's the time please?"</item>
    <item>"Only if you promise to wear protection and we have a safe word"</item>
</string-array>

假设每种语言都有大量字符串,而且我支持大量语言,问题是:

我应该如何存储这些字符串以使其在开发中易于管理和可读?我应该如何“动态”从方法中引用它们?

澄清 - 主要问题不仅仅是我如何解决:

getStringArray(R.array.(variableLocale);

但是我如何/在哪里存储这些字符串数组,以便实现可扩展和组织。

我提前感谢你。

编辑 - 切换语言的实际文本到语音实现不是问题,我已经涵盖了这一点。

2 个答案:

答案 0 :(得分:1)

Scalebale解决方案

如果要保持这种可扩展性,则需要以支持随机访问的形式保存字符串,而不必将所有内容都加载到内存中。因此,一个普通文件(其中strings.xml本质上)不会完成这项工作。

我建议您检查是否可以使用 SQLite数据库完成所需的操作。

这会导致类似:

SELECT text FROM table WHERE locale = yourlocale ORDER BY RANDOM() LIMIT 1

(见Select random row from an sqlite table)。

此解决方案需要大量工作才能创建所需的数据库,因此对于已知的小情况,请使用以下解决方案。

有限的解决方案

如果您知道自己没有太多条目,我建议使用纯文本文件(每种语言一个)。他们最容易管理。

您可以将其保存为原始资源,也可以保存在资产文件夹中。两者都相对容易读入字符串。然后你只需要调用String.split("\n")并有一个数组,你可以从中随机选择一个。

或者,您可以将字符串放在字符串数组每个本地化 strings.xml 中,并使用以下资源加载所需数组:

Resources standardResources = context.getResources();
AssetManager assets = standardResources.getAssets();
DisplayMetrics metrics = standardResources.getDisplayMetrics();
Configuration config = new Configuration(standardResources.getConfiguration());
config.locale = yourLocale;
Resources resources = new Resources(assets, metrics, config);

(见:Load language specific string from resource?

如源代码中所述,这似乎会覆盖从context.getResources()返回的资源,也许您之后必须重置为之前的语言环境。

从Jellybean开始还有context.createConfigurationContext,似乎没有这个问题。

在所有情况下,如果需要重复选择条目,最好缓存数组。

注意:此解决方案不能很好地扩展,因为必须将整个数组加载到内存中才能选择一个条目。因此,大型集合可能会超出您的堆或至少使用大量内存。

答案 1 :(得分:0)

参考this answerthis answer,我提出了以下自定义类解决方案:

package com.my.package.localisation;

import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.support.annotation.NonNull;
import android.util.DisplayMetrics;

import java.util.Formatter;
import java.util.Locale;

/**
 * Class to manage fetching {@link Resources} for a specific {@link Locale}. API levels less
 * than {@link Build.VERSION_CODES#JELLY_BEAN_MR1} require an ugly implementation.
 * <p/>
 * Subclass extends {@link Resources} in case of further functionality requirements.
 */
public class MyResources {

    private final Context mContext;
    private final AssetManager assetManager;
    private final DisplayMetrics metrics;
    private final Configuration configuration;
    private final Locale targetLocale;
    private final Locale defaultLocale;

    public MyResources(@NonNull final Context mContext, @NonNull final Locale defaultLocale,
                         @NonNull final Locale targetLocale) {

        this.mContext = mContext;
        final Resources resources = this.mContext.getResources();
        this.assetManager = resources.getAssets();
        this.metrics = resources.getDisplayMetrics();
        this.configuration = new Configuration(resources.getConfiguration());
        this.targetLocale = targetLocale;
        this.defaultLocale = defaultLocale;
    }

    public String[] getStringArray(final int resourceId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(targetLocale);
            return mContext.createConfigurationContext(configuration).getResources().getStringArray(resourceId);
        } else {
            configuration.locale = targetLocale;
            final String[] resourceArray = new ResourceManager(assetManager, metrics, configuration).getStringArray(resourceId);
            configuration.locale = defaultLocale; // reset
            new ResourceManager(assetManager, metrics, configuration); // reset
            return resourceArray;
        }
    }

    public String getString(final int resourceId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
            configuration.setLocale(targetLocale);
            return mContext.createConfigurationContext(configuration).getResources().getString(resourceId);
        } else {
            configuration.locale = targetLocale;
            final String resource = new ResourceManager(assetManager, metrics, configuration).getString(resourceId);
            configuration.locale = defaultLocale; // reset
            new ResourceManager(assetManager, metrics, configuration); // reset
            return resource;
        }
    }

    private final class ResourceManager extends Resources {
        public ResourceManager(final AssetManager assets, final DisplayMetrics metrics, final Configuration config) {
            super(assets, metrics, config);
        }

        /**
         * Return the string array associated with a particular resource ID.
         *
         * @param id The desired resource identifier, as generated by the aapt
         *           tool. This integer encodes the package, type, and resource
         *           entry. The value 0 is an invalid identifier.
         * @return The string array associated with the resource.
         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
         */
        @Override
        public String[] getStringArray(final int id) throws NotFoundException {
            return super.getStringArray(id);
        }

        /**
         * Return the string value associated with a particular resource ID,
         * substituting the format arguments as defined in {@link Formatter}
         * and {@link String#format}. It will be stripped of any styled text
         * information.
         * {@more}
         *
         * @param id         The desired resource identifier, as generated by the aapt
         *                   tool. This integer encodes the package, type, and resource
         *                   entry. The value 0 is an invalid identifier.
         * @param formatArgs The format arguments that will be used for substitution.
         * @return String The string data associated with the resource,
         * stripped of styled text information.
         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
         */
        @NonNull
        @Override
        public String getString(final int id, final Object... formatArgs) throws NotFoundException {
            return super.getString(id, formatArgs);
        }

        /**
         * Return the string value associated with a particular resource ID.  It
         * will be stripped of any styled text information.
         * {@more}
         *
         * @param id The desired resource identifier, as generated by the aapt
         *           tool. This integer encodes the package, type, and resource
         *           entry. The value 0 is an invalid identifier.
         * @return String The string data associated with the resource,
         * stripped of styled text information.
         * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
         */
        @NonNull
        @Override
        public String getString(final int id) throws NotFoundException {
            return super.getString(id);
        }
    }
}