Skip to content

Instantly share code, notes, and snippets.

@bleeding182
Last active September 15, 2018 10:19
Show Gist options
  • Save bleeding182/a7664adbbf087ff0a58967e332a8ed14 to your computer and use it in GitHub Desktop.
Save bleeding182/a7664adbbf087ff0a58967e332a8ed14 to your computer and use it in GitHub Desktop.
Resolve app Localization
package com.davidmedenjak.babel;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Build;
import android.os.LocaleList;
import android.support.annotation.NonNull;
import android.support.v4.os.LocaleListCompat;
import android.util.DisplayMetrics;
import java.util.Locale;
/**
* Helps apply the correct locale in case of the default locale ({@code res/values}) being used.
*/
public class BabelFish {
/**
* Verify that the current locale is supported by this app, otherwise override the locale with the apps default.
* <p>
* Call this in {@code attachBaseContext} of your Application, Activity, and potentially Service.
*
* @param base the context to wrap
* @param defaultLanguage the default language used for `res/values`
* @param supportedLanguages the supported languages of your app, as BCP 47 language tags
* @return a context with resources that map to the apps supported languages
* @see android.app.Application#attachBaseContext(Context)
* @see android.app.Activity#attachBaseContext(Context)
* @see android.app.Service#attachBaseContext(Context)
*/
public static Context translate(
@NonNull Context base,
@NonNull String defaultLanguage,
String[] supportedLanguages
) {
final Configuration configuration = base.getResources().getConfiguration();
final Locale currentUserLocale = getCurrentUserLocale(configuration);
final Locale defaultLocale = new Locale(defaultLanguage);
if (isUserLocaleSupported(defaultLocale, supportedLanguages, currentUserLocale)) {
// if our app supports one of the users language it will be selected
// since we don't have to override anything, lets just return the context
return base;
}
return overrideLocales(base, configuration, defaultLocale);
}
private static Context overrideLocales(@NonNull Context base, Configuration configuration, Locale defaultLocale) {
final Configuration overrideConfiguration = overrideLocale(configuration, defaultLocale);
// e.g. DateUtils up to ? uses Resources.getSystem() to format some of their texts.
// to prevent a mix of different locales we override this as well
final DisplayMetrics displayMetrics = base.getResources().getDisplayMetrics();
//noinspection deprecation
Resources.getSystem().updateConfiguration(overrideConfiguration, displayMetrics);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return base.createConfigurationContext(overrideConfiguration);
} else {
//noinspection deprecation
base.getResources().updateConfiguration(overrideConfiguration, displayMetrics);
return base;
}
}
private static boolean isUserLocaleSupported(Locale defaultLanguage, String[] supportedLanguages, Locale currentUserLocale) {
if (currentUserLocale.equals(defaultLanguage)) {
return true;
}
// we have to include `defaultLanguage` since the support library will always assume
// that the locale matches when there is only one entry
final Locale match = LocaleListCompat.create(currentUserLocale, defaultLanguage)
.getFirstMatch(supportedLanguages);
return match != null && (!match.equals(defaultLanguage));
}
private static Locale getCurrentUserLocale(Configuration configuration) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return configuration.getLocales().get(0);
} else {
//noinspection deprecation
return configuration.locale;
}
}
private static Configuration overrideLocale(Configuration configuration, Locale defaultLocale) {
final Configuration overrideConfiguration = new Configuration();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
overrideLocaleN(defaultLocale, overrideConfiguration, configuration.getLocales());
} else {
overrideLocaleCompat(defaultLocale, overrideConfiguration);
}
return overrideConfiguration;
}
@TargetApi(Build.VERSION_CODES.N)
private static void overrideLocaleN(Locale defaultLocale, Configuration overrideConfiguration, LocaleList userLocales) {
final LocaleList adaptedLocales = copyLocalesWithDefault(defaultLocale, userLocales);
LocaleList.setDefault(adaptedLocales);
overrideConfiguration.setLocales(adaptedLocales);
}
@NonNull
@TargetApi(Build.VERSION_CODES.N)
private static LocaleList copyLocalesWithDefault(Locale defaultLocale, LocaleList userLocales) {
final Locale[] locales = new Locale[userLocales.size() + 1];
locales[0] = defaultLocale;
for (int i = 1; i < locales.length; i++) {
locales[i] = userLocales.get(i - 1);
}
return new LocaleList(locales);
}
private static void overrideLocaleCompat(Locale defaultLocale, Configuration overrideConfiguration) {
Locale.setDefault(defaultLocale);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
overrideConfiguration.setLocale(defaultLocale);
} else {
overrideConfiguration.locale = defaultLocale;
}
}
}
task declareSupportedLocales {
doLast {
def defaultLocale = new Locale("en")
def foundLocales = [defaultLocale]
android.sourceSets.main.res.srcDirs.each { file ->
fileTree(dir: file, include: 'values-*').visit {
if (it.isDirectory()) {
def config = it.name.replaceAll('values-', '')
// valid directories may contain mcc/mnc values before the locale
if (config.startsWith("mcc") || config.startsWith("mnc")) {
def splitPosition = config.indexOf('-')
if (splitPosition >= 0) {
config = config.substring(splitPosition + 1)
}
}
try {
def locale
// valid directories may also have parts after the locale
if (config.startsWith("b+")) {
def splitPosition = config.indexOf('-')
if (splitPosition >= 0) {
config = config.substring(0, splitPosition)
}
locale = Locale.forLanguageTag(config.substring(2))
} else {
def parts = config.split('-')
if (parts.size() == 1 || !parts[1].startsWith('r')) {
locale = new Locale(parts[0])
} else {
locale = new Locale(parts[0], parts[1].substring(1))
}
}
if (locale.getISO3Language() != null) {
foundLocales << locale
}
} catch (Exception ignored) {
}
}
}
}
def formattedLocales = foundLocales.collect {
it.toLanguageTag()
}.toSet().sort().join("\",\"")
android.defaultConfig.buildConfigField "String", "DEFAULT_LANGUAGE", "\"$defaultLocale\""
android.defaultConfig.buildConfigField "String[]", "SUPPORTED_LANGUAGES", "new String[]{\"" + formattedLocales + "\"}"
android.defaultConfig.resConfigs foundLocales.collect { it.toString() }
}
}
preBuild.dependsOn declareSupportedLocales
android {
defaultConfig {
def defaultLocale = "en"
def locales = [defaultLocale, "de-rAT"]
buildConfigField "String", "DEFAULT_LANGUAGE", "\"$defaultLocale\""
buildConfigField "String[]", "SUPPORTED_LANGUAGES", "new String[]{\"" + locales.collect {it.replace("-r", "-")}.join("\",\"") + "\"}"
resConfigs locales
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment