| 1 | // Copyright © 2004-2006 University of Helsinki, Department of Computer Science | |
| 2 | // Copyright © 2012 various contributors | |
| 3 | // This software is released under GNU Lesser General Public License 2.1. | |
| 4 | // The license text is at http://www.gnu.org/licenses/lgpl-2.1.html | |
| 5 | ||
| 6 | package fi.helsinki.cs.titokone; | |
| 7 | ||
| 8 | import java.text.MessageFormat; | |
| 9 | import java.util.*; | |
| 10 | import java.util.logging.Logger; | |
| 11 | ||
| 12 | /** | |
| 13 | * This class deals with translating strings. It also remembers which | |
| 14 | * language is currently set, but does not know specifically what | |
| 15 | * languages are currently available. | |
| 16 | */ | |
| 17 | public class Translator { | |
| 18 | ||
| 19 | // TODO: get rid of this class, replace it with standard use of resource bundles and keep all translations in properties files | |
| 20 | ||
| 21 | /** | |
| 22 | * This name identifies the resource files containing translations | |
| 23 | * for this software. | |
| 24 | */ | |
| 25 | // XXX: non-final for testing purposes | |
| 26 | public static String resourceFamilyName = "fi.helsinki.cs.titokone.translations.Translations"; | |
| 27 | ||
| 28 | /** | |
| 29 | * This field contains the default locale. | |
| 30 | */ | |
| 31 | public static final Locale defaultLocale = Locale.ENGLISH; | |
| 32 | ||
| 33 | /** | |
| 34 | * This field stores the current locale. It defaults to | |
| 35 | * defaultLocale. | |
| 36 | */ | |
| 37 | private static Locale currentLocale = defaultLocale; | |
| 38 | ||
| 39 | // XXX: for testing purposes, resourceFamilyName can change after this class is initialized, so we can't save it in a field | |
| 40 | private static ResourceBundle defaultTranslations() { | |
| 41 | 2 | return ResourceBundle.getBundle(resourceFamilyName, defaultLocale); |
| 42 | } | |
| 43 | ||
| 44 | /** | |
| 45 | * This field stores the current ResourceBundle in use. | |
| 46 | */ | |
| 47 | 1 | private static ResourceBundle translations = defaultTranslations(); |
| 48 | ||
| 49 | ||
| 50 | /** | |
| 51 | * This function translates a fixed string to the currently used | |
| 52 | * language. If the current language has no string corresponding to | |
| 53 | * the key, a default translation is used from Translations.class. | |
| 54 | * If that class does not have the translation either, the keystring | |
| 55 | * itself is returned. The translation therefore fails silently. | |
| 56 | * | |
| 57 | * @param keyString A string key that identifies the translation in a | |
| 58 | * Translations*class file. | |
| 59 | * @return The translated string, if available, or something otherwise | |
| 60 | * usable. | |
| 61 | */ | |
| 62 | public static String translate(String keyString) { | |
| 63 | String result = null; | |
| 64 | Logger logger = | |
| 65 | Logger.getLogger(Translator.class.getPackage().getName()); | |
| 66 | try { | |
| 67 | 1 | result = translations.getString(keyString); |
| 68 | } catch (MissingResourceException untranslatedKey) { | |
| 69 | logger.fine("Translation for " + keyString + " not found in " + | |
| 70 | "current set."); // No Message used here. | |
| 71 | result = null; | |
| 72 | } catch (NullPointerException oddity) { | |
| 73 | logger.fine("ResourceBundle getString would not just return " + | |
| 74 | "null, it insists on casting. Trasnslating string " + | |
| 75 | "'" + keyString + "' failed."); | |
| 76 | result = null; | |
| 77 | } | |
| 78 | 1 | if (result == null) { // If there was no luck, try the untranslated. |
| 79 | try { | |
| 80 | 2 | result = defaultTranslations().getString(keyString); |
| 81 | } catch (MissingResourceException totallyUnknownKey) { | |
| 82 | logger.fine("Translation for " + keyString + " not found " + | |
| 83 | "in default set either."); | |
| 84 | result = null; | |
| 85 | } catch (NullPointerException oddity) { | |
| 86 | logger.fine("Null pointer exception repeated when trying " + | |
| 87 | "default translations."); | |
| 88 | result = null; | |
| 89 | } | |
| 90 | } | |
| 91 | 1 | if (result == null) // If there was still no luck, go for the keystr. |
| 92 | { | |
| 93 | result = keyString; | |
| 94 | } | |
| 95 | 1 | return result; |
| 96 | } | |
| 97 | ||
| 98 | /** | |
| 99 | * This function translates a template string to the currently | |
| 100 | * used language and replaces any {i} markers in it with strings | |
| 101 | * from the parameters array. If the current language has no | |
| 102 | * string corresponding to the key, a default translation is used | |
| 103 | * from Translations.class. If that class does not have the | |
| 104 | * translation either, the keystring itself is returned. The | |
| 105 | * translation therefore fails silently. The translation string is | |
| 106 | * fetched from the current ResourceBundle in use, and the replacement | |
| 107 | * is done with the help of java.text.MessageFormat. | |
| 108 | * | |
| 109 | * @param keyString A string key that identifies the translation in a | |
| 110 | * Translations*class file. | |
| 111 | * @param parameters A string array containing strings to replace {i} | |
| 112 | * markers in the string in order - that is, parameters[0] replaces {0}, | |
| 113 | * parameters[1] replaces {1} etc. | |
| 114 | * @return The translated string, if available, or something otherwise | |
| 115 | * usable, with {i} markers replaced from the parameters array as | |
| 116 | * far as there are available replacements. | |
| 117 | */ | |
| 118 | public static String translate(String keyString, String[] parameters) { | |
| 119 | MessageFormat formatter; | |
| 120 | // First, we translate the basic string as if it had no parameters, | |
| 121 | // then we replace any {i} fields in it by using MessageFormat's | |
| 122 | // format method, with the replacements as its parameter. The | |
| 123 | // replacement is done according to locale, although this is | |
| 124 | // unlikely to affect anything in our current implementation. | |
| 125 | 2 | formatter = new MessageFormat(translate(keyString)); |
| 126 | 1 | formatter.setLocale(currentLocale); |
| 127 | 3 | return formatter.format(parameters).toString(); |
| 128 | } | |
| 129 | ||
| 130 | /** | |
| 131 | * This method sets the current locale in use and fetches a | |
| 132 | * corresponding ResourceBundle that contains the translations most | |
| 133 | * suitable for this locale. This may mean the default English | |
| 134 | * translation if there is nothing better available. | |
| 135 | * | |
| 136 | * @param newLocale The locale to switch to, eg. new Locale("fi", | |
| 137 | * "FI"). | |
| 138 | */ | |
| 139 | public static void setLocale(Locale newLocale) { | |
| 140 | Logger logger = | |
| 141 | Logger.getLogger(Translator.class.getPackage().getName()); | |
| 142 | 1 | if (newLocale == null) { |
| 143 | 1 | throw new IllegalArgumentException("Trying to set locale to null."); |
| 144 | } | |
| 145 | currentLocale = newLocale; | |
| 146 | 1 | translations = ResourceBundle.getBundle(resourceFamilyName, newLocale); |
| 147 | logger.fine("Locale changed, new locale " + newLocale.toString() + ", translations from class " + | |
| 148 | translations.getClass().getName() + "."); | |
| 149 | } | |
| 150 | ||
| 151 | /** | |
| 152 | * This method sets the current locale in use and tries to fetch the | |
| 153 | * translation from translationPath. Use of setLocale(Locale) is | |
| 154 | * recommended, but this method enables the user to have very exact | |
| 155 | * control over where the translations are found. | |
| 156 | * | |
| 157 | * @param newLocale The locale to switch to, eg. new Locale("fi", | |
| 158 | * "FI"). | |
| 159 | * @param newTranslations A class containing the translations for this | |
| 160 | * locale. If the translations are located in the standard place, | |
| 161 | * setLocale(Locale) can be used. | |
| 162 | */ | |
| 163 | public static void setLocale(Locale newLocale, | |
| 164 | ResourceBundle newTranslations) { | |
| 165 | Logger logger = | |
| 166 | Logger.getLogger(Translator.class.getPackage().getName()); | |
| 167 | 1 | if (newLocale == null) { |
| 168 | 1 | throw new IllegalArgumentException("Trying to set locale to " + |
| 169 | "null."); | |
| 170 | } | |
| 171 | 1 | if (newTranslations == null) { |
| 172 | 1 | throw new IllegalArgumentException("Trying to set translations " + |
| 173 | "to null."); | |
| 174 | } | |
| 175 | currentLocale = newLocale; | |
| 176 | translations = newTranslations; | |
| 177 | logger.fine("Locale changed with set translations, new locale " + | |
| 178 | newLocale.toString() + | |
| 179 | ", translations from class " + | |
| 180 | translations.getClass().getName() + "."); | |
| 181 | } | |
| 182 | ||
| 183 | /** | |
| 184 | * This method returns the resource bundle in use. | |
| 185 | * | |
| 186 | * @return The resource bundle set to correspond to the current | |
| 187 | * locale. | |
| 188 | */ | |
| 189 | public static ResourceBundle getResourceBundle() { | |
| 190 | 1 | return translations; |
| 191 | } | |
| 192 | } | |
Mutations | ||
| 41 |
removed call to java/util/ResourceBundle::getBundle : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) mutated return of Object value for fi/helsinki/cs/titokone/Translator::defaultTranslations to ( if (x != null) null else throw new RuntimeException ) : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 47 |
removed call to fi/helsinki/cs/titokone/Translator::defaultTranslations : TIMED_OUT |
|
| 67 |
removed call to java/util/ResourceBundle::getString : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testBasicTranslate(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 78 |
negated conditional : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 80 |
removed call to java/util/ResourceBundle::getString : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) removed call to fi/helsinki/cs/titokone/Translator::defaultTranslations : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 91 |
negated conditional : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 95 |
mutated return of Object value for fi/helsinki/cs/titokone/Translator::translate to ( if (x != null) null else throw new RuntimeException ) : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 125 |
removed call to fi/helsinki/cs/titokone/Translator::translate : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) removed call to java/text/MessageFormat::<init> : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 126 |
removed call to java/text/MessageFormat::setLocale : SURVIVED |
|
| 127 |
removed call to java/lang/String::toString : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) removed call to java/text/MessageFormat::format : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) mutated return of Object value for fi/helsinki/cs/titokone/Translator::translate to ( if (x != null) null else throw new RuntimeException ) : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 142 |
negated conditional : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 143 |
removed call to java/lang/IllegalArgumentException::<init> : NO_COVERAGE |
|
| 146 |
removed call to java/util/ResourceBundle::getBundle : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 167 |
negated conditional : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 168 |
removed call to java/lang/IllegalArgumentException::<init> : NO_COVERAGE |
|
| 171 |
negated conditional : KILLED -> fi.helsinki.cs.titokone.TranslatorTest.testReplacementTranslation(fi.helsinki.cs.titokone.TranslatorTest) |
|
| 172 |
removed call to java/lang/IllegalArgumentException::<init> : NO_COVERAGE |
|
| 190 |
mutated return of Object value for fi/helsinki/cs/titokone/Translator::getResourceBundle to ( if (x != null) null else throw new RuntimeException ) : NO_COVERAGE |