我需要开发一个
Android应用程序,从其主要APK之外的外部资源加载本地化文本.这样做的原因是为了使第三方能够独立提供应用程序的翻译.该应用程序目前具有单个英语本地化,具有相当多的字符串(~2,000).
我宁愿不打破Android的资源系统;例如,我想在strings.xml中提供主要语言本地化字符串,就像在任何Android应用程序中一样.
为了实现这一点,我创建了一个扩展android.content.res.Resources的类,重写了三个getText方法.覆盖实现将尽可能从外部本地化源返回资源,否则将请求转发给super.getText()实现.
资源包装器:
public class IntegratedResources extends Resources {
private ResourceIntegrator ri;
public IntegratedResources(AssetManager assets, DisplayMetrics metrics, Configuration config, ResourceIntegrator ri) {
super(assets, metrics, config);
this.ri = ri;
}
@Override
public CharSequence getText(int id)
throws NotFoundException {
return ri == null ? super.getText(id) : ri.getText(id);
}
@Override
public CharSequence getText(int id, CharSequence def)
throws NotFoundException {
return ri == null ? super.getText(id, def) : ri.getText(id, def);
}
@Override
public CharSequence[] getTextArray(int id)
throws NotFoundException {
return ri == null ? super.getTextArray(id) : ri.getTextArray(id);
}
}
然后我创建了一个ContextWrapper实现来包装Activity的上下文.上下文包装器的getResources()方法将返回上面的IntegratedResources对象.
ContextWrapper:
public class IntegratedResourceContext extends ContextWrapper {
private IntegratedResources integratedResources;
public IntegratedResourceContext(Activity activity, String packageName)
throws NameNotFoundException {
super(activity);
ResourceIntegrator ri = packageName == null ? null : new ResourceIntegrator(activity, packageName);
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindow().getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
integratedResources = new IntegratedResources(activity.getAssets(), displayMetrics,
activity.getResources().getConfiguration(), ri);
}
@Override
public Resources getResources() {
return integratedResources;
}
}
最后我们有了“ResourceIntegrator”类,它从指定的已安装的第三方本地化APK中选择资源.如果需要,可以创建不同的实现以从XML或属性文件中提取它们.
ResourceIntegrator:
public class ResourceIntegrator {
private Resources rBase;
private Resources rExternal;
private String externalPackageName;
private Map<Integer, Integer> baseIdToExternalId = new HashMap<Integer, Integer>();
public ResourceIntegrator(Context context, String externalPackageName)
throws NameNotFoundException {
super();
rBase = context.getResources();
this.externalPackageName = externalPackageName;
if (externalPackageName != null) {
PackageManager pm = context.getPackageManager();
rExternal = pm.getResourcesForApplication(externalPackageName);
}
}
public CharSequence getText(int id, CharSequence def) {
if (rExternal == null) {
return rBase.getText(id, def);
}
Integer externalId = baseIdToExternalId.get(id);
if (externalId == null) {
// Not loaded yet.
externalId = loadExternal(id);
}
if (externalId == 0) {
// Resource does not exist in external resources, return from base.
return rBase.getText(id, def);
} else {
// Resource has a value in external resources, return it.
return rExternal.getText(externalId);
}
}
public CharSequence getText(int id)
throws NotFoundException {
if (rExternal == null) {
return rBase.getText(id);
}
Integer externalId = baseIdToExternalId.get(id);
if (externalId == null) {
// Not loaded yet.
externalId = loadExternal(id);
}
if (externalId == 0) {
// Resource does not exist in external resources, return from base.
return rBase.getText(id);
} else {
// Resource has a value in external resources, return it.
return rExternal.getText(externalId);
}
}
public CharSequence[] getTextArray(int id)
throws NotFoundException {
if (rExternal == null) {
return rBase.getTextArray(id);
}
Integer externalId = baseIdToExternalId.get(id);
if (externalId == null) {
// Not loaded yet.
externalId = loadExternal(id);
}
if (externalId == 0) {
// Resource does not exist in external resources, return from base.
return rBase.getTextArray(id);
} else {
// Resource has a value in external resources, return it.
return rExternal.getTextArray(externalId);
}
}
private int loadExternal(int baseId) {
int externalId;
try {
String entryName = rBase.getResourceEntryName(baseId);
String typeName = rBase.getResourceTypeName(baseId);
externalId = rExternal.getIdentifier(entryName, typeName, externalPackageName);
} catch (NotFoundException ex) {
externalId = 0;
}
baseIdToExternalId.put(baseId, externalId);
return externalId;
}
}
我对stackoverflow的问题是上面的实现是否是一个好主意,它是否正确使用API,以及它的设计是否能够面向未来的Android未知版本.我之前没有见过有人这样做过,而且似乎无法在文档或网络上找到解决此问题的任何内容.
允许独立第三方翻译的基本要求非常关键.目前在内部维护此应用程序的数十种翻译是不可行的,而且我没有能力审查用户提供的翻译质量.如果这个设计是一个非常糟糕的想法并且没有类似的替代方案可用,那么本地化可能必须在没有Android的资源管理系统的情况下完成.
如果这是一个好主意,请随意使用和改进上述代码.
最佳答案
My question to stackoverflow is whether the above is a good idea
如果它是一个完整的解决方案,我会感到震惊,因为你不能强迫Android使用你的自定义ContextWrapper.它应该适用于您手动调用getString(),getText()等的任何地方,但我不知道它如何在Android访问超出您的某个活动之外的那些资源的任何地方工作.有很多地方你不能自己调用getText()等,例如:
>主屏幕发射器
>“设置”中的应用信息
>最近的任务清单
此外,除非您打算永远不要添加新字符串,否则您将遇到永久版本问题.您无法强制第三方翻译支持新字符串,因此您最终会得到一个混合了翻译和非翻译字符串的应用程序.
whether it’s using the API properly
那似乎没问题.
whether its design is future-proof against the unknown versions of Android of tomorrow
Android将访问资源本身的位置数量可能会增加而不是减少.
It is not currently feasible to internally maintain dozens of translations for this application, and I have no capability to vet user-provided translations for quality.
然后只支持您愿意自己管理的语言.首先,正如我已经指出的那样,我不会在几个方面看到这将是一个完整的解决方案.其次,你似乎认为你的方法会让你不会因为糟糕的翻译而受到指责,虽然它可能会减少你得到的责任,但它不会消除这种责任.在这方面我同意Squonk的观点.
我很钦佩你提供更多翻译的热情,但是,就个人而言,我不会走这条特定的道路.