J'ai besoin de développer une application Android qui charge du texte localisé à partir de ressources externes en dehors de sa propre application. APK primaire .
La raison en est de permettre à des tiers de fournir indépendamment des traductions de l'application. L'application dispose actuellement d'une seule localisation en anglais avec un nombre assez important de chaînes de caractères (~2 000).
Je préférerais ne pas m'éloigner du système de ressources d'Android ; par exemple, je voudrais fournir les chaînes localisées dans la langue principale dans strings.xml
comme dans n'importe quelle application Android.
Pour ce faire, j'ai créé une classe qui étend Android.content.res.Resources, en surchargeant les trois éléments suivants getText
méthodes. Les implémentations de remplacement renverront les ressources de la source de localisation externe lorsque cela est possible, sinon elles transmettront la requête à la méthode super.getText()
mise en œuvre.
Emballage des ressources :
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);
}
}
J'ai ensuite créé une implémentation de ContextWrapper pour envelopper le contexte d'une activité. La méthode getResources() de l'enveloppe du contexte renvoie l'objet IntegratedResources ci-dessus.
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;
}
}
Enfin, nous avons la classe "ResourceIntegrator", qui extrait les ressources d'un APK de localisation tiers installé et spécifié.
Une implémentation différente pourrait être créée pour les extraire d'un fichier XML ou de propriétés si vous le souhaitez.
Intégrateur de ressources :
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;
}
}
Ma question à stackoverflow est la suivante si l'implémentation ci-dessus est une bonne idée Il s'agit de savoir si elle utilise correctement l'API et si sa conception est à l'épreuve des versions inconnues d'Android de demain.
Je n'ai jamais vu personne faire cela auparavant, et je ne trouve rien sur la résolution de ce problème dans la documentation ou sur le web.
L'exigence sous-jacente d'autoriser des traductions indépendantes par des tiers est assez critique. Il n'est actuellement pas possible de maintenir en interne des dizaines de traductions pour cette application, et je n'ai pas la possibilité de vérifier la qualité des traductions fournies par les utilisateurs.
Dans le cas où cette conception est une très mauvaise idée et qu'aucune alternative similaire n'est disponible, la localisation devra alors se faire sans le système de gestion des ressources d'Android.
Dans le cas où cette idée serait bonne, n'hésitez pas à utiliser et à améliorer le code ci-dessus.