29

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
...
VinceFR
  • 2,551
  • 1
  • 21
  • 27
  • 2
    Just curious: but by default wouldn't you know which locales are there since you put them in the app? – Salil Pandit Jul 25 '12 at 17:53
  • 3
    You're right! but i'm creating a library(so end developers can add languages) and i need to know programmatically how many are embedded – VinceFR Jul 26 '12 at 07:49

10 Answers10

15

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

Jared Rummler
  • 37,824
  • 19
  • 133
  • 148
pawelzieba
  • 16,082
  • 3
  • 46
  • 72
10

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.

hackbod
  • 90,665
  • 16
  • 140
  • 154
  • 3
    There's `AssetManager.getNonSystemLocales()` but it has my favorite `@hide` tag in the JavaDoc. It is [exactly what Android uses to determine which locale to use](https://android.googlesource.com/platform/frameworks/base/+/c37457799be3db0590a5d94832b2fef5f64ef439/core/java/android/content/res/ResourcesImpl.java#402) to access the resources in your app. Also, it's "available" only since Nougat. – arekolek Feb 13 '19 at 00:29
  • 1
    I started wondering how it was done pre-Nougat, and from what I see, back then it was all done in native code, it wasn't exposed to Java. Java code would only call `AssetManager.setConfiguration` and later `AssetManager.loadResourceValue` which are both `native` methods. – arekolek Nov 08 '19 at 11:13
9

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.

Mendhak
  • 8,194
  • 5
  • 47
  • 64
9

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
Bob bobbington
  • 1,225
  • 13
  • 12
  • 1
    That would be my preferred solution, unfortunately it's no good in a multi-module project. – arekolek Feb 12 '19 at 23:42
  • Why wouldn't it work in a multi-module project? I guess only if more than one module contains string resources - but if only one of them does, that should be fine, no? (I know your comment is years old) @arekolek – Konrad Morawski Aug 30 '23 at 08:09
  • 1
    @KonradMorawski in my case I had multiple app modules that should support different sets of languages and wanted to have access to the list of those languages in a library module (used by the app modules), so that the library would know at runtime what languages are supported by the app - BuildConfig can't be used for that – arekolek Aug 30 '23 at 10:56
  • Ah, I get it. I guess it could still be done by retrieving the BuildConfig on the app module level, and then "injecting" this knowledge back to the library module upon start-up? – Konrad Morawski Sep 01 '23 at 10:48
4

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...

injecteer
  • 20,038
  • 4
  • 45
  • 89
2

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.

BorisJ
  • 29
  • 3
  • 1
    You are answering a very old post. Please indicate which version of Android your solution targets if it's different from already post answers. – Poutrathor Jul 22 '19 at 16:46
  • Thanks for the tip. The reason I added my answer here is that I couldn't find the solution myself on any other posts and hopefully it will save someone some time searching. – BorisJ Jul 22 '19 at 18:43
  • 1
    This method will return supported language list in system setting, not from application – Yugy Jun 09 '21 at 14:23
1

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

AAnkit
  • 27,299
  • 12
  • 60
  • 71
  • 1
    same as user370305, i want to know all available languages in my app – VinceFR Jul 23 '12 at 10:57
  • 2
    "You can then call getDisplayLanguage() to know which language is getting shown." - if your app supports only English, but device is set to French, you get `français` this way, even though the app is showing English texts – arekolek Feb 12 '19 at 16:33
0

Are you talking about this?

String language = Locale.getDefault().getDisplayLanguage();
user370305
  • 108,599
  • 23
  • 164
  • 151
  • 1
    no, this method just return the default language, I want to know all the languages specified in my res folder – VinceFR Jul 23 '12 at 10:55
0

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

Codelaby
  • 2,604
  • 1
  • 25
  • 25
-2

Do you mean:

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

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

  • 6
    I don't want to have the languages that my device have but the language that my application have – VinceFR Jul 26 '12 at 08:07