ISIS-903: refactoring TranslationService so can write out .pot file... ... with msgid_plural if required.
Project: http://git-wip-us.apache.org/repos/asf/isis/repo Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/6ab09597 Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/6ab09597 Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/6ab09597 Branch: refs/heads/master Commit: 6ab09597317b81899cbc6dcb8a49856067f9fe84 Parents: 0512781 Author: Dan Haywood <[email protected]> Authored: Tue Feb 17 14:44:36 2015 +0000 Committer: Dan Haywood <[email protected]> Committed: Wed Feb 18 14:07:43 2015 +0000 ---------------------------------------------------------------------- .../services/i18n/TranslatableString.java | 7 +- .../services/i18n/TranslationService.java | 23 +++- .../isis/applib/services/i18n/TrStringTest.java | 113 ---------------- .../services/i18n/TranslatableStringTest.java | 135 +++++++++++++++++++ .../core/metamodel/services/i18n/po/Block.java | 4 +- .../services/i18n/po/ContextAndMsgId.java | 24 +++- .../metamodel/services/i18n/po/PoAbstract.java | 2 + .../metamodel/services/i18n/po/PoReader.java | 26 +++- .../metamodel/services/i18n/po/PoWriter.java | 77 +++++++---- .../services/i18n/po/TranslationServicePo.java | 9 +- 10 files changed, 267 insertions(+), 153 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java ---------------------------------------------------------------------- diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java index 08870a4..09d24b3 100644 --- a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java +++ b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslatableString.java @@ -177,7 +177,10 @@ public final class TranslatableString { * @return */ public String translate(final TranslationService translationService, final String context, final Locale locale) { - final String translatedText = translationService.translate(context, getPattern(), locale); + final String translatedText = + !isPluralForm() + ? translationService.translate(context, getSingularText(), locale) + : translationService.translate(context, getSingularText(), getPluralText(), number, locale); return translated(translatedText); } @@ -188,7 +191,7 @@ public final class TranslatableString { * Any placeholders will <i>not</i> have been replaced. * </p> */ - public String getPattern() { + String getPattern() { return !isPluralForm() || number == 1 ? getSingularText() : getPluralText(); } http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java ---------------------------------------------------------------------- diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java index dfad5ca..ca20004 100644 --- a/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java +++ b/core/applib/src/main/java/org/apache/isis/applib/services/i18n/TranslationService.java @@ -23,7 +23,28 @@ import org.apache.isis.applib.annotation.Programmatic; public interface TranslationService { + /** + * Return a translation of the text, for the specified locale. + * + * @param context + * @param text + * @param targetLocale + * @return + */ @Programmatic - public String translate(final String context, final String originalText, final Locale targetLocale); + public String translate(final String context, final String text, final Locale targetLocale); + + /** + * Return a translation of either the singular or the plural text, dependent on the <tt>num</tt> parameter, for the specified locale. + * + * @param context + * @param singularText + * @param pluralText + * @param num - whether to return the translation of the singular (if =1) or of the plural (if != 1) + * @param targetLocale + * @return + */ + @Programmatic + public String translate(final String context, final String singularText, final String pluralText, int num, final Locale targetLocale); } http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java ---------------------------------------------------------------------- diff --git a/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java b/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java deleted file mode 100644 index 953b6a5..0000000 --- a/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TrStringTest.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.apache.isis.applib.services.i18n; - -import java.util.Locale; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -public class TrStringTest { - - public static class GetText extends TrStringTest { - - @Test - public void singularForm() throws Exception { - final TranslatableString ts = TranslatableString.tr("No, you can't do that!"); - - assertThat(ts.getPattern(), is("No, you can't do that!")); - } - - @Test - public void pluralFormOne() throws Exception { - final TranslatableString ts = TranslatableString.trn("You can't do that because there is a dependent object", "You can't do that because there are dependent objects", 1); - - assertThat(ts.getPattern(), is("You can't do that because there is a dependent object")); - } - - @Test - public void pluralFormTwo() throws Exception { - final TranslatableString ts = TranslatableString.trn("You can't do that because there is a dependent object", "You can't do that because there are dependent objects", 2); - - assertThat(ts.getPattern(), is("You can't do that because there are dependent objects")); - } - - } - - public static class Translated extends TrStringTest { - - @Test - public void singularForm() throws Exception { - final TranslatableString ts = TranslatableString.tr("My name is {lastName}, {firstName} {lastName}.", "lastName", "Bond", "firstName", "James"); - - assertThat(ts.translated("Iche heisse {lastName}, {firstName} {lastName}."), is("Iche heisse Bond, James Bond.")); - } - - @Test - public void pluralFormOne() throws Exception { - final TranslatableString ts = TranslatableString.trn( - "My name is {lastName}, {firstName} {lastName}.", - "My name is {firstName} {lastName}.", - 1, - "lastName", "Bond", "firstName", "James"); - - assertThat(ts.translated("Iche heisse {lastName}, {firstName} {lastName}."), is("Iche heisse Bond, James Bond.")); - } - } - - public static class Translate extends TrStringTest { - - @Rule - public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES); - - private TranslationService echoTranslationService; - - private String originalTextToTranslate; - - @Before - public void setUp() throws Exception { - echoTranslationService = new TranslationService() { - @Override - public String translate(final String context, final String originalText, final Locale targetLocale) { - originalTextToTranslate = originalText; - return originalText; - } - }; - } - - @Test - public void singularForm() throws Exception { - final TranslatableString ts = TranslatableString.tr("My name is {lastName}, {firstName} {lastName}.", "lastName", "Bond", "firstName", "James"); - - assertThat(ts.translate(echoTranslationService, null, null), is("My name is Bond, James Bond.")); - assertThat(originalTextToTranslate, is("My name is {lastName}, {firstName} {lastName}.")); - } - - @Test - public void pluralFormOne() throws Exception { - final TranslatableString ts = TranslatableString.trn( - "My name is {lastName}, {firstName} {lastName}.", - "My name is {firstName} {lastName}.", - 1, - "lastName", "Bond", "firstName", "James"); - - assertThat(ts.translate(echoTranslationService, null, null), is("My name is Bond, James Bond.")); - assertThat(originalTextToTranslate, is("My name is {lastName}, {firstName} {lastName}.")); - } - - @Test - public void pluralFormTwo() throws Exception { - final TranslatableString ts = TranslatableString.trn( - "My name is {lastName}, {firstName} {lastName}.", - "My name is {firstName} {lastName}.", - 2, - "lastName", "Bond", "firstName", "James"); - - assertThat(ts.translate(echoTranslationService, null, null), is("My name is James Bond.")); - assertThat(originalTextToTranslate, is("My name is {firstName} {lastName}.")); - } - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TranslatableStringTest.java ---------------------------------------------------------------------- diff --git a/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TranslatableStringTest.java b/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TranslatableStringTest.java new file mode 100644 index 0000000..1e0f0fd --- /dev/null +++ b/core/applib/src/test/java/org/apache/isis/applib/services/i18n/TranslatableStringTest.java @@ -0,0 +1,135 @@ +package org.apache.isis.applib.services.i18n; + +import java.util.Locale; +import org.jmock.Expectations; +import org.jmock.auto.Mock; +import org.junit.Rule; +import org.junit.Test; +import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +public class TranslatableStringTest { + + public static class GetText extends TranslatableStringTest { + + @Test + public void singularForm() throws Exception { + final TranslatableString ts = TranslatableString.tr("No, you can't do that!"); + + assertThat(ts.getPattern(), is("No, you can't do that!")); + } + + @Test + public void pluralFormOne() throws Exception { + final TranslatableString ts = TranslatableString.trn("You can't do that because there is a dependent object", "You can't do that because there are dependent objects", 1); + + assertThat(ts.getPattern(), is("You can't do that because there is a dependent object")); + } + + @Test + public void pluralFormTwo() throws Exception { + final TranslatableString ts = TranslatableString.trn("You can't do that because there is a dependent object", "You can't do that because there are dependent objects", 2); + + assertThat(ts.getPattern(), is("You can't do that because there are dependent objects")); + } + + } + + public static class Translated extends TranslatableStringTest { + + @Test + public void singularForm() throws Exception { + final TranslatableString ts = TranslatableString.tr("My name is {lastName}, {firstName} {lastName}.", "lastName", "Bond", "firstName", "James"); + + assertThat(ts.translated("Iche heisse {lastName}, {firstName} {lastName}."), is("Iche heisse Bond, James Bond.")); + } + + @Test + public void pluralFormOne() throws Exception { + final TranslatableString ts = TranslatableString.trn( + "My name is {lastName}, {firstName} {lastName}.", + "My name is {firstName} {lastName}.", + 1, + "lastName", "Bond", "firstName", "James"); + + assertThat(ts.translated("Iche heisse {lastName}, {firstName} {lastName}."), is("Iche heisse Bond, James Bond.")); + } + } + + public static class Translate extends TranslatableStringTest { + + @Rule + public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES); + + @Mock + private TranslationService mockTranslationService; + + @Test + public void singularForm() throws Exception { + // given + final String simpleText = "text to translate"; + final String someContext = "someContext"; + final Locale someLocale = Locale.CANADA; // any + final String translation = "the translation"; + + final TranslatableString ts = TranslatableString.tr(simpleText); + + // expect + context.checking(new Expectations() {{ + oneOf(mockTranslationService).translate(someContext, simpleText, someLocale); + will(returnValue(translation)); + }}); + + // when + assertThat(ts.translate(mockTranslationService, someContext, someLocale), is(translation)); + } + + @Test + public void pluralFormOne() throws Exception { + + // given + final String singularText = "singular text to translate"; + final String pluralText = "plural text to translate"; + final String someContext = "someContext"; + final Locale someLocale = Locale.CANADA; // any + final String translation = "the translation"; + + final TranslatableString ts = TranslatableString.trn(singularText, pluralText, 1); + + // expect + context.checking(new Expectations() {{ + oneOf(mockTranslationService).translate(someContext, singularText, someLocale); + will(returnValue(translation)); + }}); + + // when + assertThat(ts.translate(mockTranslationService, someContext, someLocale), is(translation)); + } + + @Test + public void pluralFormTwo() throws Exception { + + // given + final String singularText = "singular text to translate"; + final String pluralText = "plural text to translate"; + final String someContext = "someContext"; + final Locale someLocale = Locale.CANADA; // any + final String translation = "the translation"; + final int number = 2; // != 1 + + final TranslatableString ts = TranslatableString.trn(singularText, pluralText, number); + + // expect + context.checking(new Expectations() {{ + oneOf(mockTranslationService).translate(someContext, singularText, pluralText, number, someLocale); + will(returnValue(translation)); + }}); + + // when + assertThat(ts.translate(mockTranslationService, someContext, someLocale), is(translation)); + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/Block.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/Block.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/Block.java index cdfbbd4..43870d9 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/Block.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/Block.java @@ -101,11 +101,11 @@ class Block { void append(final Map<ContextAndMsgId, String> translationsByKey) { for (String context : contextList) { if(msgid != null && msgstr != null) { - final ContextAndMsgId mc = new ContextAndMsgId(context, msgid); + final ContextAndMsgId mc = new ContextAndMsgId(context, msgid, ContextAndMsgId.Type.REGULAR); translationsByKey.put(mc, msgstr); } if(msgid_plural != null && msgstr_plural != null) { - final ContextAndMsgId mc = new ContextAndMsgId(context, msgid_plural); + final ContextAndMsgId mc = new ContextAndMsgId(context, msgid_plural, ContextAndMsgId.Type.PLURAL_ONLY); translationsByKey.put(mc, msgstr_plural); } } http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/ContextAndMsgId.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/ContextAndMsgId.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/ContextAndMsgId.java index 1fbfd02..2077c11 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/ContextAndMsgId.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/ContextAndMsgId.java @@ -23,12 +23,25 @@ package org.apache.isis.core.metamodel.services.i18n.po; */ public class ContextAndMsgId implements Comparable<ContextAndMsgId> { + public enum Type { + /** + * The text to use when there is no plural form, or the text to use for singular pattern when there is also a plural form. + */ + REGULAR, + /** + * The text to use for plural form. + */ + PLURAL_ONLY + } + private final String context; private final String msgId; + private final Type type; - public ContextAndMsgId(final String context, final String msgId) { - this.msgId = msgId; + public ContextAndMsgId(final String context, final String msgId, final Type type) { this.context = context == null? "": context; + this.msgId = msgId; + this.type = type; } public String getMsgId() { @@ -39,6 +52,13 @@ public class ContextAndMsgId implements Comparable<ContextAndMsgId> { return context; } + /** + * Not part of equals/hashCode impl. + */ + public Type getType() { + return type; + } + @Override public boolean equals(final Object o) { if (this == o) return true; http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoAbstract.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoAbstract.java index b910984..e3ddba9 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoAbstract.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoAbstract.java @@ -36,4 +36,6 @@ abstract class PoAbstract { abstract String translate(final String context, final String msgId, final Locale targetLocale); + abstract String translate(final String context, final String msgId, final String msgIdPlural, int num, final Locale targetLocale); + } http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoReader.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoReader.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoReader.java index 24d96db..237ae4c 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoReader.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoReader.java @@ -68,15 +68,37 @@ class PoReader extends PoAbstract { public String translate(final String context, final String msgId, final Locale targetLocale) { + return translate(context, msgId, ContextAndMsgId.Type.REGULAR, targetLocale); + } + + @Override + String translate(final String context, final String msgId, final String msgIdPlural, final int num, final Locale targetLocale) { + + final String msgIdToUse; + final ContextAndMsgId.Type type; + if (num == 1) { + msgIdToUse = msgId; + type = ContextAndMsgId.Type.REGULAR; + } else { + msgIdToUse = msgIdPlural; + type = ContextAndMsgId.Type.PLURAL_ONLY; + } + + return translate(context, msgIdToUse, type, targetLocale); + } + + private String translate( + final String context, final String msgId, final ContextAndMsgId.Type type, + final Locale targetLocale) { final Map<ContextAndMsgId, String> translationsByKey = readAndCacheTranslationsIfRequired(targetLocale); - final ContextAndMsgId key = new ContextAndMsgId(context, msgId); + final ContextAndMsgId key = new ContextAndMsgId(context, msgId, type); final String translation = translationsByKey.get(key); if (translation != null) { return translation; } - final ContextAndMsgId keyNoContext = new ContextAndMsgId("", msgId); + final ContextAndMsgId keyNoContext = new ContextAndMsgId("", msgId, type); final String translationNoContext = translationsByKey.get(keyNoContext); if (translationNoContext != null) { return translationNoContext; http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoWriter.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoWriter.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoWriter.java index d2182de..6c023cf 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoWriter.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoWriter.java @@ -18,11 +18,12 @@ */ package org.apache.isis.core.metamodel.services.i18n.po; -import java.util.Collection; import java.util.Locale; import java.util.Map; -import java.util.NavigableSet; -import com.google.common.collect.TreeMultimap; +import java.util.SortedMap; +import java.util.SortedSet; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import org.joda.time.LocalDateTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,7 +32,17 @@ class PoWriter extends PoAbstract { public static Logger LOG = LoggerFactory.getLogger(PoWriter.class); - private final TreeMultimap<String, String> contextsByMsgId = TreeMultimap.create(); + private static class Block { + private final String msgId; + private final SortedSet<String> contexts = Sets.newTreeSet(); + private String msgIdPlural; + + private Block(final String msgId) { + this.msgId = msgId; + } + } + + private final SortedMap<String, Block> blocksByMsgId = Maps.newTreeMap(); public PoWriter(final TranslationServicePo translationServicePo) { super(translationServicePo); @@ -78,42 +89,50 @@ class PoWriter extends PoAbstract { //endregion - public String translate(final String context, final String originalText, final Locale targetLocale) { - final NavigableSet<String> contexts = contextsByMsgId.get(originalText); - contexts.add(context); - return originalText; + public String translate(final String context, final String msgId, final Locale targetLocale) { + Block block = blocksByMsgId.get(msgId); + if(block == null) { + block = new Block(msgId); + } + block.contexts.add(context); + + return msgId; + } + + @Override + String translate(final String context, final String msgId, final String msgIdPlural, final int num, final Locale targetLocale) { + + Block block = blocksByMsgId.get(msgId); + if(block == null) { + block = new Block(msgId); + } + block.msgIdPlural = msgIdPlural; + block.contexts.add(context); + + return null; } /** * Not API */ String toPot() { - final Map<String, Collection<String>> messages = messagesWithContext(); final StringBuilder buf = new StringBuilder(); - for (String message : messages.keySet()) { - final Collection<String> contexts = messages.get(message); - for (String context : contexts) { + for (String msgId : blocksByMsgId.keySet()) { + final Block block = blocksByMsgId.get(msgId); + for (String context : block.contexts) { buf.append("#: ").append(context).append("\n"); } - buf.append("msgid: \"").append(message).append("\"\n"); - buf.append("msgstr: \"\"\n"); - buf.append("\n\n\n"); + buf.append("msgid \"").append(msgId).append("\"\n"); + if(block.msgIdPlural == null) { + buf.append("msgstr \"\"\n"); + } else { + buf.append("msgid_plural \"").append(block.msgIdPlural).append("\"\n"); + buf.append("msgstr[0] \"\"\n"); + buf.append("msgstr[1] \"\"\n"); + } + buf.append("\n\n"); } return buf.toString(); } - /** - * Returns the set of messages encountered and cached by the service (the key of the map) along with a set of - * context strings (the value of the map) - * - * <p> - * The intention is that an implementation running in prototype mode should retain all requests to - * {@link #translate(String, String, java.util.Locale)}, such that they can be translated and used by the - * same implementation in non-prototype mode. - * </p> - */ - Map<String, Collection<String>> messagesWithContext() { - return contextsByMsgId.asMap(); - } - } http://git-wip-us.apache.org/repos/asf/isis/blob/6ab09597/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePo.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePo.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePo.java index 785f334..e390f6d 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePo.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePo.java @@ -80,8 +80,13 @@ public class TranslationServicePo implements TranslationService { @Override @Programmatic - public String translate(final String context, final String originalText, final Locale targetLocale) { - return po.translate(context, originalText, targetLocale); + public String translate(final String context, final String text, final Locale targetLocale) { + return po.translate(context, text, targetLocale); + } + + @Override + public String translate(final String context, final String singularText, final String pluralText, final int num, final Locale targetLocale) { + return po.translate(context, singularText, pluralText, num, targetLocale); } /**
