diff --git a/briar-android/.gitignore b/briar-android/.gitignore index 2efc20c3651be2ac308b8fe13c0f3e6f56cb5554..4d702fcd882eb5ddecacedeb92bfddcb6608ca0d 100644 --- a/briar-android/.gitignore +++ b/briar-android/.gitignore @@ -4,3 +4,4 @@ build local.properties .settings src/main/assets/*.zip +src/main/res/values-iw diff --git a/briar-android/build.gradle b/briar-android/build.gradle index 3c5db75c2ef0c300f9daf09dc5a497386000ad0b..b335e50e724d4dafa7b8fd08c8ea37f2d40476fa 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -285,3 +285,46 @@ android { warning 'ExtraTranslation' } } + +task verifyTranslations { + doLast { + def file = "briar-android/src/main/res/values/arrays.xml" + def arrays = new XmlParser().parse(file) + def lc = arrays.children().find { it.@name == "pref_language_values" } + def translations = [] + lc.children().each { value -> translations.add(value.text()) } + + def folders = ["default", "en-US"] + new File("briar-android/src/main/res").eachDir { dir -> + if (dir.name.startsWith("values-") && !dir.name.endsWith("night")) { + folders.add(dir.name.substring(7).replace("-r", "-")) + } + } + folders.each { n -> + if (!translations.remove(n) && n != 'iw') { + throw new GradleException("Translation " + n + " is missing in $file") + } + } + if (translations.size() != 0) + throw new GradleException("Translations\n" + translations.join("\n") + + "\nhave no matching value folder") + + // Some devices use iw instead of he for hebrew + def hebrew_legacy = new File("briar-android/src/main/res/values-iw") + def hebrew = new File("briar-android/src/main/res/values-he") + // Copy values-he to values-iw + if (hebrew.exists()) { + hebrew_legacy.mkdir() + copy { + from 'src/main/res/values-he' + into 'src/main/res/values-iw' + } + } + } +} + +project.afterEvaluate { + preBuild.dependsOn.add(verifyTranslations) +} + + diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java index 97e19062a941fde22b5260acec8b25345795c102..a2ff968a7e61c7df5f00bccc6c2325ad3411d404 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarApplicationImpl.java @@ -2,9 +2,12 @@ package org.briarproject.briar.android; import android.app.Application; import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; import android.os.StrictMode; import android.os.StrictMode.ThreadPolicy; import android.os.StrictMode.VmPolicy; +import android.preference.PreferenceManager; import org.acra.ACRA; import org.acra.ReportingInteractionMode; @@ -75,7 +78,12 @@ public class BriarApplicationImpl extends Application @Override protected void attachBaseContext(Context base) { - super.attachBaseContext(base); + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(base); + // Loading the language needs to be done here. + Localizer.initialize(prefs); + super.attachBaseContext( + Localizer.getInstance().setLocale(base)); ACRA.init(this); } @@ -108,6 +116,12 @@ public class BriarApplicationImpl extends Application AndroidEagerSingletons.initEagerSingletons(applicationComponent); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Localizer.getInstance().setLocale(this); + } + private void enableStrictMode() { ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder(); threadPolicy.detectAll(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java index f3cc72c5b7a7ddbb158739cf8c2eae465b246f2b..b6aaad4b930606ec4faf7735812ef3849e697b42 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/BriarService.java @@ -168,6 +168,11 @@ public class BriarService extends Service { registerReceiver(receiver, filter); } + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(Localizer.getInstance().setLocale(base)); + } + private void showStartupFailureNotification(StartResult result) { androidExecutor.runOnUiThread(() -> { NotificationCompat.Builder b = new NotificationCompat.Builder( diff --git a/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java new file mode 100644 index 0000000000000000000000000000000000000000..3f305c3f9b31c2cc99ac875decf959ac6f990adf --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/Localizer.java @@ -0,0 +1,79 @@ +package org.briarproject.briar.android; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.Locale; + +import javax.annotation.Nullable; + +import static android.os.Build.VERSION.SDK_INT; +import static org.briarproject.briar.android.settings.SettingsFragment.LANGUAGE; + +@NotNullByDefault +public class Localizer { + + // Locking: class + @Nullable + private static Localizer INSTANCE; + @Nullable + private final Locale locale; + + private Localizer(SharedPreferences sharedPreferences) { + locale = getLocaleFromTag( + sharedPreferences.getString(LANGUAGE, "default")); + } + + public static synchronized void initialize(SharedPreferences prefs) { + if (INSTANCE == null) + INSTANCE = new Localizer(prefs); + } + + public static synchronized Localizer getInstance() { + if (INSTANCE == null) + throw new IllegalStateException("Localizer not initialized"); + return INSTANCE; + } + + // Get Locale from BCP-47 tag + @Nullable + public static Locale getLocaleFromTag(String tag) { + if (tag.equals("default")) + return null; + if (SDK_INT >= 21) { + return Locale.forLanguageTag(tag); + } + if (tag.contains("-")) { + String[] langArray = tag.split("-"); + return new Locale(langArray[0], langArray[1]); + } else + return new Locale(tag); + } + + public Context setLocale(Context context) { + if (locale == null) + return context; + Resources res = context.getResources(); + Configuration conf = res.getConfiguration(); + Locale currentLocale; + if (SDK_INT >= 24) { + currentLocale = conf.getLocales().get(0); + } else + currentLocale = conf.locale; + if (locale.equals(currentLocale)) + return context; + Locale.setDefault(locale); + if (SDK_INT >= 17) { + conf.setLocale(locale); + context.createConfigurationContext(conf); + } else + conf.locale = locale; + //noinspection deprecation + res.updateConfiguration(conf, res.getDisplayMetrics()); + return context; + } +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java index 7c8f5061ae0c8777f3bebd1679174dba8e545a39..c693ccbfc27443679d3582f8e7492ba7e2da69e1 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/BaseActivity.java @@ -1,5 +1,6 @@ package org.briarproject.briar.android.activity; +import android.content.Context; import android.os.Bundle; import android.os.IBinder; import android.support.annotation.LayoutRes; @@ -18,6 +19,7 @@ import org.briarproject.briar.R; import org.briarproject.briar.android.AndroidComponent; import org.briarproject.briar.android.BriarApplication; import org.briarproject.briar.android.DestroyableContext; +import org.briarproject.briar.android.Localizer; import org.briarproject.briar.android.controller.ActivityLifecycleController; import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.fragment.BaseFragment; @@ -84,6 +86,12 @@ public abstract class BaseActivity extends AppCompatActivity } } + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext( + Localizer.getInstance().setLocale(base)); + } + public ActivityComponent getActivityComponent() { return activityComponent; } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java index af09db700455912c9c9946100a02604937cb43cc..e7800f1db097f7b5904bf6e5e6623da908770969 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/navdrawer/NavDrawerActivity.java @@ -68,6 +68,7 @@ public class NavDrawerActivity extends BriarActivity implements public static final String INTENT_GROUPS = "intent_groups"; public static final String INTENT_FORUMS = "intent_forums"; public static final String INTENT_BLOGS = "intent_blogs"; + public static final String INTENT_SIGN_OUT = "intent_sign_out"; private static final Logger LOG = Logger.getLogger(NavDrawerActivity.class.getName()); @@ -99,6 +100,8 @@ public class NavDrawerActivity extends BriarActivity implements R.id.nav_btn_contacts); } else if (intent.getBooleanExtra(INTENT_BLOGS, false)) { startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs); + } else if (intent.getBooleanExtra(INTENT_SIGN_OUT, false)) { + signOut(false); } setIntent(null); } @@ -225,12 +228,12 @@ public class NavDrawerActivity extends BriarActivity implements finish(); } else if (fm.getBackStackEntryCount() == 0 && fm.findFragmentByTag(ContactListFragment.TAG) == null) { - /* - * This makes sure that the first fragment (ContactListFragment) the - * user sees is the same as the last fragment the user sees before - * exiting. This models the typical Google navigation behaviour such - * as in Gmail/Inbox. - */ + /* + * This makes sure that the first fragment (ContactListFragment) the + * user sees is the same as the last fragment the user sees before + * exiting. This models the typical Google navigation behaviour such + * as in Gmail/Inbox. + */ startFragment(ContactListFragment.newInstance(), R.id.nav_btn_contacts); } else { diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java index 37870a98318a627e94f4b72edd0d0217c9a3c2fb..48b4b7fa9023082623a678fbcbb7ba81c60a6ea5 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsActivity.java @@ -36,5 +36,4 @@ public class SettingsActivity extends BriarActivity { } return false; } - } diff --git a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java index c69dd65deb44c9556650711472f70a72a02056b0..00546afd47b83630ddd4c96573bcc014b1a24271 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/settings/SettingsFragment.java @@ -1,6 +1,7 @@ package org.briarproject.briar.android.settings; import android.annotation.TargetApi; +import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.media.Ringtone; @@ -8,6 +9,7 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Bundle; import android.support.annotation.StringRes; +import android.support.v4.text.TextUtilsCompat; import android.support.v7.preference.CheckBoxPreference; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; @@ -30,8 +32,13 @@ import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; import org.briarproject.bramble.api.system.AndroidExecutor; import org.briarproject.bramble.util.StringUtils; import org.briarproject.briar.R; +import org.briarproject.briar.android.Localizer; +import org.briarproject.briar.android.navdrawer.NavDrawerActivity; import org.briarproject.briar.android.util.UserFeedback; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import java.util.logging.Logger; import javax.inject.Inject; @@ -50,6 +57,7 @@ import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS; import static android.provider.Settings.EXTRA_APP_PACKAGE; import static android.provider.Settings.EXTRA_CHANNEL_ID; import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; +import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_LTR; import static android.widget.Toast.LENGTH_SHORT; import static java.util.logging.Level.INFO; import static java.util.logging.Level.WARNING; @@ -58,6 +66,7 @@ import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS; import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD; import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE; +import static org.briarproject.briar.android.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT; import static org.briarproject.briar.api.android.AndroidNotificationManager.BLOG_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.CONTACT_CHANNEL_ID; import static org.briarproject.briar.api.android.AndroidNotificationManager.FORUM_CHANNEL_ID; @@ -80,11 +89,13 @@ public class SettingsFragment extends PreferenceFragmentCompat public static final String SETTINGS_NAMESPACE = "android-ui"; public static final String BT_NAMESPACE = BluetoothConstants.ID.getString(); public static final String TOR_NAMESPACE = TorConstants.ID.getString(); + public static final String LANGUAGE = "pref_key_language"; private static final Logger LOG = Logger.getLogger(SettingsFragment.class.getName()); private SettingsActivity listener; + private ListPreference language; private ListPreference enableBluetooth; private ListPreference torNetwork; private CheckBoxPreference notifyPrivateMessages; @@ -119,6 +130,8 @@ public class SettingsFragment extends PreferenceFragmentCompat public void onCreatePreferences(Bundle bundle, String s) { addPreferencesFromResource(R.xml.settings); + language = (ListPreference) findPreference(LANGUAGE); + setLanguageEntries(); enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); torNetwork = (ListPreference) findPreference("pref_key_tor_network"); notifyPrivateMessages = (CheckBoxPreference) findPreference( @@ -137,6 +150,7 @@ public class SettingsFragment extends PreferenceFragmentCompat setSettingsEnabled(false); + language.setOnPreferenceChangeListener(this); enableBluetooth.setOnPreferenceChangeListener(this); torNetwork.setOnPreferenceChangeListener(this); if (SDK_INT >= 21) { @@ -180,6 +194,51 @@ public class SettingsFragment extends PreferenceFragmentCompat eventBus.removeListener(this); } + private void setLanguageEntries() { + CharSequence[] tags = language.getEntryValues(); + List<CharSequence> entries = new ArrayList<>(tags.length); + List<CharSequence> entryValues = new ArrayList<>(tags.length); + for (CharSequence cs : tags) { + String tag = cs.toString(); + if (tag.equals("default")) { + entries.add(getString(R.string.pref_language_default)); + entryValues.add(tag); + continue; + } + Locale locale = Localizer.getLocaleFromTag(tag); + if (locale == null) + throw new IllegalStateException(); + // Exclude RTL locales on API < 17, they won't be laid out correctly + if (SDK_INT < 17 && !isLeftToRight(locale)) { + if (LOG.isLoggable(INFO)) + LOG.info("Skipping RTL locale " + tag); + continue; + } + String nativeName = locale.getDisplayName(locale); + // Fallback to English if the name is unknown in both native and + // current locale. + if (nativeName.equals(tag)) { + String tmp = locale.getDisplayLanguage(Locale.ENGLISH); + if (!tmp.isEmpty() && !tmp.equals(nativeName)) + nativeName = tmp; + } + // Prefix with LRM marker to prevent any RTL direction + entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase() + + nativeName.substring(1)); + entryValues.add(tag); + } + language.setEntries(entries.toArray(new CharSequence[0])); + language.setEntryValues(entryValues.toArray(new CharSequence[0])); + } + + private boolean isLeftToRight(Locale locale) { + // TextUtilsCompat returns the wrong direction for Hebrew on some phones + String language = locale.getLanguage(); + if (language.equals("iw") || language.equals("he")) return false; + int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale); + return direction == LAYOUT_DIRECTION_LTR; + } + private void loadSettings() { listener.runOnDbThread(() -> { try { @@ -312,41 +371,65 @@ public class SettingsFragment extends PreferenceFragmentCompat } @Override - public boolean onPreferenceChange(Preference preference, Object o) { - if (preference == enableBluetooth) { - boolean btSetting = Boolean.valueOf((String) o); + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == language) { + if (!language.getValue().equals(newValue)) + languageChanged((String) newValue); + return false; + } else if (preference == enableBluetooth) { + boolean btSetting = Boolean.valueOf((String) newValue); storeBluetoothSettings(btSetting); } else if (preference == torNetwork) { - int torSetting = Integer.valueOf((String) o); + int torSetting = Integer.valueOf((String) newValue); storeTorSettings(torSetting); } else if (preference == notifyPrivateMessages) { Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) o); + s.putBoolean(PREF_NOTIFY_PRIVATE, (Boolean) newValue); storeSettings(s); } else if (preference == notifyGroupMessages) { Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) o); + s.putBoolean(PREF_NOTIFY_GROUP, (Boolean) newValue); storeSettings(s); } else if (preference == notifyForumPosts) { Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) o); + s.putBoolean(PREF_NOTIFY_FORUM, (Boolean) newValue); storeSettings(s); } else if (preference == notifyBlogPosts) { Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_BLOG, (Boolean) o); + s.putBoolean(PREF_NOTIFY_BLOG, (Boolean) newValue); storeSettings(s); } else if (preference == notifyVibration) { Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) o); + s.putBoolean(PREF_NOTIFY_VIBRATION, (Boolean) newValue); storeSettings(s); } else if (preference == notifyLockscreen) { Settings s = new Settings(); - s.putBoolean(PREF_NOTIFY_LOCK_SCREEN, (Boolean) o); + s.putBoolean(PREF_NOTIFY_LOCK_SCREEN, (Boolean) newValue); storeSettings(s); } return true; } + private void languageChanged(String newValue) { + AlertDialog.Builder builder = + new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.pref_language_title); + builder.setMessage(R.string.pref_language_changed); + builder.setPositiveButton(R.string.sign_out_button, + (dialogInterface, i) -> { + language.setValue(newValue); + Intent intent = new Intent(getContext(), + NavDrawerActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(INTENT_SIGN_OUT, true); + getActivity().startActivity(intent); + getActivity().finish(); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setCancelable(false); + builder.show(); + } + private void storeTorSettings(int torSetting) { listener.runOnDbThread(() -> { try { diff --git a/briar-android/src/main/res/values/arrays.xml b/briar-android/src/main/res/values/arrays.xml index 3eea71ecd758e23340424437cb570c15378a65a4..978c98698025dbab54c2b7405e6d35b334180e47 100644 --- a/briar-android/src/main/res/values/arrays.xml +++ b/briar-android/src/main/res/values/arrays.xml @@ -18,4 +18,37 @@ <item>1</item> <item>2</item> </string-array> -</resources> \ No newline at end of file + <string-array name="pref_language_values"> + <item>default</item> + <item>en-US</item> + <item>ast</item> + <item>bg</item> + <item>br</item> + <item>ca</item> + <item>cs</item> + <item>de</item> + <item>es</item> + <item>eu</item> + <item>fa</item> + <item>fi</item> + <item>fr</item> + <item>gl</item> + <item>he</item> + <item>hi</item> + <item>it</item> + <item>ja</item> + <item>ms</item> + <item>nb</item> + <item>nl</item> + <item>oc</item> + <item>pl</item> + <item>pt-BR</item> + <item>ro</item> + <item>ru</item> + <item>sq</item> + <item>sr</item> + <item>sv</item> + <item>tr</item> + <item>zh-CN</item> + </string-array> +</resources> diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 1306feae1d116a66efc8a03d6209570eace1d42d..877c40e1f059b318a1c6ae15606f4d3ae99266f8 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -323,6 +323,12 @@ <string name="blogs_rss_feeds_manage_empty_state">No RSS feeds to show\n\nTap the + icon to import a feed</string> <string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string> + <!-- Settings Display --> + <string name="pref_language_title">Language & region</string> + <string name="pref_language_changed">This setting will take effect when you restart Briar. Please sign out and restart Briar.</string> + <string name="pref_language_default">System default</string> + <string name="display_settings_title">Display</string> + <!-- Settings Network --> <string name="network_settings_title">Networks</string> <string name="bluetooth_setting">Connect via Bluetooth</string> diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index 452815014309f41f110e49c745f551e1aeb07d0d..7b3181fa05dcf2bf827b1506b309786fc7cd8956 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -1,6 +1,17 @@ <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="@string/display_settings_title"> + + <ListPreference + android:defaultValue="default" + android:entryValues="@array/pref_language_values" + android:key="pref_key_language" + android:summary="%s" + android:title="@string/pref_language_title"/> + </PreferenceCategory> <PreferenceCategory android:layout="@layout/preferences_category" diff --git a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java index 6f232bef0a91ee669087f54aaec60428badc1be4..ae086abf3385fde2ede3dd979a0e73cf79525edc 100644 --- a/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java +++ b/briar-android/src/test/java/org/briarproject/briar/android/TestBriarApplication.java @@ -1,6 +1,8 @@ package org.briarproject.briar.android; import android.app.Application; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.briar.BriarCoreModule; @@ -27,6 +29,8 @@ public class TestBriarApplication extends Application super.onCreate(); LOG.info("Created"); + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + Localizer.initialize(prefs); applicationComponent = DaggerAndroidComponent.builder() .appModule(new AppModule(this)) .build();