You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/src/androidTest/java/com/todoroo/andlib/test/TranslationTests.java

340 lines
12 KiB
Java

/**
* Copyright (c) 2012 Todoroo Inc
*
* See the file "LICENSE" for the full license governing this code.
*/
package com.todoroo.andlib.test;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.test.AndroidTestCase;
import android.util.DisplayMetrics;
import org.tasks.R;
import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import static org.tasks.date.DateTimeUtils.newDate;
/**
* Tests translations for consistency with the default values. You must
* extend this class and create it with your own values for strings
* and arrays.
*
* @author Tim Su <tim@todoroo.com>
*
*/
abstract public class TranslationTests extends AndroidTestCase {
// --- abstract methods
/**
* @return R.string.class
*/
public abstract Class<?> getStringResources();
/**
* @return R.array.class
*/
public abstract Class<?> getArrayResources();
/**
* @return array of fields that are parsed by SimpleDateFormat
*/
public abstract int[] getDateFormatStrings();
// --- tests
/**
* Sets locale
*/
private void setLocale(Locale locale) {
Locale.setDefault(locale);
Configuration config = new Configuration();
config.locale = locale;
DisplayMetrics metrics = getContext().getResources().getDisplayMetrics();
getContext().getResources().updateConfiguration(config, metrics);
}
/**
* Loop through each locale and call runnable
*/
protected void forEachLocale(Runnable r) {
Locale[] locales = Locale.getAvailableLocales();
for(Locale locale : locales) {
setLocale(locale);
r.run();
}
}
private static final class FormatStringData {
private static final char[] scratch = new char[10];
/** format characters */
public final char[] characters;
/** the original string */
public final String string;
public FormatStringData(String string) {
this.string = string;
int pos = -1;
int count = 0;
while(true) {
pos = string.indexOf('%', ++pos);
if(pos++ == -1)
break;
if(pos >= string.length())
scratch[count++] = '\0';
else
scratch[count++] = string.charAt(pos);
}
characters = new char[count];
for(int i = 0; i < count; i++) {
characters[i] = scratch[i];
}
}
/** test that the characters match */
public boolean matches(FormatStringData other) {
if(characters.length != other.characters.length)
return false;
outer: for(int i = 0; i < characters.length; i++) {
if(Character.isDigit(characters[i])) {
for(int j = 0; j < other.characters.length; j++)
if(characters[i] == other.characters[j])
break outer;
return false;
} else if(characters[i] != other.characters[i])
return false;
}
return true;
}
@Override
public String toString() {
StringBuilder value = new StringBuilder("[");
for(int i = 0; i < characters.length; i++) {
value.append(characters[i]);
if(i < characters.length - 1)
value.append(',');
}
value.append("]: '").append(string).append('\'');
return value.toString();
}
}
/**
* Internal test of format string parser
*/
public void testFormatStringParser() {
String s = "abc";
FormatStringData data = new FormatStringData(s);
assertEquals(s, data.string);
assertEquals(0, data.characters.length);
s = "abc %s def";
data = new FormatStringData(s);
assertEquals(1, data.characters.length);
assertEquals('s', data.characters[0]);
s = "abc %%s def %d";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals('%', data.characters[0]);
assertEquals('d', data.characters[1]);
assertTrue(data.toString(), data.toString().contains("[%"));
assertTrue(data.toString(), data.toString().contains("d]"));
assertTrue(data.toString(), data.toString().contains(s));
assertTrue(data.matches(new FormatStringData("espanol %% und %d si")));
assertFalse(data.matches(new FormatStringData("ingles %d ja %% pon")));
s = "% abc %";
data = new FormatStringData(s);
assertEquals(2, data.characters.length);
assertEquals(' ', data.characters[0]);
assertEquals('\0', data.characters[1]);
}
/**
* Test that the format specifiers in translations match exactly the
* translations in the default text
*/
public void testFormatStringsMatch() throws Exception {
final Resources r = getContext().getResources();
final int[] strings = getResourceIds(getStringResources());
final FormatStringData[] formatStrings = new FormatStringData[strings.length];
final StringBuilder failures = new StringBuilder();
for(int i = 0; i < strings.length; i++) {
try {
String string = r.getString(strings[i]);
formatStrings[i] = new FormatStringData(string);
} catch (Exception e) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("error opening %s: %s\n",
name, e.getMessage()));
}
}
forEachLocale(new Runnable() {
public void run() {
Locale locale = r.getConfiguration().locale;
for(int i = 0; i < strings.length; i++) {
try {
switch(strings[i]) {
case R.string.premium_speech_bubble_2: // Special exception--this string contains a % character
case R.string.abc_shareactionprovider_share_with_application:
continue;
}
String string = r.getString(strings[i]);
FormatStringData newFS = new FormatStringData(string);
if(!newFS.matches(formatStrings[i])) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("%s (%s): %s != %s\n",
name, locale.toString(), newFS, formatStrings[i]));
}
} catch (Exception e) {
String name = r.getResourceName(strings[i]);
failures.append(String.format("%s: error opening %s: %s\n",
locale.toString(), name, e.getMessage()));
}
}
}
});
assertTrue(failures.toString(), errorCount(failures) == 0);
}
/**
* Test that date formatters parse correctly
*/
public void testDateFormats() throws Exception {
final Resources r = getContext().getResources();
final StringBuilder failures = new StringBuilder();
final int[] dateStrings = getDateFormatStrings();
final Date date = newDate();
forEachLocale(new Runnable() {
public void run() {
Locale locale = r.getConfiguration().locale;
for (int dateString : dateStrings) {
try {
String string = r.getString(dateString);
try {
new SimpleDateFormat(string).format(date);
} catch (Exception e) {
String name = r.getResourceName(dateString);
failures.append(String.format("%s: invalid format string '%s': %s\n",
locale.toString(), name, e.getMessage()));
}
} catch (Exception e) {
String name = r.getResourceName(dateString);
failures.append(String.format("%s: error opening %s: %s\n",
locale.toString(), name, e.getMessage()));
}
}
}
});
assertEquals(failures.toString(), 0, errorCount(failures));
}
/**
* Test that there are the same number of array entries in each locale
*/
public void testArraySizesMatch() throws Exception {
final Resources r = getContext().getResources();
final int[] arrays = getResourceIds(getArrayResources());
final int[] sizes = new int[arrays.length];
final StringBuilder failures = new StringBuilder();
for(int i = 0; i < arrays.length; i++) {
try {
sizes[i] = r.getStringArray(arrays[i]).length;
} catch (Resources.NotFoundException e) {
String name = r.getResourceName(arrays[i]);
failures.append(String.format("error opening %s: %s\n",
name, e.getMessage()));
sizes[i] = -1;
}
}
forEachLocale(new Runnable() {
public void run() {
for(int i = 0; i < arrays.length; i++) {
if(sizes[i] == -1)
continue;
int size;
try {
size = r.getStringArray(arrays[i]).length;
} catch (Resources.NotFoundException e) {
String name = r.getResourceName(arrays[i]);
Locale locale = r.getConfiguration().locale;
failures.append(String.format("%s: error opening %s: %s\n",
locale, name, e.getMessage()));
continue;
}
if(size != sizes[i]) {
String name = r.getResourceName(arrays[i]);
Locale locale = r.getConfiguration().locale;
failures.append(String.format("%s (%s): size %d != %d\n",
name, locale.toString(), size, sizes[i]));
}
}
}
});
assertEquals(failures.toString(), 0, errorCount(failures));
}
// --- helper methods
/**
* Count newlines
*/
private int errorCount(StringBuilder failures) {
int count = 0;
int pos = -1;
while(true) {
pos = failures.indexOf("\n", pos + 1);
if(pos == -1)
return count;
count++;
}
}
/**
* @return an array of all string resource id's
*/
public int[] getResourceIds(Class<?> resources) {
Field[] fields = resources.getDeclaredFields();
List<Integer> ids = new ArrayList<>(fields.length);
for (Field field : fields) {
try {
ids.add(field.getInt(null));
} catch (Exception e) {
// not a field we care about
}
}
int[] idsAsIntArray = new int[ids.size()];
for(int i = 0; i < ids.size(); i++)
idsAsIntArray[i] = ids.get(i);
return idsAsIntArray;
}
}