diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 54486423f..4b3070379 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -13,7 +13,6 @@ before_script:
test:
script:
- cd app
- - ./tools/langs-list-check.py
- ./tools/check-string-format.py
- cd ..
- ./gradlew assemble -PdisablePreDex
diff --git a/RELEASE_CHECKLIST.md b/RELEASE_CHECKLIST.md
new file mode 100644
index 000000000..a0a7ae3dd
--- /dev/null
+++ b/RELEASE_CHECKLIST.md
@@ -0,0 +1,23 @@
+
+# Release Checklist
+
+This is the things that need to happen for all releases, alpha or stable:
+
+* pull translations from Weblate: ./tools/pull-trans.sh
+
+* rebase Weblate in its web interface, since we squash commits
+
+* update `versionCode` in _app/build.gradle_
+
+* make signed tag with version name
+
+* update _metadata/org.fdroid.fdroid.txt_ in _fdroiddata_
+
+## Stable releases
+
+For stable releases, there are a couple more steps to do __before__
+making the release tag:
+
+* update CHANGELOG.md
+
+* run `./tools/trim-incomplete-translations-for-release.py`
diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java
index 4d78f0af9..ed95dedef 100644
--- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java
+++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java
@@ -27,7 +27,6 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
-import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -36,16 +35,15 @@ import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.StrictMode;
-import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
-
import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiskCache;
import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
-
+import info.guardianproject.netcipher.NetCipher;
+import info.guardianproject.netcipher.proxy.OrbotHelper;
import org.acra.ACRA;
import org.acra.ReportingInteractionMode;
import org.acra.annotation.ReportsCrashes;
@@ -59,17 +57,13 @@ import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.net.ImageLoaderForUIL;
import org.fdroid.fdroid.net.WifiStateChangeService;
+import sun.net.www.protocol.bluetooth.Handler;
import java.net.URL;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.security.Security;
import java.util.List;
-import java.util.Locale;
-
-import info.guardianproject.netcipher.NetCipher;
-import info.guardianproject.netcipher.proxy.OrbotHelper;
-import sun.net.www.protocol.bluetooth.Handler;
@ReportsCrashes(mailTo = "reports@f-droid.org",
mode = ReportingInteractionMode.DIALOG,
@@ -82,8 +76,6 @@ public class FDroidApp extends Application {
public static final String SYSTEM_DIR_NAME = Environment.getRootDirectory().getAbsolutePath();
- private static Locale locale;
-
// for the local repo on this device, all static since there is only one
public static volatile int port;
public static volatile String ipAddressString;
@@ -181,25 +173,10 @@ public class FDroidApp extends Application {
repo = new Repo();
}
- public void updateLanguage() {
- Context ctx = getBaseContext();
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctx);
- String lang = prefs.getString(Preferences.PREF_LANGUAGE, "");
- locale = Utils.getLocaleFromAndroidLangTag(lang);
- applyLanguage();
- }
-
- private void applyLanguage() {
- Context ctx = getBaseContext();
- Configuration cfg = new Configuration();
- cfg.locale = locale == null ? Locale.getDefault() : locale;
- ctx.getResources().updateConfiguration(cfg, null);
- }
-
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
- applyLanguage();
+ Languages.setLanguage(this, Preferences.get().getLangauge(), false);
}
@Override
@@ -215,7 +192,9 @@ public class FDroidApp extends Application {
.penaltyLog()
.build());
}
- updateLanguage();
+ Preferences.setup(this);
+ Languages.setup(getClass(), R.string.pref_language_default);
+ Languages.setLanguage(this, Preferences.get().getLangauge(), false);
ACRA.init(this);
if (isAcraProcess()) {
@@ -224,7 +203,6 @@ public class FDroidApp extends Application {
PRNGFixes.apply();
- Preferences.setup(this);
curTheme = Preferences.get().getTheme();
Preferences.get().configureProxy();
@@ -325,13 +303,13 @@ public class FDroidApp extends Application {
/**
* Asks if the current process is "org.fdroid.fdroid:acra".
- *
+ *
* This is helpful for bailing out of the {@link FDroidApp#onCreate} method early, preventing
* problems that arise from executing the code twice. This happens due to the `android:process`
* statement in AndroidManifest.xml causes another process to be created to run
* {@link org.fdroid.fdroid.acra.CrashReportActivity}. This was causing lots of things to be
* started/run twice including {@link CleanCacheService} and {@link WifiStateChangeService}.
- *
+ *
* Note that it is not perfect, because some devices seem to not provide a list of running app
* processes when asked. In such situations, F-Droid may regress to the behaviour where some
* services may run twice and thus cause weirdness or slowness. However that is probably better
diff --git a/app/src/main/java/org/fdroid/fdroid/Languages.java b/app/src/main/java/org/fdroid/fdroid/Languages.java
new file mode 100644
index 000000000..5891d9123
--- /dev/null
+++ b/app/src/main/java/org/fdroid/fdroid/Languages.java
@@ -0,0 +1,285 @@
+package org.fdroid.fdroid;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.res.AssetManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+
+public final class Languages {
+ public static final String TAG = "Languages";
+
+ public static final String USE_SYSTEM_DEFAULT = "";
+
+ private static final Locale DEFAULT_LOCALE;
+ private static final Locale TIBETAN = new Locale("bo");
+ private static final Locale CHINESE_HONG_KONG = new Locale("zh", "HK");
+ private static final String DEFAULT_STRING = "System Default";
+
+ private static Locale locale;
+ private static Languages singleton;
+ private static Class> clazz;
+ private static int resId;
+ private static Map tmpMap = new TreeMap<>();
+ private static Map nameMap;
+
+ static {
+ DEFAULT_LOCALE = Locale.getDefault();
+ }
+
+ private Languages(Activity activity) {
+ AssetManager assets = activity.getAssets();
+ Configuration config = activity.getResources().getConfiguration();
+ // Resources() requires DisplayMetrics, but they are only needed for drawables
+ DisplayMetrics ignored = new DisplayMetrics();
+ activity.getWindowManager().getDefaultDisplay().getMetrics(ignored);
+ Resources resources;
+ Set localeSet = new LinkedHashSet<>();
+ for (Locale locale : LOCALES_TO_TEST) {
+ config.locale = locale;
+ resources = new Resources(assets, ignored, config);
+ if (!TextUtils.equals(DEFAULT_STRING, resources.getString(resId))
+ || locale.equals(Locale.ENGLISH)) {
+ localeSet.add(locale);
+ }
+ }
+ for (Locale locale : localeSet) {
+ if (locale.equals(TIBETAN)) {
+ // include English name for devices without Tibetan font support
+ tmpMap.put(TIBETAN.getLanguage(), "Tibetan བོད་སྐད།"); // Tibetan
+ } else if (locale.equals(Locale.SIMPLIFIED_CHINESE)) {
+ tmpMap.put(Locale.SIMPLIFIED_CHINESE.toString(), "中文 (中国)"); // Chinese (China)
+ } else if (locale.equals(Locale.TRADITIONAL_CHINESE)) {
+ tmpMap.put(Locale.TRADITIONAL_CHINESE.toString(), "中文 (台灣)"); // Chinese (Taiwan)
+ } else if (locale.equals(CHINESE_HONG_KONG)) {
+ tmpMap.put(CHINESE_HONG_KONG.toString(), "中文 (香港)"); // Chinese (Hong Kong)
+ } else {
+ tmpMap.put(locale.getLanguage(), capitalize(locale.getDisplayLanguage(locale)));
+ }
+ }
+
+ /* SYSTEM_DEFAULT is a fake one for displaying in a chooser menu. */
+ localeSet.add(null);
+ tmpMap.put(USE_SYSTEM_DEFAULT, activity.getString(resId));
+ nameMap = Collections.unmodifiableMap(tmpMap);
+ }
+
+ /**
+ * Get the instance of {@link Languages} to work with, providing the
+ * {@link Activity} that is will be working as part of, as well as the
+ * {@code resId} that has the exact string "Use System Default",
+ * i.e. {@code R.string.use_system_default}.
+ *
+ * That string resource {@code resId} is also used to find the supported
+ * translations: if an included translation has a translated string that
+ * matches that {@code resId}, then that language will be included as a
+ * supported language.
+ *
+ * @param clazz the {@link Class} of the default {@code Activity},
+ * usually the main {@code Activity} from where the
+ * Settings is launched from.
+ * @param resId the string resource ID to for the string "System Default",
+ * e.g. {@code R.string.pref_language_default}
+ */
+ public static void setup(Class> clazz, int resId) {
+ if (Languages.clazz == null) {
+ Languages.clazz = clazz;
+ Languages.resId = resId;
+ } else {
+ throw new RuntimeException("Languages singleton was already initialized, duplicate call to Languages.setup()!");
+ }
+ }
+
+ /**
+ * @param activity the {@link Activity} this is working as part of
+ * @return the singleton to work with
+ */
+ public static Languages get(Activity activity) {
+ if (singleton == null) {
+ singleton = new Languages(activity);
+ }
+ return singleton;
+ }
+
+ @TargetApi(17)
+ public static void setLanguage(final ContextWrapper contextWrapper, String language, boolean refresh) {
+ if (locale != null && TextUtils.equals(locale.getLanguage(), language) && (!refresh)) {
+ return; // already configured
+ } else if (language == null || language.equals(USE_SYSTEM_DEFAULT)) {
+ 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);
+ }
+ }
+
+ final Resources resources = contextWrapper.getBaseContext().getResources();
+ Configuration config = resources.getConfiguration();
+ if (Build.VERSION.SDK_INT >= 17) {
+ config.setLocale(locale);
+ } else {
+ config.locale = locale;
+ }
+ resources.updateConfiguration(config, resources.getDisplayMetrics());
+ Locale.setDefault(locale);
+
+ }
+
+ /**
+ * Force reload the {@link Activity to make language changes take effect.}
+ *
+ * @param activity the {@code Activity} to force reload
+ */
+ public static void forceChangeLanguage(Activity activity) {
+ 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 the name of the language based on the locale.
+ */
+ public String getName(String locale) {
+ String ret = nameMap.get(locale);
+ // if no match, try to return a more general name (i.e. English for en_IN)
+ if (ret == null && locale.contains("_")) {
+ ret = nameMap.get(locale.split("_")[0]);
+ }
+ return ret;
+ }
+
+ /**
+ * @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 nameMap.values().toArray(new String[nameMap.size()]);
+ }
+
+ public int getPosition(Locale locale) {
+ String localeName = locale.getLanguage();
+ int i = 0;
+ for (String key : nameMap.keySet()) {
+ if (TextUtils.equals(key, localeName)) {
+ return i;
+ } else {
+ i++;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * @return sorted list of supported locales.
+ */
+ public String[] getSupportedLocales() {
+ Set keys = nameMap.keySet();
+ return keys.toArray(new String[keys.size()]);
+ }
+
+ private String capitalize(final String line) {
+ return Character.toUpperCase(line.charAt(0)) + line.substring(1);
+ }
+
+ private static final Locale[] LOCALES_TO_TEST = {
+ Locale.ENGLISH,
+ Locale.FRENCH,
+ Locale.GERMAN,
+ Locale.ITALIAN,
+ Locale.JAPANESE,
+ Locale.KOREAN,
+ Locale.SIMPLIFIED_CHINESE,
+ Locale.TRADITIONAL_CHINESE,
+ CHINESE_HONG_KONG,
+ TIBETAN,
+ new Locale("af"),
+ new Locale("am"),
+ new Locale("ar"),
+ new Locale("az"),
+ new Locale("be"),
+ new Locale("bg"),
+ new Locale("bn"),
+ new Locale("ca"),
+ new Locale("cs"),
+ new Locale("da"),
+ new Locale("el"),
+ new Locale("es"),
+ new Locale("et"),
+ new Locale("eu"),
+ new Locale("fa"),
+ new Locale("fi"),
+ new Locale("gl"),
+ new Locale("hi"),
+ new Locale("hr"),
+ new Locale("hu"),
+ new Locale("hy"),
+ new Locale("in"),
+ new Locale("hy"),
+ new Locale("in"),
+ new Locale("is"),
+ new Locale("it"),
+ new Locale("iw"),
+ new Locale("ka"),
+ new Locale("kk"),
+ new Locale("km"),
+ new Locale("kn"),
+ new Locale("ky"),
+ new Locale("lo"),
+ new Locale("lt"),
+ new Locale("lv"),
+ new Locale("mk"),
+ new Locale("ml"),
+ new Locale("mn"),
+ new Locale("mr"),
+ new Locale("ms"),
+ new Locale("my"),
+ new Locale("nb"),
+ new Locale("ne"),
+ new Locale("nl"),
+ new Locale("pl"),
+ new Locale("pt"),
+ new Locale("rm"),
+ new Locale("ro"),
+ new Locale("ru"),
+ new Locale("si"),
+ new Locale("sk"),
+ new Locale("sl"),
+ new Locale("sn"),
+ new Locale("sr"),
+ new Locale("sv"),
+ new Locale("sw"),
+ new Locale("ta"),
+ new Locale("te"),
+ new Locale("th"),
+ new Locale("tl"),
+ new Locale("tr"),
+ new Locale("uk"),
+ new Locale("ur"),
+ new Locale("uz"),
+ new Locale("vi"),
+ new Locale("zu"),
+ };
+
+}
diff --git a/app/src/main/java/org/fdroid/fdroid/Preferences.java b/app/src/main/java/org/fdroid/fdroid/Preferences.java
index a3b8d7936..92977927e 100644
--- a/app/src/main/java/org/fdroid/fdroid/Preferences.java
+++ b/app/src/main/java/org/fdroid/fdroid/Preferences.java
@@ -229,6 +229,10 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
.replaceAll(" ", "-");
}
+ public String getLangauge() {
+ return preferences.getString(Preferences.PREF_LANGUAGE, "");
+ }
+
public String getLocalRepoName() {
return preferences.getString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName());
}
diff --git a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java
index 4574cb177..ed50137d5 100644
--- a/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java
+++ b/app/src/main/java/org/fdroid/fdroid/views/fragments/PreferencesFragment.java
@@ -12,19 +12,18 @@ import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.support.v4.preference.PreferenceFragment;
import android.text.TextUtils;
-
+import com.geecko.QuickLyric.view.AppCompatListPreference;
+import info.guardianproject.netcipher.NetCipher;
+import info.guardianproject.netcipher.proxy.OrbotHelper;
import org.fdroid.fdroid.AppDetails2;
import org.fdroid.fdroid.CleanCacheService;
-import org.fdroid.fdroid.FDroidApp;
+import org.fdroid.fdroid.Languages;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.installer.InstallHistoryService;
import org.fdroid.fdroid.installer.PrivilegedInstaller;
-import info.guardianproject.netcipher.NetCipher;
-import info.guardianproject.netcipher.proxy.OrbotHelper;
-
public class PreferencesFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
@@ -62,6 +61,12 @@ public class PreferencesFragment extends PreferenceFragment
enableProxyCheckPref = (CheckBoxPreference) findPreference(Preferences.PREF_ENABLE_PROXY);
updateAutoDownloadPref = findPreference(Preferences.PREF_AUTO_DOWNLOAD_INSTALL_UPDATES);
updatePrivilegedExtensionPref = findPreference(Preferences.PREF_UNINSTALL_PRIVILEGED_APP);
+
+ AppCompatListPreference languagePref = (AppCompatListPreference) findPreference(Preferences.PREF_LANGUAGE);
+ Languages languages = Languages.get(getActivity());
+ languagePref.setDefaultValue(Languages.USE_SYSTEM_DEFAULT);
+ languagePref.setEntries(languages.getAllNames());
+ languagePref.setEntryValues(languages.getSupportedLocales());
}
private void checkSummary(String key, int resId) {
@@ -136,8 +141,9 @@ public class PreferencesFragment extends PreferenceFragment
case Preferences.PREF_LANGUAGE:
entrySummary(key);
if (changing) {
- // TODO: Ask MainActivity to restart itself.
- ((FDroidApp) getActivity().getApplication()).updateLanguage();
+ Activity activity = getActivity();
+ Languages.setLanguage(activity, Preferences.get().getLangauge(), false);
+ Languages.forceChangeLanguage(activity);
}
break;
diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml
index 505cefb68..1de33474f 100644
--- a/app/src/main/res/values/donottranslate.xml
+++ b/app/src/main/res/values/donottranslate.xml
@@ -44,126 +44,4 @@
- night
-
-
- - en
- - af
- - ar
- - ast
- - be
- - bg
- - ca
- - cs
- - da
- - de
- - el
- - eo
- - es
- - et
- - eu
- - fa
- - fi
- - fr
- - gl
- - he
- - hi
- - hr
- - hu
- - hy
- - id
- - is
- - it
- - ja
- - ko
- - lt
- - lv
- - mk
- - my
- - nb
- - nl
- - pl
- - pt-rBR
- - pt-rPT
- - ro
- - ru
- - sc
- - sk
- - sl
- - sn
- - sq
- - sr
- - sv
- - ta
- - th
- - tr
- - ug
- - uk
- - ur
- - vi
- - zh-rCN
- - zh-rHK
- - zh-rTW
-
-
-
- - @string/pref_language_default
- - English
- - Afrikaans
- - ﺎﻠﻋﺮﺒﻳﺓ
- - Asturian
- - белорусский
- - Български
- - Català
- - Čeština
- - Dansk
- - Deutsch
- - Ελληνικά
- - Esperanto
- - Español
- - Eesti
- - Euskara
- - ﻑﺍﺮﺳی
- - Suomi
- - Français
- - Galego
- - עברית
- - हिन्दी
- - Hrvatski
- - Magyar
- - հայերեն
- - Bahasa Indonesia
- - Íslenska
- - Italiano
- - 日本語
- - 한국어
- - Lietuvių
- - Latviešu
- - македонски
- - မြန်မာစာ
- - Norsk bokmål
- - Nederlands
- - Polski
- - Português (Brasil)
- - Português (Portugal)
- - Română
- - Русский
- - Sardinian
- - Slovenčina
- - Slovenščina
- - ChiSona
- - Shqip
- - Српски
- - Svenska
- - தமிழ்
- - ไทย
- - Türkçe
- - ﺉۇﻲﻏۇﺭچە
- - Українська
- - اردو
- - Tiếng Việt
- - 中文 (中国)
- - 中文 (香港)
- - 中文 (台湾)
-
-
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 6850f2e76..8f560d234 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -43,10 +43,7 @@
+ android:key="language"/>
4:
+ if float(row[4]) > 75.0:
+ continue
+ locale = row[1]
+ if '_' in locale:
+ codes = locale.split('_')
+ if codes[1] == 'Hans':
+ codes[1] = 'CN'
+ elif codes[1] == 'Hant':
+ codes[1] = 'TW'
+ locale = codes[0] + '-r' + codes[1]
+ translation_file = 'app/src/main/res/values-' + locale + '/strings.xml'
+ percent = str(int(float(row[4]))) + '%'
+ print('Removing incomplete file: (' + percent + ')\t',
+ translation_file)
+ os.remove(os.path.join(projectbasedir, translation_file))
+ repo.index.remove([translation_file, ])
+ if len(percent) == 2:
+ msg += ' '
+ msg += percent + ' ' + row[1] + ' ' + row[0] + '\n'
+
+repo.index.commit(msg)