VinceFR
VinceFR

Reputation: 2571

get the application's resources languages

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

Upvotes: 31

Views: 11161

Answers (11)

Vinay John
Vinay John

Reputation: 1029

Kotlin solution : (id is the resource id of a string that is used in all locales)

fun getAppLanguages(id: Int): Set<String> {
    val dm = resources.displayMetrics
    val conf: Configuration = resources.configuration
    val originalLocale = conf.locales.get(0)
    conf.setLocale(Locale.ENGLISH)
    val reference = resources.getString(id)

    val result: MutableSet<String> = HashSet()
    result.add(Locale.ENGLISH.language)

    for (loc in resources.assets.locales) {
        if (loc.isEmpty()) continue
        val locale: Locale = Locale.forLanguageTag(loc)
        conf.setLocale(locale)
        if (reference != Resources(resources.assets, dm, conf).getString(id)) {
            result.add(locale.getLanguage())
        }
    }
    conf.setLocale(originalLocale)
    return result
}

Upvotes: 1

BorisJ
BorisJ

Reputation: 29

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.

Upvotes: 2

Bob bobbington
Bob bobbington

Reputation: 1255

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

Upvotes: 10

Codelaby
Codelaby

Reputation: 2873

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

Upvotes: 0

Mendhak
Mendhak

Reputation: 8785

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.

Upvotes: 10

pawelzieba
pawelzieba

Reputation: 16082

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

Upvotes: 16

injecteer
injecteer

Reputation: 20699

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

Upvotes: 4

AAnkit
AAnkit

Reputation: 27549

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

Upvotes: 1

hackbod
hackbod

Reputation: 91331

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.

Upvotes: 10

Bright Great
Bright Great

Reputation: 29

Do you mean:

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

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

Upvotes: -3

user370305
user370305

Reputation: 109237

Are you talking about this?

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

Upvotes: 0

Related Questions