parent
6a0a419f0c
commit
a7aa980e58
@ -0,0 +1,40 @@
|
||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.MainThread;
|
||||
import android.support.v4.os.ConfigurationCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class DynamicLanguageActivityHelper {
|
||||
|
||||
private static final String TAG = Log.tag(DynamicLanguageActivityHelper.class);
|
||||
|
||||
private static String reentryProtection;
|
||||
|
||||
/**
|
||||
* If the activity isn't in the specified language, it will restart the activity.
|
||||
*/
|
||||
@MainThread
|
||||
public static void recreateIfNotInCorrectLanguage(Activity activity, String language) {
|
||||
Locale currentActivityLocale = ConfigurationCompat.getLocales(activity.getResources().getConfiguration()).get(0);
|
||||
Locale selectedLocale = LocaleParser.findBestMatchingLocaleForLanguage(language);
|
||||
|
||||
if (currentActivityLocale.equals(selectedLocale)) {
|
||||
reentryProtection = "";
|
||||
return;
|
||||
}
|
||||
|
||||
String reentryKey = activity.getClass().getName() + ":" + selectedLocale;
|
||||
if (!reentryKey.equals(reentryProtection)) {
|
||||
reentryProtection = reentryKey;
|
||||
Log.d(TAG, String.format("Activity Locale %s, Selected locale %s, restarting", currentActivityLocale, selectedLocale));
|
||||
activity.recreate();
|
||||
} else {
|
||||
Log.d(TAG, String.format("Skipping recreate as looks like looping, Activity Locale %s, Selected locale %s", currentActivityLocale, selectedLocale));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Updates a context with an alternative language.
|
||||
*/
|
||||
public final class DynamicLanguageContextWrapper {
|
||||
|
||||
public static Context updateContext(Context context, String language) {
|
||||
final Locale newLocale = LocaleParser.findBestMatchingLocaleForLanguage(language);
|
||||
|
||||
Locale.setDefault(newLocale);
|
||||
|
||||
final Resources resources = context.getResources();
|
||||
final Configuration config = resources.getConfiguration();
|
||||
final Configuration newConfig = copyWithNewLocale(config, newLocale);
|
||||
|
||||
resources.updateConfiguration(newConfig, resources.getDisplayMetrics());
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
private static Configuration copyWithNewLocale(Configuration config, Locale locale) {
|
||||
final Configuration copy = new Configuration(config);
|
||||
copy.setLocale(locale);
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public final class LanguageString {
|
||||
|
||||
private LanguageString() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param languageString String in format language_REGION, e.g. en_US
|
||||
* @return Locale, or null if cannot parse
|
||||
*/
|
||||
@Nullable
|
||||
public static Locale parseLocale(@Nullable String languageString) {
|
||||
if (languageString == null || languageString.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final Locale locale = createLocale(languageString);
|
||||
|
||||
if (!isValid(locale)) {
|
||||
return null;
|
||||
} else {
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
private static Locale createLocale(@NonNull String languageString) {
|
||||
final String language[] = languageString.split("_");
|
||||
if (language.length == 2) {
|
||||
return new Locale(language[0], language[1]);
|
||||
} else {
|
||||
return new Locale(language[0]);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isValid(@NonNull Locale locale) {
|
||||
try {
|
||||
return locale.getISO3Language() != null && locale.getISO3Country() != null;
|
||||
} catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
||||
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.os.ConfigurationCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
final class LocaleParser {
|
||||
|
||||
private LocaleParser() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a language, gets the best choice from the apps list of supported languages and the
|
||||
* Systems set of languages.
|
||||
*/
|
||||
static Locale findBestMatchingLocaleForLanguage(@Nullable String language) {
|
||||
final Locale locale = LanguageString.parseLocale(language);
|
||||
if (appSupportsTheExactLocale(locale)) {
|
||||
return locale;
|
||||
} else {
|
||||
return findBestSystemLocale();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean appSupportsTheExactLocale(@Nullable Locale locale) {
|
||||
if (locale == null) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.asList(BuildConfig.LANGUAGES).contains(locale.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first preferred language the app supports.
|
||||
*/
|
||||
private static Locale findBestSystemLocale() {
|
||||
final Configuration config = Resources.getSystem().getConfiguration();
|
||||
|
||||
final Locale firstMatch = ConfigurationCompat.getLocales(config)
|
||||
.getFirstMatch(BuildConfig.LANGUAGES);
|
||||
|
||||
if (firstMatch != null) {
|
||||
return firstMatch;
|
||||
}
|
||||
|
||||
return Locale.ENGLISH;
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package org.thoughtcrime.securesms.logging;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public final class LogTest {
|
||||
|
||||
@Test
|
||||
public void tag_short_class_name() {
|
||||
assertEquals("MyClass", Log.tag(MyClass.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tag_23_character_class_name() {
|
||||
String tag = Log.tag(TwentyThreeCharacters23.class);
|
||||
assertEquals("TwentyThreeCharacters23", tag);
|
||||
assertEquals(23, tag.length());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tag_24_character_class_name() {
|
||||
assertEquals(24, TwentyFour24Characters24.class.getSimpleName().length());
|
||||
String tag = Log.tag(TwentyFour24Characters24.class);
|
||||
assertEquals("TwentyFour24Characters2", tag);
|
||||
assertEquals(23, Log.tag(TwentyThreeCharacters23.class).length());
|
||||
}
|
||||
|
||||
private class MyClass {
|
||||
}
|
||||
|
||||
private class TwentyThreeCharacters23 {
|
||||
}
|
||||
|
||||
private class TwentyFour24Characters24 {
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public final class LanguageStringTest {
|
||||
|
||||
private final Locale expected;
|
||||
private final String input;
|
||||
|
||||
@Parameterized.Parameters
|
||||
public static Collection<Object[]> data() {
|
||||
return Arrays.asList(new Object[][]{
|
||||
|
||||
/* Language */
|
||||
{ new Locale("en"), "en" },
|
||||
{ new Locale("de"), "de" },
|
||||
{ new Locale("fr"), "FR" },
|
||||
|
||||
/* Language and region */
|
||||
{ new Locale("en", "US"), "en_US" },
|
||||
{ new Locale("es", "US"), "es_US" },
|
||||
{ new Locale("es", "MX"), "es_MX" },
|
||||
{ new Locale("es", "MX"), "es_mx" },
|
||||
{ new Locale("de", "DE"), "de_DE" },
|
||||
|
||||
/* Not parsable input */
|
||||
{ null, null },
|
||||
{ null, "" },
|
||||
{ null, "zz" },
|
||||
{ null, "zz_ZZ" },
|
||||
{ null, "fr_ZZ" },
|
||||
{ null, "zz_FR" },
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public LanguageStringTest(Locale expected, String input) {
|
||||
this.expected = expected;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parse() {
|
||||
assertEquals(expected, LanguageString.parseLocale(input));
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package org.thoughtcrime.securesms.util.dynamiclanguage;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.thoughtcrime.securesms.BuildConfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, application = Application.class)
|
||||
public final class LocaleParserTest {
|
||||
|
||||
@Test
|
||||
public void findBestMatchingLocaleForLanguage_all_build_config_languages_can_be_resolved() {
|
||||
for (String lang : buildConfigLanguages()) {
|
||||
Locale locale = LocaleParser.findBestMatchingLocaleForLanguage(lang);
|
||||
assertEquals(lang, locale.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "fr")
|
||||
public void findBestMatchingLocaleForLanguage_a_non_build_config_language_defaults_to_device_value_which_is_supported_directly() {
|
||||
String unsupportedLanguage = getUnsupportedLanguage();
|
||||
assertEquals(Locale.FRENCH, LocaleParser.findBestMatchingLocaleForLanguage(unsupportedLanguage));
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "en-rCA")
|
||||
public void findBestMatchingLocaleForLanguage_a_non_build_config_language_defaults_to_device_value_which_is_not_supported_directly() {
|
||||
String unsupportedLanguage = getUnsupportedLanguage();
|
||||
assertEquals(Locale.CANADA, LocaleParser.findBestMatchingLocaleForLanguage(unsupportedLanguage));
|
||||
}
|
||||
|
||||
private static String getUnsupportedLanguage() {
|
||||
String unsupportedLanguage = "af";
|
||||
assertFalse("Language should be an unsupported one", buildConfigLanguages().contains(unsupportedLanguage));
|
||||
return unsupportedLanguage;
|
||||
}
|
||||
|
||||
private static List<String> buildConfigLanguages() {
|
||||
return Arrays.asList(BuildConfig.LANGUAGES);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue