Merge branch 'localization-fixes' into 'master'
localization fixes Closes #923 See merge request !484
This commit is contained in:
commit
b2d89ec665
211
app/src/androidTest/java/org/fdroid/fdroid/LocalizationTest.java
Normal file
211
app/src/androidTest/java/org/fdroid/fdroid/LocalizationTest.java
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.app.Instrumentation;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.DisplayMetrics;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs through all of the translated strings and tests them with the same format
|
||||||
|
* values that the source strings expect. This is to ensure that the formats in
|
||||||
|
* the translations are correct in number and in type (e.g. {@code s} or {@code s}.
|
||||||
|
* It reads the source formats and then builds {@code formats} to represent the
|
||||||
|
* position and type of the formats. Then it runs through all of the translations
|
||||||
|
* with formats of the correct number and type.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class LocalizationTest {
|
||||||
|
public static final String TAG = "LocalizationTest";
|
||||||
|
|
||||||
|
private final Pattern androidFormat = Pattern.compile("(%[a-z0-9]\\$?[a-z]?)");
|
||||||
|
private final Locale[] locales = Locale.getAvailableLocales();
|
||||||
|
private final HashSet<String> localeNames = new HashSet<>(locales.length);
|
||||||
|
|
||||||
|
private AssetManager assets;
|
||||||
|
private Configuration config;
|
||||||
|
private Resources resources;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
for (Locale locale : Languages.LOCALES_TO_TEST) {
|
||||||
|
localeNames.add(locale.toString());
|
||||||
|
}
|
||||||
|
for (Locale locale : locales) {
|
||||||
|
localeNames.add(locale.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
|
||||||
|
Context context = instrumentation.getTargetContext();
|
||||||
|
assets = context.getAssets();
|
||||||
|
config = context.getResources().getConfiguration();
|
||||||
|
config.locale = Locale.ENGLISH;
|
||||||
|
// Resources() requires DisplayMetrics, but they are only needed for drawables
|
||||||
|
resources = new Resources(assets, new DisplayMetrics(), config);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadAllPlural() throws IllegalAccessException {
|
||||||
|
Field[] fields = R.plurals.class.getDeclaredFields();
|
||||||
|
|
||||||
|
HashMap<String, String> haveFormats = new HashMap<>();
|
||||||
|
for (Field field : fields) {
|
||||||
|
//Log.i(TAG, field.getName());
|
||||||
|
int resId = field.getInt(int.class);
|
||||||
|
CharSequence string = resources.getQuantityText(resId, 4);
|
||||||
|
//Log.i(TAG, field.getName() + ": '" + string + "'");
|
||||||
|
Matcher matcher = androidFormat.matcher(string);
|
||||||
|
int matches = 0;
|
||||||
|
char[] formats = new char[5];
|
||||||
|
while (matcher.find()) {
|
||||||
|
String match = matcher.group(0);
|
||||||
|
char formatType = match.charAt(match.length() - 1);
|
||||||
|
switch (match.length()) {
|
||||||
|
case 2:
|
||||||
|
formats[matches] = formatType;
|
||||||
|
matches++;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
formats[Integer.parseInt(match.substring(1, 2)) - 1] = formatType;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
formats[Integer.parseInt(match.substring(1, 3)) - 1] = formatType;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException(field.getName() + " has bad format: " + match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
haveFormats.put(field.getName(), new String(formats).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Locale locale : locales) {
|
||||||
|
config.locale = locale;
|
||||||
|
// Resources() requires DisplayMetrics, but they are only needed for drawables
|
||||||
|
resources = new Resources(assets, new DisplayMetrics(), config);
|
||||||
|
for (Field field : fields) {
|
||||||
|
//Log.i(TAG, field.getName());
|
||||||
|
int resId = field.getInt(int.class);
|
||||||
|
for (int quantity = 0; quantity < 22; quantity++) {
|
||||||
|
resources.getQuantityString(resId, quantity);
|
||||||
|
}
|
||||||
|
|
||||||
|
String formats = haveFormats.get(field.getName());
|
||||||
|
String formattedString = null;
|
||||||
|
switch (formats) {
|
||||||
|
case "d":
|
||||||
|
formattedString = resources.getQuantityString(resId, 1, 1);
|
||||||
|
break;
|
||||||
|
case "s":
|
||||||
|
formattedString = resources.getQuantityString(resId, 1, "ONE");
|
||||||
|
break;
|
||||||
|
case "ds":
|
||||||
|
formattedString = resources.getQuantityString(resId, 2, 1, "TWO");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!TextUtils.isEmpty(formats)) {
|
||||||
|
throw new IllegalStateException("Pattern not included in tests: " + formats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (formattedString != null) { // NOPMD
|
||||||
|
//Log.i(TAG, locale + " " + field.getName() + " FORMATTED: " + formattedString);
|
||||||
|
}
|
||||||
|
//Log.i(TAG, field.getName() + ": " + string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLoadAllStrings() throws IllegalAccessException {
|
||||||
|
Field[] fields = R.string.class.getDeclaredFields();
|
||||||
|
|
||||||
|
HashMap<String, String> haveFormats = new HashMap<>();
|
||||||
|
for (Field field : fields) {
|
||||||
|
String string = resources.getString(field.getInt(int.class));
|
||||||
|
Matcher matcher = androidFormat.matcher(string);
|
||||||
|
int matches = 0;
|
||||||
|
char[] formats = new char[5];
|
||||||
|
while (matcher.find()) {
|
||||||
|
String match = matcher.group(0);
|
||||||
|
char formatType = match.charAt(match.length() - 1);
|
||||||
|
switch (match.length()) {
|
||||||
|
case 2:
|
||||||
|
formats[matches] = formatType;
|
||||||
|
matches++;
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
formats[Integer.parseInt(match.substring(1, 2)) - 1] = formatType;
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
formats[Integer.parseInt(match.substring(1, 3)) - 1] = formatType;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException(field.getName() + " has bad format: " + match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
haveFormats.put(field.getName(), new String(formats).trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
//for (Locale locale : new Locale[]{new Locale("es")}) {
|
||||||
|
for (Locale locale : locales) {
|
||||||
|
config.locale = locale;
|
||||||
|
// Resources() requires DisplayMetrics, but they are only needed for drawables
|
||||||
|
resources = new Resources(assets, new DisplayMetrics(), config);
|
||||||
|
for (Field field : fields) {
|
||||||
|
//Log.i(TAG, field.getName());
|
||||||
|
int resId = field.getInt(int.class);
|
||||||
|
resources.getString(resId);
|
||||||
|
|
||||||
|
String formats = haveFormats.get(field.getName());
|
||||||
|
String formattedString = null;
|
||||||
|
switch (formats) {
|
||||||
|
case "d":
|
||||||
|
formattedString = resources.getString(resId, 1);
|
||||||
|
break;
|
||||||
|
case "dd":
|
||||||
|
formattedString = resources.getString(resId, 1, 2);
|
||||||
|
break;
|
||||||
|
case "s":
|
||||||
|
formattedString = resources.getString(resId, "ONE");
|
||||||
|
break;
|
||||||
|
case "ss":
|
||||||
|
formattedString = resources.getString(resId, "ONE", "TWO");
|
||||||
|
break;
|
||||||
|
case "sss":
|
||||||
|
formattedString = resources.getString(resId, "ONE", "TWO", "THREE");
|
||||||
|
break;
|
||||||
|
case "ssss":
|
||||||
|
formattedString = resources.getString(resId, "ONE", "TWO", "THREE", "FOUR");
|
||||||
|
break;
|
||||||
|
case "ssd":
|
||||||
|
formattedString = resources.getString(resId, "ONE", "TWO", 3);
|
||||||
|
break;
|
||||||
|
case "sssd":
|
||||||
|
formattedString = resources.getString(resId, "ONE", "TWO", "THREE", 4);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!TextUtils.isEmpty(formats)) {
|
||||||
|
throw new IllegalStateException("Pattern not included in tests: " + formats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (formattedString != null) { //NOPMD
|
||||||
|
//Log.i(TAG, "FORMATTED: " + formattedString);
|
||||||
|
}
|
||||||
|
//Log.i(TAG, field.getName() + ": " + string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -203,7 +203,7 @@ public final class Languages {
|
|||||||
return Character.toUpperCase(line.charAt(0)) + line.substring(1);
|
return Character.toUpperCase(line.charAt(0)) + line.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Locale[] LOCALES_TO_TEST = {
|
public static final Locale[] LOCALES_TO_TEST = {
|
||||||
Locale.ENGLISH,
|
Locale.ENGLISH,
|
||||||
Locale.FRENCH,
|
Locale.FRENCH,
|
||||||
Locale.GERMAN,
|
Locale.GERMAN,
|
||||||
|
@ -15,11 +15,9 @@ import android.os.Parcelable;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JacksonInject;
|
import com.fasterxml.jackson.annotation.JacksonInject;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
import org.apache.commons.io.filefilter.RegexFileFilter;
|
import org.apache.commons.io.filefilter.RegexFileFilter;
|
||||||
import org.fdroid.fdroid.AppFilter;
|
import org.fdroid.fdroid.AppFilter;
|
||||||
import org.fdroid.fdroid.FDroidApp;
|
import org.fdroid.fdroid.FDroidApp;
|
||||||
@ -40,10 +38,10 @@ import java.util.Collections;
|
|||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
@ -377,7 +375,7 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
|||||||
String languageTag = defaultLocale.getLanguage();
|
String languageTag = defaultLocale.getLanguage();
|
||||||
String localeTag = languageTag + "-" + defaultLocale.getCountry();
|
String localeTag = languageTag + "-" + defaultLocale.getCountry();
|
||||||
Set<String> locales = localized.keySet();
|
Set<String> locales = localized.keySet();
|
||||||
Set<String> localesToUse = new TreeSet<>();
|
Set<String> localesToUse = new LinkedHashSet<>();
|
||||||
|
|
||||||
if (locales.contains(localeTag)) {
|
if (locales.contains(localeTag)) {
|
||||||
localesToUse.add(localeTag);
|
localesToUse.add(localeTag);
|
||||||
@ -398,11 +396,14 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if key starts with Upper case, its set by humans
|
// if key starts with Upper case, its set by humans
|
||||||
video = getLocalizedEntry(localized, localesToUse, "Video");
|
String value = getLocalizedEntry(localized, localesToUse, "Video");
|
||||||
|
if (!TextUtils.isEmpty(value)) {
|
||||||
|
video = value.split("\n", 1)[0];
|
||||||
|
}
|
||||||
whatsNew = getLocalizedEntry(localized, localesToUse, "WhatsNew");
|
whatsNew = getLocalizedEntry(localized, localesToUse, "WhatsNew");
|
||||||
// Name, Summary, Description existed before localization so they shouldn't replace
|
// Name, Summary, Description existed before localization so they shouldn't replace
|
||||||
// non-localized old data format with a null or blank string
|
// non-localized old data format with a null or blank string
|
||||||
String value = getLocalizedEntry(localized, localesToUse, "Name");
|
value = getLocalizedEntry(localized, localesToUse, "Name");
|
||||||
if (!TextUtils.isEmpty(value)) {
|
if (!TextUtils.isEmpty(value)) {
|
||||||
name = value;
|
name = value;
|
||||||
}
|
}
|
||||||
@ -432,7 +433,10 @@ public class App extends ValueObject implements Comparable<App>, Parcelable {
|
|||||||
try {
|
try {
|
||||||
for (String locale : locales) {
|
for (String locale : locales) {
|
||||||
if (localized.containsKey(locale)) {
|
if (localized.containsKey(locale)) {
|
||||||
return (String) localized.get(locale).get(key);
|
String value = (String) localized.get(locale).get(key);
|
||||||
|
if (value != null) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ClassCastException e) {
|
} catch (ClassCastException e) {
|
||||||
|
@ -740,6 +740,11 @@ public class AppDetailsRecyclerViewAdapter
|
|||||||
updateExpandableItem(false);
|
updateExpandableItem(false);
|
||||||
contentView.removeAllViews();
|
contentView.removeAllViews();
|
||||||
|
|
||||||
|
// Video link
|
||||||
|
if (uriIsSetAndCanBeOpened(app.video)) {
|
||||||
|
addLinkItemView(contentView, R.string.menu_video, R.drawable.ic_video, app.video);
|
||||||
|
}
|
||||||
|
|
||||||
// Source button
|
// Source button
|
||||||
if (uriIsSetAndCanBeOpened(app.sourceCode)) {
|
if (uriIsSetAndCanBeOpened(app.sourceCode)) {
|
||||||
addLinkItemView(contentView, R.string.menu_source, R.drawable.ic_source_code, app.sourceCode);
|
addLinkItemView(contentView, R.string.menu_source, R.drawable.ic_source_code, app.sourceCode);
|
||||||
|
7
app/src/main/res/drawable/ic_video.xml
Normal file
7
app/src/main/res/drawable/ic_video.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path android:fillColor="#666666" android:pathData="M10,16.5V7.5L16,12M20,4.4C19.4,4.2 15.7,4 12,4C8.3,4 4.6,4.19 4,4.38C2.44,4.9 2,8.4 2,12C2,15.59 2.44,19.1 4,19.61C4.6,19.81 8.3,20 12,20C15.7,20 19.4,19.81 20,19.61C21.56,19.1 22,15.59 22,12C22,8.4 21.56,4.91 20,4.4Z" />
|
||||||
|
</vector>
|
@ -481,8 +481,8 @@
|
|||||||
<string name="tts_category_name">Kategori %1$s</string>
|
<string name="tts_category_name">Kategori %1$s</string>
|
||||||
|
|
||||||
<plurals name="tts_view_all_in_category">
|
<plurals name="tts_view_all_in_category">
|
||||||
<item quantity="one">Visa enda appen i %2$d kategorin</item>
|
<item quantity="one">Visa enda appen i %2$s kategorin</item>
|
||||||
<item quantity="other">Visa alla %2$d appar från %2$s kategorin</item>
|
<item quantity="other">Visa alla %1$d appar från %2$s kategorin</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="app__install_downloaded_update">Uppdatera</string>
|
<string name="app__install_downloaded_update">Uppdatera</string>
|
||||||
|
|
||||||
|
@ -159,6 +159,7 @@
|
|||||||
<string name="menu_email">E-Mail Author</string>
|
<string name="menu_email">E-Mail Author</string>
|
||||||
<string name="menu_issues">Issues</string>
|
<string name="menu_issues">Issues</string>
|
||||||
<string name="menu_changelog">Changelog</string>
|
<string name="menu_changelog">Changelog</string>
|
||||||
|
<string name="menu_video">Video</string>
|
||||||
<string name="menu_source">Source Code</string>
|
<string name="menu_source">Source Code</string>
|
||||||
<string name="menu_upgrade">Upgrade</string>
|
<string name="menu_upgrade">Upgrade</string>
|
||||||
<string name="menu_donate">Donate</string>
|
<string name="menu_donate">Donate</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user