android – get the application's resources languages-ThrowExceptions

Exception or error:

Is it possible, at runtime, to know which resources languages are embedded in my app?

i.e the presence of this folders:

values-en
values-de
values-fr
...
How to solve:

AssetManager.getLocales() is actually the way to do this. However, from the public API every AssetManager you create also has the framework resources included in its search path… so when you call AssetManager.getLocales() you will also see any locales that are part of the framework resources. There is no way to get around this with the SDK, sorry.

###

It’s complicated because even if you have a folder named values-de it doesn’t mean you have any resources there. If you have string.xml in values-de it doesn’t mean you have string value there.

values:

<resources>
    <string name="app_name">LocTest</string>
    <string name="hello_world">Hello world!</string>
    <string name="menu_settings">Settings</string>
</resources>

values-de:

<resources>
    <string name="hello_world">Hallo Welt!</string>
</resources>

You can test if a resource for a specific locale is different than the default:

DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
Resources r = getResources();
Configuration c = r.getConfiguration();
String[] loc = r.getAssets().getLocales();
for (int i = 0; i < loc.length; i++) {
    Log.d("LOCALE", i + ": " + loc[i]);

    c.locale = new Locale(loc[i]);
    Resources res = new Resources(getAssets(), metrics, c);
    String s1 = res.getString(R.string.hello_world);
    c.locale = new Locale("");
    Resources res2 = new Resources(getAssets(), metrics, c);
    String s2 = res2.getString(R.string.hello_world);

    if(!s1.equals(s2)){
        Log.d("DIFFERENT LOCALE", i + ": "+ s1+" "+s2 +" "+ loc[i]);
    }
}

It has one fault – you can check one value whether it has translation.

The dirty code above will print something like:

LOCALE(5667): 51: en_NZ LOCALE(5667): 52: uk_UA LOCALE(5667): 53:
nl_BE LOCALE(5667): 54: de_DE DIFFERENT LOCALE(5667): 54: Hallo Welt!
Hello world! de_DE LOCALE(5667): 55: ka_GE LOCALE(5667): 56: sv_SE
LOCALE(5667): 57: bg_BG LOCALE(5667): 58: de_CH DIFFERENT
LOCALE(5667): 58: Hallo Welt! Hello world! de_CH LOCALE(5667): 59:
fr_CH LOCALE(5667): 60: fi_FI

###

Inspired by Mendhak’s solution I created something a bit cleaner:

    defaultConfig {
        ....

        def locales = ["en", "it", "pl", "fr", "es", "de", "ru"]

        buildConfigField "String[]", "TRANSLATION_ARRAY", "new String[]{\""+locales.join("\",\"")+"\"}"
        resConfigs locales
    }

Then in Java use:

BuildConfig.TRANSLATION_ARRAY

Advatages of this method:

  • Smaller apk – resConfigs will cut out resources from libraries which you don’t need (some have hundreds)
  • Fast – No need to parse resource configurations

###

Inspired by the answers above I created a simple method to get all app’s languages based on provided translations:

public static Set<String> getAppLanguages( Context ctx, int id ) {
  DisplayMetrics dm = ctx.getResources().getDisplayMetrics();
  Configuration conf = ctx.getResources().getConfiguration();
  Locale originalLocale = conf.locale;
  conf.locale = Locale.ENGLISH;
  final String reference = new Resources( ctx.getAssets(), dm, conf ).getString( id );

  Set<String> result = new HashSet<>();
  result.add( Locale.ENGLISH.getLanguage() );

  for( String loc : ctx.getAssets().getLocales() ){
    if( loc.isEmpty() ) continue;
    Locale l = Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT_WATCH ? new Locale( loc.substring( 0, 2 ) ) : Locale.forLanguageTag( loc );
    conf.locale = l;
    if( !reference.equals( new Resources( ctx.getAssets(), dm, conf ).getString( id ) ) ) result.add( l.getLanguage() );
  }
  conf.locale = originalLocale;
  return result; 
}

where as id arg should be used a R.string.some_message which is provided in all translations and contains clearly distinguishable text, like "Do you really want to delete the object?"

Maybe it would help someone…

###

For anyone using Gradle, I did it like so below it traverses all strings.xml, grabs the directory names and figures out the locales from there. It adds a String[] to BuildConfig which you can access as BuildConfig.TRANSLATION_ARRAY

task buildTranslationArray << {
    def foundLocales = new StringBuilder()
    foundLocales.append("new String[]{")

    fileTree("src/main/res").visit { FileVisitDetails details ->
        if(details.file.path.endsWith("strings.xml")){
            def languageCode = details.file.parent.tokenize('/').last().replaceAll('values-','').replaceAll('-r','-')
            languageCode = (languageCode == "values") ? "en" : languageCode;
            foundLocales.append("\"").append(languageCode).append("\"").append(",")
        }
    }

    foundLocales.append("}")
    //Don't forget to remove the trailing comma
    def foundLocalesString = foundLocales.toString().replaceAll(',}','}')
    android.defaultConfig.buildConfigField "String[]", "TRANSLATION_ARRAY", foundLocalesString

}
preBuild.dependsOn buildTranslationArray

So after the above task occurs (on prebuild) the BuildConfig.TRANSLATION_ARRAY has your list of locales.

I’m not a Gradle/Groovy expert so this could definitely be a bit neater.

Reasoning – I ran into too many issues implementing pawelzieba’s solution, I had no reliable strings to ‘compare’ as the translations were crowdsourced. The easiest way then was to actually look at the values-blah folders available.

###

LocaleList or LocaleListCompat are one of the ways you could get the languages supported by your application.

LocaleList was introduced in API 24.

The thing to consider when using LocaleListCompat is that for API < 24 only the first language tag will be used.

###

these res-lang actually depends on Locales, so you need to get locale from device and you can get which language is getting shown from locale..

Locale myPhoneLocale = Locale.getDefault();

You can then call getDisplayLanguage() to know which language is getting shown.

Reference : Locale

###

Are you talking about this?

String language = Locale.getDefault().getDisplayLanguage();

###

Try calling AssetManager.getLocales():

Get the locales that this asset manager contains data for.

Or you can try if you can get a list using list().

Return a String array of all the assets at the given path.

###

Do you mean:

String[] locales = getAssets().getLocales();  

This would let you get the language that your device have.

###

Inspired with the code by@Injecteer
I have done the following:

for the list of languages ​​that the app supports , it is necessary to pass the default language , since it is not possible to detect

public static Map<String,String> getAppLanguages(Context context, String appDefaultLang) {
    Map<String, String> listAppLocales = new LinkedHashMap<>();

    listAppLocales.put("default","Auto");

    DisplayMetrics metrics = new DisplayMetrics();
            Resources res = context.getResources();
    Configuration conf = res.getConfiguration();
    String[] listLocates = res.getAssets().getLocales();

    for (String locate : listLocates) {

        conf.locale = new Locale(locate);
        Resources res1 = new Resources(context.getAssets(), metrics, conf);
        String s1 = res1.getString(R.string.title_itinerary);
        String value = ucfirst(conf.locale.getDisplayName());

        conf.locale = new Locale("");
        Resources res2 = new Resources(context.getAssets(), metrics, conf);
        String s2 = res2.getString(R.string.title_itinerary);

        if (!s1.equals(s2)) {
            listAppLocales.put(locate, value);
        } else if (locate.equals(appDefaultLang)) {
            listAppLocales.put(locate, value);
        }
    }
    return listAppLocales;
}

The result is a list map<key,value>of languages ​​supported by the application, the first thing is for if you want to use to populate a listPreference

Leave a Reply

Your email address will not be published. Required fields are marked *