Merge branch 'system-language-names' into 'master'

get language names from system, instead of hard coded list

Closes #908 and #858

See merge request !461
This commit is contained in:
Hans-Christoph Steiner 2017-04-07 16:51:56 +00:00
commit 78ecba646c
10 changed files with 377 additions and 228 deletions

View File

@ -13,7 +13,6 @@ before_script:
test:
script:
- cd app
- ./tools/langs-list-check.py
- ./tools/check-string-format.py
- cd ..
- ./gradlew assemble -PdisablePreDex

23
RELEASE_CHECKLIST.md Normal file
View File

@ -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`

View File

@ -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".
*
* <p>
* 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}.
*
* <p>
* 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

View File

@ -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<String, String> tmpMap = new TreeMap<>();
private static Map<String, String> 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<Locale> 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}.
* <p/>
* 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<String> 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"),
};
}

View File

@ -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());
}

View File

@ -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;

View File

@ -44,126 +44,4 @@
<item>night</item>
</string-array>
<string-array name="languageValues">
<item></item>
<item>en</item>
<item>af</item>
<item>ar</item>
<item>ast</item>
<item>be</item>
<item>bg</item>
<item>ca</item>
<item>cs</item>
<item>da</item>
<item>de</item>
<item>el</item>
<item>eo</item>
<item>es</item>
<item>et</item>
<item>eu</item>
<item>fa</item>
<item>fi</item>
<item>fr</item>
<item>gl</item>
<item>he</item>
<item>hi</item>
<item>hr</item>
<item>hu</item>
<item>hy</item>
<item>id</item>
<item>is</item>
<item>it</item>
<item>ja</item>
<item>ko</item>
<item>lt</item>
<item>lv</item>
<item>mk</item>
<item>my</item>
<item>nb</item>
<item>nl</item>
<item>pl</item>
<item>pt-rBR</item>
<item>pt-rPT</item>
<item>ro</item>
<item>ru</item>
<item>sc</item>
<item>sk</item>
<item>sl</item>
<item>sn</item>
<item>sq</item>
<item>sr</item>
<item>sv</item>
<item>ta</item>
<item>th</item>
<item>tr</item>
<item>ug</item>
<item>uk</item>
<item>ur</item>
<item>vi</item>
<item>zh-rCN</item>
<item>zh-rHK</item>
<item>zh-rTW</item>
</string-array>
<string-array name="languageNames">
<item>@string/pref_language_default</item>
<item>English</item>
<item>Afrikaans</item>
<item>ﺎﻠﻋﺮﺒﻳﺓ</item>
<item>Asturian</item>
<item>белорусский</item>
<item>Български</item>
<item>Català</item>
<item>Čeština</item>
<item>Dansk</item>
<item>Deutsch</item>
<item>Ελληνικά</item>
<item>Esperanto</item>
<item>Español</item>
<item>Eesti</item>
<item>Euskara</item>
<item>ﻑﺍﺮﺳی</item>
<item>Suomi</item>
<item>Français</item>
<item>Galego</item>
<item>עברית</item>
<item>हिन्दी</item>
<item>Hrvatski</item>
<item>Magyar</item>
<item>հայերեն</item>
<item>Bahasa Indonesia</item>
<item>Íslenska</item>
<item>Italiano</item>
<item>日本語</item>
<item>한국어</item>
<item>Lietuvių</item>
<item>Latviešu</item>
<item>македонски</item>
<item>မြန်မာစာ</item>
<item>Norsk bokmål</item>
<item>Nederlands</item>
<item>Polski</item>
<item>Português (Brasil)</item>
<item>Português (Portugal)</item>
<item>Română</item>
<item>Русский</item>
<item>Sardinian</item>
<item>Slovenčina</item>
<item>Slovenščina</item>
<item>ChiSona</item>
<item>Shqip</item>
<item>Српски</item>
<item>Svenska</item>
<item>தமிழ்</item>
<item>ไทย</item>
<item>Türkçe</item>
<item>ﺉۇﻲﻏۇﺭچە</item>
<item>Українська</item>
<item>اردو</item>
<item>Tiếng Việt</item>
<item>中文 (中国)</item>
<item>中文 (香港)</item>
<item>中文 (台湾)</item>
</string-array>
</resources>

View File

@ -43,10 +43,7 @@
</PreferenceCategory>
<PreferenceCategory android:title="@string/display">
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/pref_language"
android:key="language"
android:defaultValue=""
android:entries="@array/languageNames"
android:entryValues="@array/languageValues" />
android:key="language"/>
<com.geecko.QuickLyric.view.AppCompatListPreference android:title="@string/theme"
android:key="theme"
android:defaultValue="light"

View File

@ -1,63 +0,0 @@
#!/usr/bin/env python3
# List supported languages missing from the preference array
import glob
import os
import sys
import re
from xml.etree import ElementTree
prefs = set([''])
trans = set(['', 'en'])
donottranslate = os.path.join('src', 'main', 'res', 'values', 'donottranslate.xml')
for e in ElementTree.parse(donottranslate).getroot().findall('.//string-array'):
if e.attrib['name'] != 'languageValues':
continue
for i in e.findall('.//item'):
lang = i.text
if not lang:
continue
prefs.add(lang)
for d in glob.glob(os.path.join('src', 'main', 'res', 'values-*')):
lang = d[len(os.path.join('src', 'main', 'res', 'values-')):]
if not lang:
continue
if re.match('^sw[0-9]+dp|v[0-9]+$', lang):
continue
if lang == 'ldrtl':
continue
if os.path.islink(d):
continue
trans.add(lang)
print("In the settings array: %s" % ' '.join(sorted(prefs)))
print("Actually translated: %s" % ' '.join(sorted(trans)))
missing = []
for lang in trans:
if lang not in prefs:
missing.append(lang)
if missing:
print("Missing:")
for lang in missing:
print(" %s" % lang)
extra = []
for lang in prefs:
if lang not in trans:
extra.append(lang)
if extra:
print("Extra:")
for lang in extra:
print(" %s" % lang)
if not missing and not extra:
print("All good.")
else:
sys.exit(1)

View File

@ -0,0 +1,42 @@
#!/usr/bin/python3
import csv
import git
import os
import requests
projectbasedir = os.path.dirname(os.path.dirname(__file__))
print(projectbasedir)
repo = git.Repo(projectbasedir)
msg = 'removing all translations less than 75% complete\n\n'
url = 'https://hosted.weblate.org/exports/stats/f-droid/f-droid/?format=csv'
r = requests.get(url)
stats = csv.reader(r.iter_lines(decode_unicode=True), delimiter=',')
next(stats) # skip CSV header
for row in stats:
if len(row) > 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)