管理动态本地化资源
Managing dynamic localisation resources
为了这个问题,想象一下我的应用程序帮助用户练习外语。
他们按下一个按钮开始 Text to Speech 介绍,它说:
<string name="repeat_after_me" translatable="true">Repeat after me</string>
该字符串将以正常方式本地化,根据设备区域设置从适当的 res/values-lang/strings.xml
文件中获取字符串。
引入后,应用程序需要说出他们当前希望学习的 language/locale 个随机字符串中的任意一个。问题就出在这里。
假设 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'(以便随机选择一种) .
<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>
假设我有大量的每种语言的字符串并且我支持大量的语言,问题:
我应该如何存储这些字符串以使其在开发中易于管理和可读?我应该如何 'dynamically' 从方法中引用它们?
澄清-主要问题不仅在于我如何解决:
getStringArray(R.array.(variableLocale);
而且 how/where 我存储这些字符串数组,以便实现可扩展和有组织。
先谢谢你了。
Edit - 切换语言的实际 Text to Speech 实现不是问题,我已经涵盖了。
可扩展解决方案
如果你想保持这种可伸缩性,你需要以一种支持随机访问的形式保存你的字符串,而不需要将所有内容都加载到内存中。因此,普通文件(strings.xml 本质上是)无法完成这项工作。
我建议您检查是否可以使用 SQLite 数据库.
完成您想要的任务
这将导致如下结果:
SELECT text FROM table WHERE locale = yourlocale ORDER BY RANDOM() LIMIT 1
(参见 Select random row from an sqlite table)。
此解决方案需要大量工作来创建所需的数据库,因此对于已知的小情况,请使用以下解决方案。
有限的解决方案
如果您知道条目不会太多,我建议您使用纯文本文件(每种语言一个)。它们最容易管理。
您可以将它们保存为 原始资源 或保存在 assets 文件夹中。两者都相对容易读入字符串。然后你只需要调用 String.split("\n")
并有一个数组,你可以从中随机 select 一个。
或者,您可以将字符串放在 字符串数组中 每个本地化 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
,好像没有这个问题
在所有情况下,如果您需要重复 select 个条目,缓存数组可能是个好主意。
注意:此解决方案不能很好地扩展,因为必须将整个数组加载到内存中才能 select 一个条目。所以大型集合可能会超出您的堆或至少使用大量内存。
参考 this answer and this answer,我提出了以下自定义 class 解决方案:
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);
}
}
}
为了这个问题,想象一下我的应用程序帮助用户练习外语。
他们按下一个按钮开始 Text to Speech 介绍,它说:
<string name="repeat_after_me" translatable="true">Repeat after me</string>
该字符串将以正常方式本地化,根据设备区域设置从适当的 res/values-lang/strings.xml
文件中获取字符串。
引入后,应用程序需要说出他们当前希望学习的 language/locale 个随机字符串中的任意一个。问题就出在这里。
假设 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'(以便随机选择一种) .
<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>
假设我有大量的每种语言的字符串并且我支持大量的语言,问题:
我应该如何存储这些字符串以使其在开发中易于管理和可读?我应该如何 'dynamically' 从方法中引用它们?
澄清-主要问题不仅在于我如何解决:
getStringArray(R.array.(variableLocale);
而且 how/where 我存储这些字符串数组,以便实现可扩展和有组织。
先谢谢你了。
Edit - 切换语言的实际 Text to Speech 实现不是问题,我已经涵盖了。
可扩展解决方案
如果你想保持这种可伸缩性,你需要以一种支持随机访问的形式保存你的字符串,而不需要将所有内容都加载到内存中。因此,普通文件(strings.xml 本质上是)无法完成这项工作。
我建议您检查是否可以使用 SQLite 数据库.
完成您想要的任务这将导致如下结果:
SELECT text FROM table WHERE locale = yourlocale ORDER BY RANDOM() LIMIT 1
(参见 Select random row from an sqlite table)。
此解决方案需要大量工作来创建所需的数据库,因此对于已知的小情况,请使用以下解决方案。
有限的解决方案
如果您知道条目不会太多,我建议您使用纯文本文件(每种语言一个)。它们最容易管理。
您可以将它们保存为 原始资源 或保存在 assets 文件夹中。两者都相对容易读入字符串。然后你只需要调用 String.split("\n")
并有一个数组,你可以从中随机 select 一个。
或者,您可以将字符串放在 字符串数组中 每个本地化 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
,好像没有这个问题
在所有情况下,如果您需要重复 select 个条目,缓存数组可能是个好主意。
注意:此解决方案不能很好地扩展,因为必须将整个数组加载到内存中才能 select 一个条目。所以大型集合可能会超出您的堆或至少使用大量内存。
参考 this answer and this answer,我提出了以下自定义 class 解决方案:
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);
}
}
}