--- /dev/null
+package net.sf.openrocket.l10n;
+
+import java.util.MissingResourceException;
+
+import net.sf.openrocket.logging.TraceException;
+import net.sf.openrocket.util.BugException;
+
+/**
+ * A translator that prepends a pre-defined class name in front of a translation key
+ * and retrieves the translator for that key, and only if that is missing reverts to
+ * the base key name. The base class name can either be provided to the constructor
+ * or retrieved from the stack.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ClassBasedTranslator implements Translator {
+
+
+ private final Translator translator;
+ private final String className;
+
+ /**
+ * Construct a translator using a specified class name.
+ *
+ * @param translator the translator from which to obtain the translations.
+ * @param className the base class name to prepend.
+ */
+ public ClassBasedTranslator(Translator translator, String className) {
+ this.translator = translator;
+ this.className = className;
+ }
+
+ /**
+ * Construct a translator by obtaining the base class name from the stack.
+ *
+ * @param translator the translator from which to obtain the translations.
+ * @param levels the number of levels to move upwards in the stack from the point where this method is called.
+ */
+ public ClassBasedTranslator(Translator translator, int levels) {
+ this(translator, getStackClass(levels));
+ }
+
+
+
+ @Override
+ public String get(String key) {
+ String classKey = className + "." + key;
+
+ try {
+ return translator.get(classKey);
+ } catch (MissingResourceException e) {
+ // Ignore
+ }
+
+ try {
+ return translator.get(key);
+ } catch (MissingResourceException e) {
+ MissingResourceException mre = new MissingResourceException(
+ "Neither key '" + classKey + "' nor '" + key + "' could be found", e.getClassName(), key);
+ mre.initCause(e);
+ throw mre;
+ }
+ }
+
+
+
+ private static String getStackClass(int levels) {
+ TraceException trace = new TraceException();
+ StackTraceElement stack[] = trace.getStackTrace();
+ final int index = levels + 2;
+ if (stack.length <= index) {
+ throw new BugException("Stack trace is too short, length=" + stack.length + ", expected=" + index, trace);
+ }
+
+ StackTraceElement element = stack[index];
+ String cn = element.getClassName();
+ int pos = cn.lastIndexOf('.');
+ if (pos >= 0) {
+ cn = cn.substring(pos + 1);
+ }
+ return cn;
+ }
+
+
+
+
+ // For unit testing purposes
+ String getClassName() {
+ return className;
+ }
+
+}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.Set;
+
+import net.sf.openrocket.gui.main.ExceptionHandler;
+
+/**
+ * A translator that suppresses MissingResourceExceptions and handles them gracefully.
+ * For every missing key this class calls the exception handler exactly once, and
+ * returns the key itself as the translation.
+ *
+ * @author Sampo Niskanen <sampo.niskanen@iki.fi>
+ */
+public class ExceptionSuppressingTranslator implements Translator {
+
+ private static final Set<String> errors = new HashSet<String>();
+ // For unit testing:
+ static int failures = 0;
+
+ private final Translator translator;
+
+
+
+ /**
+ * Sole constructor.
+ *
+ * @param translator the translator to use
+ */
+ public ExceptionSuppressingTranslator(Translator translator) {
+ this.translator = translator;
+ }
+
+
+
+ @Override
+ public String get(String key) {
+ try {
+ return translator.get(key);
+ } catch (MissingResourceException e) {
+ handleError(key, e);
+ }
+
+ return key;
+ }
+
+
+
+ private static synchronized void handleError(String key, MissingResourceException e) {
+ if (errors.add(key)) {
+ failures++;
+ ExceptionHandler.handleErrorCondition("Can not find translation for '" + key + "' locale=" + Locale.getDefault(), e);
+ }
+ }
+
+}
package net.sf.openrocket.l10n;
import java.util.Locale;
-import java.util.MissingResourceException;
import java.util.ResourceBundle;
-import net.sf.openrocket.logging.LogHelper;
-import net.sf.openrocket.startup.Application;
-
/**
* A translator that obtains translated strings from a resource bundle.
- * <p>
- * If a message is not found in any resource bundle, an error is logged and the key itself
- * is returned.
*
* @author Sampo Niskanen <sampo.niskanen@iki.fi>
*/
public class ResourceBundleTranslator implements Translator {
- private static final LogHelper log = Application.getLogger();
private final ResourceBundle bundle;
- private final String baseName;
- private final Locale locale;
/**
* Create a ResourceBundleTranslator using the default Locale.
*/
public ResourceBundleTranslator(String baseName, Locale locale) {
this.bundle = ResourceBundle.getBundle(baseName, locale);
- this.baseName = baseName;
- this.locale = locale;
}
*/
@Override
public synchronized String get(String key) {
- try {
- return bundle.getString(key);
- } catch (MissingResourceException e) {
- log.error("String not found for key '" + key + "' in bundle '" + baseName + "' with locale " + locale, e);
- }
- return key;
+ return bundle.getString(key);
}
}
package net.sf.openrocket.l10n;
+import java.util.MissingResourceException;
+
/**
* An interface for obtaining translations from logical keys.
* <p>
*
* @param key the logical string key.
* @return the translated string.
+ * @throws MissingResourceException if the translation corresponding to the key is not found.
* @throws NullPointerException if key is null.
*/
public String get(String key);
package net.sf.openrocket.startup;
import net.sf.openrocket.database.ThrustCurveMotorSetDatabase;
+import net.sf.openrocket.l10n.ClassBasedTranslator;
import net.sf.openrocket.l10n.DebugTranslator;
+import net.sf.openrocket.l10n.ExceptionSuppressingTranslator;
import net.sf.openrocket.l10n.Translator;
import net.sf.openrocket.logging.LogHelper;
import net.sf.openrocket.logging.LogLevel;
private static LogHelper logger;
private static LogLevelBufferLogger logBuffer;
- private static Translator translator = new DebugTranslator();
+ private static Translator baseTranslator = new DebugTranslator();
private static ThrustCurveMotorSetDatabase motorSetDatabase;
* @return a translator.
*/
public static Translator getTranslator() {
- return translator;
+ Translator t = baseTranslator;
+ t = new ClassBasedTranslator(t, 1);
+ t = new ExceptionSuppressingTranslator(t);
+ return t;
}
/**
* Set the translator used in obtaining translated strings.
* @param translator the translator to set.
*/
- public static void setTranslator(Translator translator) {
- Application.translator = translator;
+ public static void setBaseTranslator(Translator translator) {
+ Application.baseTranslator = translator;
}
} else {
l = new Locale(split[0], split[1], split[2]);
}
+ log.info("Setting custom locale " + l);
Locale.setDefault(l);
}
log.info("Set up translation for locale " + Locale.getDefault() +
", debug.currentFile=" + t.get("debug.currentFile"));
- Application.setTranslator(t);
+ Application.setBaseTranslator(t);
}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import static org.junit.Assert.*;
+
+import java.util.MissingResourceException;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.auto.Mock;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+
+@RunWith(JMock.class)
+public class TestClassBasedTranslator {
+ Mockery context = new JUnit4Mockery();
+
+ @Mock
+ Translator translator;
+
+ @Test
+ public void testClassName() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(null, 0);
+ assertEquals("TestClassBasedTranslator", cbt.getClassName());
+
+ cbt = new ClassBasedTranslator(null, "foobar");
+ assertEquals("foobar", cbt.getClassName());
+ }
+
+ @Test
+ public void testGetWithClassName() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(translator, 0);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("TestClassBasedTranslator.fake.key"); will(returnValue("foobar"));
+ }});
+ // @formatter:on
+
+ assertEquals("foobar", cbt.get("fake.key"));
+ }
+
+
+ @Test
+ public void testGetWithoutClassName() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(translator, 0);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("TestClassBasedTranslator.fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key"); will(returnValue("barbaz"));
+ }});
+ // @formatter:on
+
+ assertEquals("barbaz", cbt.get("fake.key"));
+ }
+
+
+ @Test
+ public void testMissing() {
+ ClassBasedTranslator cbt = new ClassBasedTranslator(translator, 0);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("TestClassBasedTranslator.fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ }});
+ // @formatter:on
+
+ try {
+ fail("Returned: " + cbt.get("fake.key"));
+ } catch (MissingResourceException e) {
+ assertEquals("Neither key 'TestClassBasedTranslator.fake.key' nor 'fake.key' could be found", e.getMessage());
+ }
+
+ }
+}
--- /dev/null
+package net.sf.openrocket.l10n;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.MissingResourceException;
+
+import org.jmock.Expectations;
+import org.jmock.Mockery;
+import org.jmock.auto.Mock;
+import org.jmock.integration.junit4.JMock;
+import org.jmock.integration.junit4.JUnit4Mockery;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(JMock.class)
+public class TestExceptionSuppressingTranslator {
+ Mockery context = new JUnit4Mockery();
+
+ @Mock
+ Translator translator;
+
+ @Test
+ public void testSuccessful() {
+ ExceptionSuppressingTranslator est = new ExceptionSuppressingTranslator(translator);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("fake.key"); will(returnValue("foobar"));
+ }});
+ // @formatter:on
+
+ assertEquals("foobar", est.get("fake.key"));
+ }
+
+
+ @Test
+ public void testFailure() {
+ ExceptionSuppressingTranslator est = new ExceptionSuppressingTranslator(translator);
+
+ assertEquals("Prerequisite failed", 0, ExceptionSuppressingTranslator.failures);
+
+ // @formatter:off
+ context.checking(new Expectations() {{
+ oneOf(translator).get("fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key"); will(throwException(new MissingResourceException("a", "b", "c")));
+ oneOf(translator).get("fake.key2"); will(throwException(new MissingResourceException("a", "b", "c")));
+ }});
+ // @formatter:on
+
+ // Test first failure
+ assertEquals("fake.key", est.get("fake.key"));
+ assertEquals(1, ExceptionSuppressingTranslator.failures);
+
+ // Test second failure
+ assertEquals("fake.key", est.get("fake.key"));
+ assertEquals(1, ExceptionSuppressingTranslator.failures);
+
+ // Test failure with other key
+ assertEquals("fake.key2", est.get("fake.key"));
+ assertEquals(2, ExceptionSuppressingTranslator.failures);
+ }
+
+
+}