How to iterate over all strings, of all modules, and of all languages?

邮差的信 提交于 2019-12-11 01:48:07

问题


Background

I know it's possible to iterate over all strings of a given R java file, using reflection as such (based on here):

    for (Field field : R.string.class.getDeclaredFields()) {
        if (Modifier.isStatic(field.getModifiers()) && !Modifier.isPrivate(field.getModifiers()) && field.getType().equals(int.class)) {
            try {
                int stringResId = field.getInt(null);
                Log.d("AppLog", field.getName() + ":" + getResources().getString(stringResId));
            } catch (IllegalArgumentException | IllegalAccessException e) {
                Log.e("AppLog", e.toString());
            }
        }
    }

This will show all of the strings used in the app, including of all modules together (they are merged), using the current locale.

The question

Is it possible to iterate over all strings of specific modules (or all except specific ones), and also iterate over them in all supported languages ?


回答1:


Is it possible to iterate over all strings of specific modules (or all except specific ones

No. There's no concept of "module" for resources.

As you already noticed these are all merged into one R. So your only chance is to make keys unique (i.e. prefixed) and then skip all keys that are not prefixed during your walk, or you can create separate elements holding all the keys of strings from given module and work on it later, which should be faster than iterating over all, still painful to maintain. Or you can combine both, and build such filtered array based on prefix on startup and then work on it later if you need to access it more times.

EDIT

For example, iterate over both English and German strings ?

First, note that for most entries you will have the same keys (which obvious, but easy to overlook). And to iterate on both, you need to get the right resource context:

Resources rsrc = getResources();
Configuration config = new Configuration(rsrc.getConfiguration());
config.locale = Locale.US;   //
Resources localeResources = new Resources(rsrc.getAssets(), rsrc.getDisplayMetrics(), config);

and then read as usual. And then repeat setting Locale.GERMAN in config.

EDIT 2

also iterate over them in all supported languages

Theoretically AssetManager features getLocales():

Get the locales that this asset manager contains data for.

but things get tricky here when you realise that "contains data for" is not what you mean. It's sufficient to include i.e. external library which provides i.e. Greek translation too, to have Greek language also returned.

So the simplest approach may simply be to keep index of supported languages as part of your code be it directly in code or maybe as array in resources and rely on that whenever you want to know what languages your app support. This should be easy to maintain and you can even create small gradle task to have you this generated at build time.




回答2:


Here is an example combining my answer here with the code to iterate over the locales:

void foo() {
  getStringsForResource(R.strings.class, context);
  getStringsForResource(com.example.package1.R.strings.class, context);
  getStringsForResource(com.example.package2.R.strings.class, context);
  getStringsForResource(com.example.package3.R.strings.class, context);
}


void getStringsForResource(Class<?> R_strings, Context initialContext) {
  for (String locale : initialContext.getAssets().getLocales()) {
    Configuration config = new Configuration();
    config.setToDefaults();
    config.setLocale(Locale.forLanguageTag(locale));
    Context localeSpecificContext = initialContext.createConfigurationContext(config);

    getStringsForContext(R_strings, localeSpecificContext);
  }
}

void getStringsForContext(Class<?> R_strings, Context context) {
  for (Field field : R_strings.getDeclaredFields())
  {
    if (Modifier.isStatic(field.getModifiers()) && !Modifier.isPrivate(field.getModifiers()) && field.getType().equals(int.class))
    {
      try
      {
        int id = field.getInt(null);
        String resourceName = field.getName();
        String s = context.getString(id);
        // Do something with the string here
      } catch (IllegalArgumentException e)
      {
        // ignore
      } catch (IllegalAccessException e)
      {
        // ignore
      }
    }
  }

}

This code gets the list of locales based on the locales present in the resources (if you are below API 21 though, it will become more trouble than it's worth parsing this, see https://developer.android.com/reference/android/content/res/AssetManager.html#getLocales(), and you may be better off with Locale.getAvailableLocales() as in a comment on the question; be careful with what you expect out of this, though, as if a language does not exist in your resources you will get whatever Android determines is the "closest match", which will probably be en-US for a lot of them), and then uses reflection to get the string IDs in order to get all the strings. The list of resource classes is done manually; it is possible to do this part automatically as well using classpath scanning, but doing so in Android is fragile; it is better if you can just hard-code the list of resource classes to scan.



来源:https://stackoverflow.com/questions/42806443/how-to-iterate-over-all-strings-of-all-modules-and-of-all-languages

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!