From 3b5035c4ce0583b9dddc6d780194e7ce47781b80 Mon Sep 17 00:00:00 2001 From: Tim Jacomb <21194782+timja@users.noreply.github.com> Date: Mon, 9 May 2022 16:39:33 +0100 Subject: [PATCH] Use `PropertyResourceBundle` for localization (#357) Co-authored-by: Basil Crow Co-authored-by: Jesse Glick --- .../kohsuke/stapler/jelly/ResourceBundle.java | 42 +++++++------ .../stapler/jelly/ResourceBundleTest.java | 63 +++++++++++++++++++ .../jelly/ResourceBundleTest/index.properties | 1 + .../ResourceBundleTest/index_en_CA.properties | 1 + .../ResourceBundleTest/index_fr_FR.properties | 1 + .../ResourceBundleTest/index_zh_CN.properties | 1 + .../ResourceBundleTest/index_zh_TW.properties | 1 + 7 files changed, 90 insertions(+), 20 deletions(-) create mode 100644 jelly/src/test/java/org/kohsuke/stapler/jelly/ResourceBundleTest.java create mode 100644 jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index.properties create mode 100644 jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_en_CA.properties create mode 100644 jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_fr_FR.properties create mode 100644 jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_CN.properties create mode 100644 jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_TW.properties diff --git a/jelly/src/main/java/org/kohsuke/stapler/jelly/ResourceBundle.java b/jelly/src/main/java/org/kohsuke/stapler/jelly/ResourceBundle.java index 3f2bbb4714..aaabb40be5 100644 --- a/jelly/src/main/java/org/kohsuke/stapler/jelly/ResourceBundle.java +++ b/jelly/src/main/java/org/kohsuke/stapler/jelly/ResourceBundle.java @@ -24,6 +24,12 @@ package org.kohsuke.stapler.jelly; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import java.io.FileNotFoundException; +import java.io.UncheckedIOException; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.PropertyResourceBundle; import org.kohsuke.stapler.MetaClass; import org.kohsuke.stapler.WebApp; @@ -39,7 +45,7 @@ /** * Cache of localization strings. - * + * * @author Kohsuke Kawaguchi */ public class ResourceBundle { @@ -142,29 +148,25 @@ protected Properties get(String key) { // attempt to load props = new Properties(); String url = baseName + key + ".properties"; - InputStream in=null; - try { - in = openStream(url); - // an user reported that on IBM JDK, URL.openStream - // returns null instead of IOException. - // see http://www.nabble.com/WAS---Hudson-tt16026561.html - } catch (IOException e) { - // failed. - } + try (InputStream in = openStream(url)) { + PropertyResourceBundle propertyResourceBundle = new PropertyResourceBundle(in); - if(in!=null) { - try { - try { - props.load(in); - } finally { - in.close(); - } - } catch (IOException e) { - throw new Error("Failed to load "+url,e); + Enumeration keys = propertyResourceBundle.getKeys(); + // TODO Java 9+ can use 'asIterator' and get rid of below collections conversion + List keysAsSaneType = Collections.list(keys); + + for (String localKey : keysAsSaneType) { + String value = propertyResourceBundle.getString(localKey); + props.setProperty(localKey, value); } + + } catch (FileNotFoundException ignored) { + // we fall back to the default properties file if a locale file is missing + } catch (IOException e) { + throw new UncheckedIOException("Failed to load " + url + ": " + e, e); } - resources.put(key,wrapUp(key.length()>0 ? key.substring(1) : "",props)); + resources.put(key,wrapUp(key.length()>0 ? key.substring(1) : "", props)); return props; } diff --git a/jelly/src/test/java/org/kohsuke/stapler/jelly/ResourceBundleTest.java b/jelly/src/test/java/org/kohsuke/stapler/jelly/ResourceBundleTest.java new file mode 100644 index 0000000000..8ab8cf5c8a --- /dev/null +++ b/jelly/src/test/java/org/kohsuke/stapler/jelly/ResourceBundleTest.java @@ -0,0 +1,63 @@ +package org.kohsuke.stapler.jelly; + +import hudson.util.VersionNumber; +import io.jenkins.lib.versionnumber.JavaSpecificationVersion; +import java.net.URL; +import java.util.Locale; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.Assume.assumeThat; + +public class ResourceBundleTest { + + private static final URL RESOURCE_BUNDLE = ResourceBundleTest.class.getResource("/org/kohsuke/stapler/jelly/ResourceBundleTest/index.properties"); + private static final String FILE_PATH = RESOURCE_BUNDLE.toExternalForm().replace(".properties", ""); + private static final ResourceBundle resourceBundle = new ResourceBundle(FILE_PATH); + + @Test + public void format() { + String helloWorld = resourceBundle.format(Locale.ENGLISH, "hello_world"); + + assertThat(helloWorld, is("Hello, World!")); + } + + @Test + public void formatFallsBackToDefaultIfMissing() { + String helloWorld = resourceBundle.format(Locale.FRANCE, "hello_world"); + + assertThat(helloWorld, is("Hello, World!")); + } + + @Test + public void formatLocaleOverrideInPlace() { + String helloWorld = resourceBundle.format(Locale.CANADA, "hello_world"); + + assertThat(helloWorld, is("Welcome, World!")); + } + + @Test + public void formatLocaleNonISO_8859_1() { + String helloWorld = resourceBundle.format(Locale.CHINA, "hello_world"); + + assertThat(helloWorld, is("你好,世界!简化")); + } + + @Test + public void formatLocaleNonISO_8859_1_encoded_with_utf8() { + assumeThat("Requires Java 9+", true, is(JavaSpecificationVersion.forCurrentJVM() + .isNewerThan(new VersionNumber("8")) + )); + String helloWorld = resourceBundle.format(Locale.TRADITIONAL_CHINESE, "hello_world"); + + assertThat(helloWorld, is("你好世界!")); + } + + @Test + public void formatLocaleISO_8859_1_high_range_character_invalid_utf_8() { + String helloWorld = resourceBundle.format(Locale.FRANCE, "french"); + + assertThat(helloWorld, is("Français")); + } +} diff --git a/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index.properties b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index.properties new file mode 100644 index 0000000000..6ddd921dcb --- /dev/null +++ b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index.properties @@ -0,0 +1 @@ +hello_world=Hello, World! diff --git a/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_en_CA.properties b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_en_CA.properties new file mode 100644 index 0000000000..b087240002 --- /dev/null +++ b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_en_CA.properties @@ -0,0 +1 @@ +hello_world=Welcome, World! diff --git a/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_fr_FR.properties b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_fr_FR.properties new file mode 100644 index 0000000000..9fb0ec048e --- /dev/null +++ b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_fr_FR.properties @@ -0,0 +1 @@ +french=Franais diff --git a/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_CN.properties b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_CN.properties new file mode 100644 index 0000000000..2d886e0a0a --- /dev/null +++ b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_CN.properties @@ -0,0 +1 @@ +hello_world=\u4f60\u597d\uff0c\u4e16\u754c\uff01\u7b80\u5316 diff --git a/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_TW.properties b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_TW.properties new file mode 100644 index 0000000000..a8972c3c72 --- /dev/null +++ b/jelly/src/test/resources/org/kohsuke/stapler/jelly/ResourceBundleTest/index_zh_TW.properties @@ -0,0 +1 @@ +hello_world=你好世界!