From e8073599aeb6fcaf8108d57f832d82305505c7df Mon Sep 17 00:00:00 2001 From: Felix Ableitner Date: Wed, 19 Jul 2017 12:24:45 +0900 Subject: [PATCH] Add preference to change app language (fixes #742) --- src/main/AndroidManifest.xml | 3 +- .../syncthingandroid/SyncthingApp.java | 14 ++ .../activities/SettingsActivity.java | 12 ++ .../syncthingandroid/util/Languages.java | 192 ++++++++++++++++++ src/main/res/values/strings.xml | 6 + src/main/res/xml/app_settings.xml | 5 + 6 files changed, 231 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/nutomic/syncthingandroid/SyncthingApp.java create mode 100644 src/main/java/com/nutomic/syncthingandroid/util/Languages.java diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 0b4f1cca..4fa20b19 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -24,7 +24,8 @@ android:theme="@style/Theme.Syncthing" android:description="@string/app_description" android:supportsRtl="true" - android:installLocation="internalOnly"> + android:installLocation="internalOnly" + android:name=".SyncthingApp"> diff --git a/src/main/java/com/nutomic/syncthingandroid/SyncthingApp.java b/src/main/java/com/nutomic/syncthingandroid/SyncthingApp.java new file mode 100644 index 00000000..8c4eca20 --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/SyncthingApp.java @@ -0,0 +1,14 @@ +package com.nutomic.syncthingandroid; + +import android.app.Application; + +import com.nutomic.syncthingandroid.util.Languages; + +public class SyncthingApp extends Application { + + @Override + public void onCreate() { + super.onCreate(); + new Languages(this).setLanguage(this); + } +} diff --git a/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java b/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java index a58280d6..b0aa12de 100644 --- a/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java +++ b/src/main/java/com/nutomic/syncthingandroid/activities/SettingsActivity.java @@ -7,6 +7,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.EditTextPreference; +import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceFragment; import android.preference.PreferenceScreen; @@ -22,6 +23,7 @@ import com.nutomic.syncthingandroid.model.Device; import com.nutomic.syncthingandroid.model.Options; import com.nutomic.syncthingandroid.service.RestApi; import com.nutomic.syncthingandroid.service.SyncthingService; +import com.nutomic.syncthingandroid.util.Languages; import com.nutomic.syncthingandroid.views.WifiSsidPreference; import java.security.InvalidParameterException; @@ -105,6 +107,16 @@ public class SettingsActivity extends SyncthingActivity { mSyncOnlyWifi.setEnabled(mAlwaysRunInBackground.isChecked()); mSyncOnlyOnSSIDs.setEnabled(mSyncOnlyWifi.isChecked()); + ListPreference languagePref = (ListPreference) findPreference(Languages.PREFERENCE_LANGUAGE); + Languages languages = new Languages(getActivity()); + languagePref.setDefaultValue(Languages.USE_SYSTEM_DEFAULT); + languagePref.setEntries(languages.getAllNames()); + languagePref.setEntryValues(languages.getSupportedLocales()); + languagePref.setOnPreferenceChangeListener((p, o) -> { + languages.forceChangeLanguage(getActivity(), (String) o); + return false; + }); + mDeviceName = (EditTextPreference) findPreference("deviceName"); mListenAddresses = (EditTextPreference) findPreference("listenAddresses"); mMaxRecvKbps = (EditTextPreference) findPreference("maxRecvKbps"); diff --git a/src/main/java/com/nutomic/syncthingandroid/util/Languages.java b/src/main/java/com/nutomic/syncthingandroid/util/Languages.java new file mode 100644 index 00000000..ddb8d05e --- /dev/null +++ b/src/main/java/com/nutomic/syncthingandroid/util/Languages.java @@ -0,0 +1,192 @@ +package com.nutomic.syncthingandroid.util; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.os.Build; +import android.preference.PreferenceManager; +import android.text.TextUtils; +import android.util.Log; + +import com.nutomic.syncthingandroid.R; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +/** + * Based on https://gitlab.com/fdroid/fdroidclient/blob/master/app/src/main/java/org/fdroid/fdroid/Languages.java + */ +public final class Languages { + public static final String TAG = "Languages"; + + public static final String USE_SYSTEM_DEFAULT = ""; + + private static final Locale DEFAULT_LOCALE; + public static final String PREFERENCE_LANGUAGE = "pref_current_language"; + + private final SharedPreferences mPreferences; + private static Map mAvailableLanguages; + + static { + DEFAULT_LOCALE = Locale.getDefault(); + } + + public Languages(Context context) { + Map tmpMap = new TreeMap<>(); + for (Locale locale : Arrays.asList(LOCALES_TO_TEST)) { + tmpMap.put(locale.getLanguage(), locale.getDisplayLanguage(locale)); + } + + // remove the current system language from the menu + tmpMap.remove(Locale.getDefault().getLanguage()); + + /* SYSTEM_DEFAULT is a fake one for displaying in a chooser menu. */ + tmpMap.put(USE_SYSTEM_DEFAULT, context.getString(R.string.pref_language_default)); + mAvailableLanguages = Collections.unmodifiableMap(tmpMap); + mPreferences = PreferenceManager.getDefaultSharedPreferences(context); + } + + /** + * Handles setting the language if it is different than the current language, + * or different than the current system-wide locale. The preference is cleared + * if the language matches the system-wide locale or "System Default" is chosen. + */ + @TargetApi(17) + public void setLanguage(Context context) { + if (Build.VERSION.SDK_INT >= 24) { + Log.d(TAG, "Languages.setLanguage() ignored on >= android-24"); + mPreferences.edit().remove(PREFERENCE_LANGUAGE).apply(); + return; + } + String language = mPreferences.getString(PREFERENCE_LANGUAGE, null); + Locale locale; + if (TextUtils.equals(language, DEFAULT_LOCALE.getLanguage())) { + mPreferences.edit().remove(PREFERENCE_LANGUAGE).apply(); + locale = DEFAULT_LOCALE; + } else if (language == null || language.equals(USE_SYSTEM_DEFAULT)) { + mPreferences.edit().remove(PREFERENCE_LANGUAGE).apply(); + locale = DEFAULT_LOCALE; + } else { + /* handle locales with the country in it, i.e. zh_CN, zh_TW, etc */ + String[] localeSplit = language.split("_"); + if (localeSplit.length > 1) { + locale = new Locale(localeSplit[0], localeSplit[1]); + } else { + locale = new Locale(language); + } + } + Locale.setDefault(locale); + + final Resources resources = context.getResources(); + Configuration config = resources.getConfiguration(); + if (Build.VERSION.SDK_INT >= 17) { + config.setLocale(locale); + } else { + config.locale = locale; + } + resources.updateConfiguration(config, resources.getDisplayMetrics()); + } + + /** + * Force reload the {@link Activity to make language changes take effect.} + * + * @param activity the {@code Activity} to force reload + */ + @SuppressLint("ApplySharedPref") + public void forceChangeLanguage(Activity activity, String newLanguage) { + mPreferences.edit().putString(PREFERENCE_LANGUAGE, newLanguage).commit(); + setLanguage(activity); + if (Build.VERSION.SDK_INT >= 24) { + Log.d(TAG, "Languages.forceChangeLanguage() ignored on >= android-24"); + return; + } + Intent intent = activity.getIntent(); + if (intent == null) { // when launched as LAUNCHER + return; + } + intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); + activity.finish(); + activity.overridePendingTransition(0, 0); + activity.startActivity(intent); + activity.overridePendingTransition(0, 0); + } + + /** + * @return an array of the names of all the supported languages, sorted to + * match what is returned by {@link Languages#getSupportedLocales()}. + */ + public String[] getAllNames() { + return mAvailableLanguages.values().toArray(new String[mAvailableLanguages.size()]); + } + + /** + * @return sorted list of supported locales. + */ + public String[] getSupportedLocales() { + Set keys = mAvailableLanguages.keySet(); + return keys.toArray(new String[keys.size()]); + } + + private String capitalize(final String line) { + return Character.toUpperCase(line.charAt(0)) + line.substring(1); + } + + public static final Locale[] LOCALES_TO_TEST = { + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + Locale.ITALIAN, + Locale.JAPANESE, + Locale.KOREAN, + Locale.SIMPLIFIED_CHINESE, + Locale.TRADITIONAL_CHINESE, + new Locale("af"), + new Locale("ar"), + new Locale("be"), + new Locale("bg"), + new Locale("ca"), + new Locale("cs"), + new Locale("da"), + new Locale("el"), + new Locale("es"), + new Locale("eo"), + new Locale("et"), + new Locale("eu"), + new Locale("fa"), + new Locale("fi"), + new Locale("he"), + new Locale("hi"), + new Locale("hu"), + new Locale("hy"), + new Locale("id"), + new Locale("is"), + new Locale("it"), + new Locale("ml"), + new Locale("my"), + new Locale("nb"), + new Locale("nl"), + new Locale("pl"), + new Locale("pt"), + new Locale("ro"), + new Locale("ru"), + new Locale("sc"), + new Locale("sk"), + new Locale("sn"), + new Locale("sr"), + new Locale("sv"), + new Locale("th"), + new Locale("tr"), + new Locale("uk"), + new Locale("vi"), + }; + +} diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 083a1a39..9368312a 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -271,6 +271,12 @@ Please report any problems you encounter via Github. None + Language + + Change the app language + + Default Language + None TrashCan diff --git a/src/main/res/xml/app_settings.xml b/src/main/res/xml/app_settings.xml index 6ea918a7..a7264c02 100644 --- a/src/main/res/xml/app_settings.xml +++ b/src/main/res/xml/app_settings.xml @@ -49,6 +49,11 @@ android:summary="@string/notification_type_summary" android:defaultValue="low_priority" /> + +