Last active
September 15, 2018 10:19
-
-
Save bleeding182/a7664adbbf087ff0a58967e332a8ed14 to your computer and use it in GitHub Desktop.
Resolve app Localization
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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