diff --git a/app/src/androidTest/java/org/fdroid/fdroid/LocalizationTest.java b/app/src/androidTest/java/org/fdroid/fdroid/LocalizationTest.java new file mode 100644 index 000000000..5a38b2788 --- /dev/null +++ b/app/src/androidTest/java/org/fdroid/fdroid/LocalizationTest.java @@ -0,0 +1,210 @@ +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.ArrayList; +import java.util.Collections; +import java.util.HashMap; +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 ArrayList localeNames = new ArrayList<>(locales.length); + + private AssetManager assets; + private Configuration config; + private Resources resources; + + @Before + public void setUp() { + for (Locale locale : locales) { + localeNames.add(locale.toString()); + } + Collections.sort(localeNames); + + 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 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 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); + } + } + } +} diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4685e0e4c..72210ac38 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -481,8 +481,8 @@ Kategori %1$s - Visa enda appen i %2$d kategorin - Visa alla %2$d appar från %2$s kategorin + Visa enda appen i %2$s kategorin + Visa alla %1$d appar från %2$s kategorin Uppdatera