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