diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nBase.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nBase.java index 3d7e752b9..e8ae95b93 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nBase.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nBase.java @@ -6,6 +6,8 @@ import java.util.stream.Collectors; import com.ibm.icu.text.PluralRules; +import javax.annotation.Nonnull; + /** * Handles the locale, ResourceBundle and String formatting for i18n @@ -91,6 +93,17 @@ public abstract class I18nBase { .map(entry -> baseKey + KEY_DELIMITER + entry).collect(Collectors.toSet()); } + protected String getRootKeyFromPlural(@Nonnull String pluralKey) { + checkPluralRulesAreLoaded(); + for (String keyword : pluralRules + .getKeywords()) { + final String suffix = KEY_DELIMITER + keyword; + if (pluralKey.endsWith(suffix)) { + return pluralKey.substring(0, pluralKey.length() - suffix.length()); + } + } + return null; + } private String formatMessageForLocale(String theMessage, Object... theMessageArguments) { String message = theMessage; if (messageExistsForLocale(theMessage, (theMessageArguments != null && theMessageArguments.length > 0))) { diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nBaseTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nBaseTest.java index 7961ddc40..2696cf74f 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nBaseTest.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nBaseTest.java @@ -2,7 +2,7 @@ package org.hl7.fhir.utilities.i18n; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.*; import java.io.BufferedReader; import java.io.IOException; @@ -32,7 +32,7 @@ class I18nBaseTest { String result = testClass.formatMessage(I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ARG_1); MessageFormat form = new MessageFormat(loadedBundle.getString(I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES)); Object[] testArgs = {ARG_1}; - Assertions.assertEquals(form.format(testArgs), result); + assertEquals(form.format(testArgs), result); } @Test @@ -43,7 +43,7 @@ class I18nBaseTest { String result = testClass.formatMessage(I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES, ARG_1); MessageFormat form = new MessageFormat(loadedBundle.getString(I18nConstants.BUNDLE_BUNDLE_MULTIPLEMATCHES)); Object[] testArgs = {ARG_1}; - Assertions.assertEquals(form.format(testArgs), result); + assertEquals(form.format(testArgs), result); } @Test @@ -81,7 +81,7 @@ class I18nBaseTest { @DisplayName("Assert no string modification is done when no match is found.") void testFormatMessageForNonExistentMessage() { I18nTestClass testClass = new I18nTestClass(); - Assertions.assertEquals(BAD_STRING_ARG, testClass.formatMessage(BAD_STRING_ARG, ARG_1)); + assertEquals(BAD_STRING_ARG, testClass.formatMessage(BAD_STRING_ARG, ARG_1)); } @Test @@ -92,7 +92,7 @@ class I18nBaseTest { testClass.setLocale(Locale.GERMAN); String result = testClass.formatMessage(I18nConstants.BUNDLE_BUNDLE_ENTRY_NOFIRST, ARG_1); //Ensure the umlaut is displayed correctly. If not right, will show: ü, not ü - Assertions.assertEquals("Documents oder Messages müssen mindestens einen Eintrag enthalten", result); + assertEquals("Documents oder Messages müssen mindestens einen Eintrag enthalten", result); } @Test @@ -118,5 +118,23 @@ class I18nBaseTest { return Arrays.stream(items).anyMatch(inputStr::contains); } + @Test + public void testRootKeyFromPlural() { + I18nTestClass esLocale = new I18nTestClass(); + esLocale.setLocale(Locale.forLanguageTag("es")); + + final String rootKey = "MY_KEY"; + + assertEquals(rootKey, esLocale.getRootKeyFromPlural(rootKey + "_one")); + assertEquals(rootKey, esLocale.getRootKeyFromPlural(rootKey + "_many")); + assertEquals(rootKey, esLocale.getRootKeyFromPlural(rootKey + "_other")); + + I18nTestClass enLocale = new I18nTestClass(); + enLocale.setLocale(Locale.forLanguageTag("en")); + + assertEquals(rootKey, enLocale.getRootKeyFromPlural(rootKey + "_one")); + assertEquals(rootKey, enLocale.getRootKeyFromPlural(rootKey + "_other")); + assertNull(enLocale.getRootKeyFromPlural(rootKey + "_many")); + } } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nCoverageTest.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nCoverageTest.java index 095d9d11e..977bb0bee 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nCoverageTest.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/i18n/I18nCoverageTest.java @@ -7,17 +7,14 @@ import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.Set; +import java.util.*; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; public class I18nCoverageTest { - private final Logger logger = LoggerFactory.getLogger(I18nCoverageTest.class); + final Set locales = Set.of( Locale.ENGLISH, @@ -28,6 +25,7 @@ public class I18nCoverageTest { @Test public void testCoverage() throws IllegalAccessException { + Field[] fields = I18nConstants.class.getDeclaredFields(); Map testClassMap = new HashMap<>(); @@ -35,14 +33,18 @@ public class I18nCoverageTest { testClassMap.put(locale, getI18nTestClass(locale)); } + Set messages = new HashSet<>(); + for (Field field : fields) { + String message = (String)field.get(new String()); + messages.add(message); if (field.getType() == String.class) { Map isSingularPhrase = new HashMap<>(); Map isPluralPhrase = new HashMap<>(); for (Locale locale : locales) { I18nBase base = testClassMap.get(locale); - String message = (String)field.get(new String()); + isSingularPhrase.put(locale, base.messageKeyExistsForLocale(message)); isPluralPhrase.put(locale, existsAsPluralPhrase(base, message)); @@ -52,6 +54,26 @@ public class I18nCoverageTest { logMissingPhrases(field, isSingularPhrase, isPluralPhrase); } } + + for (Locale locale : locales) { + ResourceBundle i18nMessages = ResourceBundle.getBundle("Messages", locale); + for (String message : i18nMessages.keySet()) { + boolean mapsToConstant = messages.contains(message); + boolean mapsToPluralPhrase = mapsToPluralPhrase(messages, message, testClassMap.get(locale)); + if (!(mapsToConstant || mapsToPluralPhrase)) { + System.err.println("Message " + message + " in " + locale.getLanguage() + " properties resource does not have a matching entry in " + I18nConstants.class.getName() ); + } + } + } + + } + + private boolean mapsToPluralPhrase(Set messages, String message, I18nBase base) { + String rootKey = base.getRootKeyFromPlural(message); + if (rootKey != null) { + return messages.contains(rootKey); + } + return false; } private void assertPhraseTypeAgreement(Field field, @@ -75,7 +97,7 @@ public class I18nCoverageTest { boolean existsInSomeLanguage = existsAsSingular || existsAsPlural; if (!existsInSomeLanguage) { - logger.warn("Constant " + field.getName() + " does not exist in any I18n property definition"); + System.err.println("Constant " + field.getName() + " does not exist in any I18n property definition"); return; }; if (existsAsSingular) { @@ -89,7 +111,7 @@ public class I18nCoverageTest { private void logMissingPhrases(Field field, Map phraseMap, String phraseType) { for (Locale locale : locales) { if (!phraseMap.get(locale)) { - logger.warn("Constant " + field.getName() + " is missing in I18n " + phraseType + " phrase property definition for locale " + locale.getLanguage()); + System.err.println("Constant " + field.getName() + " is missing in I18n " + phraseType + " phrase property definition for locale " + locale.getLanguage()); } } }