ISIS-903: allow translations to be read from externalized config location. In addition, pick up value of deploymentType by reading IsisContext.getDeploymentType() rather than the hacky way of reading config; this should work in all situations (org.apache.isis.WebServer, integtetss, tomcat etc). This necessitates moving the TranslatioServiePo implementation (and supporting classes) from core/metamodel to core/runtime.
Project: http://git-wip-us.apache.org/repos/asf/isis/repo Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/362a5bfc Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/362a5bfc Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/362a5bfc Branch: refs/heads/master Commit: 362a5bfcc7f5a8f45c15c9f5c242a3b4618ce3e5 Parents: 2cf70db Author: Dan Haywood <[email protected]> Authored: Wed Feb 18 11:32:17 2015 +0000 Committer: Dan Haywood <[email protected]> Committed: Wed Feb 18 14:07:54 2015 +0000 ---------------------------------------------------------------------- .../services/TranslationsResolverWicket.java | 28 ++- .../TranslationsResolverWicketTest.java | 37 ++++ .../all/i18n/TranslationFacetFactory.java | 3 +- .../core/metamodel/services/i18n/po/Block.java | 114 ---------- .../services/i18n/po/ContextAndMsgId.java | 90 -------- .../metamodel/services/i18n/po/PoAbstract.java | 43 ---- .../metamodel/services/i18n/po/PoReader.java | 190 ----------------- .../metamodel/services/i18n/po/PoWriter.java | 141 ------------ .../services/i18n/po/TranslationServicePo.java | 138 ------------ .../i18n/po/TranslationServicePoMenu.java | 94 -------- .../services/i18n/po/PoReaderTest.java | 212 ------------------- .../core/runtime/services/i18n/po/Block.java | 114 ++++++++++ .../services/i18n/po/ContextAndMsgId.java | 90 ++++++++ .../runtime/services/i18n/po/PoAbstract.java | 43 ++++ .../core/runtime/services/i18n/po/PoReader.java | 190 +++++++++++++++++ .../core/runtime/services/i18n/po/PoWriter.java | 141 ++++++++++++ .../services/i18n/po/TranslationServicePo.java | 149 +++++++++++++ .../i18n/po/TranslationServicePoMenu.java | 94 ++++++++ .../core/webapp/IsisWebAppBootstrapper.java | 6 +- .../runtime/services/i18n/po/PoReaderTest.java | 212 +++++++++++++++++++ 20 files changed, 1100 insertions(+), 1029 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicket.java ---------------------------------------------------------------------- diff --git a/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicket.java b/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicket.java index 0a104b8..110854a 100644 --- a/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicket.java +++ b/component/viewer/wicket/impl/src/main/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicket.java @@ -18,18 +18,22 @@ */ package org.apache.isis.viewer.wicket.viewer.services; +import java.io.File; import java.io.IOException; import java.net.URL; +import java.nio.file.Path; import java.util.List; import javax.servlet.ServletContext; import com.google.common.base.Charsets; import com.google.common.io.CharSource; +import com.google.common.io.Files; import com.google.common.io.Resources; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.isis.applib.annotation.DomainService; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.services.i18n.TranslationsResolver; +import org.apache.isis.core.webapp.WebAppConstants; import org.apache.isis.viewer.wicket.viewer.IsisWicketApplication; @@ -44,15 +48,33 @@ public class TranslationsResolverWicket implements TranslationsResolver { @Override @Programmatic public List<String> readLines(final String file) { + final ServletContext servletContext = getServletContext(); + + final String configLocation = servletContext.getInitParameter(WebAppConstants.CONFIG_DIR_PARAM); try { - final ServletContext servletContext = getIsisWicketApplication().getServletContext(); - final URL url = servletContext.getResource("/WEB-INF/" + file); - return readLines(url); + if(configLocation != null) { + LOG.info( "Reading translations relative to config override location: " + configLocation ); + return Files.readLines(newFile(configLocation, file), Charsets.UTF_8); + } else { + final URL url = servletContext.getResource("/WEB-INF/" + file); + return readLines(url); + } } catch (final RuntimeException | IOException ignored) { return null; } } + static File newFile(final String dir, final String file) { + final File base = new File(dir); + final Path path = base.toPath(); + final Path resolve = path.resolve(file); + return resolve.toFile(); + } + + protected ServletContext getServletContext() { + return getIsisWicketApplication().getServletContext(); + } + private static List<String> readLines(final URL url) throws IOException { if(url == null) { return null; http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/component/viewer/wicket/impl/src/test/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicketTest.java ---------------------------------------------------------------------- diff --git a/component/viewer/wicket/impl/src/test/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicketTest.java b/component/viewer/wicket/impl/src/test/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicketTest.java new file mode 100644 index 0000000..2190a62 --- /dev/null +++ b/component/viewer/wicket/impl/src/test/java/org/apache/isis/viewer/wicket/viewer/services/TranslationsResolverWicketTest.java @@ -0,0 +1,37 @@ +package org.apache.isis.viewer.wicket.viewer.services; + +import java.io.File; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assume.assumeThat; + +public class TranslationsResolverWicketTest { + + public static class NewFile extends TranslationsResolverWicketTest { + + @Before + public void setUp() throws Exception { + assumeThat(System.getProperty("os.name").startsWith("Windows"), is(true)); + } + + @Test + public void simple() throws Exception { + final File file = TranslationsResolverWicket.newFile("c:/foo", "bar"); + final String absolutePath = file.getAbsolutePath(); + assertThat(absolutePath, is("c:\\foo\\bar")); + } + + @Test + public void nestedChild() throws Exception { + final File file = TranslationsResolverWicket.newFile("c:/foo", "bar/baz"); + final String absolutePath = file.getAbsolutePath(); + assertThat(absolutePath, is("c:\\foo\\bar\\baz")); + } + + } + + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/TranslationFacetFactory.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/TranslationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/TranslationFacetFactory.java index 4200a95..bcadd2c 100644 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/TranslationFacetFactory.java +++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/all/i18n/TranslationFacetFactory.java @@ -127,7 +127,8 @@ public class TranslationFacetFactory extends FacetFactoryAbstract implements Con * Looks up from {@link org.apache.isis.core.metamodel.runtimecontext.ServicesInjector}. * * <p> - * There is guaranteed to be an instance because {@link org.apache.isis.core.metamodel.services.i18n.po.TranslationServicePo} is annotated as a {@link org.apache.isis.applib.annotation.DomainService @DomainService}. + * There is guaranteed to be an instance because <code>TranslationServicePo</code> (in runtime) is annotated + * as a {@link org.apache.isis.applib.annotation.DomainService @DomainService}. * </p> */ TranslationService lookupTranslationService() { http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/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 deleted file mode 100644 index 43870d9..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/Block.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.apache.isis.core.metamodel.services.i18n.po; - -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import com.google.common.collect.Lists; - -class Block { - - private enum State { - CONTEXT("^#: (?<value>.+)$"), - MSGID("^msgid \"(?<value>.+)\"$"), - MSGID_PLURAL("^msgid_plural \"(?<value>.+)\"$"), - MSGSTR("^msgstr \"(?<value>.+)\"$"), - MSGSTR0("^msgstr\\[0\\] \"(?<value>.+)\"$"), - MSGSTR1("^msgstr\\[1\\] \"(?<value>.+)\"$"); - - private final Pattern pattern; - - private State(final String regex) { - pattern = Pattern.compile(regex); - } - } - - State state = State.CONTEXT; - - List<String> contextList = Lists.newArrayList(); - String msgid = null; - String msgid_plural = null; - String msgstr = null; // either from msgstr or msgstr[0] if there is a plural - String msgstr_plural = null; // from msgstr[1] - - Block parseLine(final String line, final Map<ContextAndMsgId, String> translationsByKey) { - if (state == State.CONTEXT) { - final Matcher contextMatcher = state.pattern.matcher(line); - if (contextMatcher.matches()) { - final String context = contextMatcher.group("value"); - contextList.add(context); - return this; - } else { - state = State.MSGID; - // fallthrough (there may not have been any more context) - } - } - - if (state == State.MSGID) { - final Matcher msgidMatcher = state.pattern.matcher(line); - if (msgidMatcher.matches()) { - msgid = msgidMatcher.group("value"); - state = State.MSGID_PLURAL; // found, next time look for plurals - } else { - return new Block(); - } - return this; - } - - if (state == State.MSGID_PLURAL) { - final Matcher msgIdPluralMatcher = state.pattern.matcher(line); - if (msgIdPluralMatcher.matches()) { - msgid_plural = msgIdPluralMatcher.group("value"); - state = State.MSGSTR0; // next time look for msgstr[0] - return this; - } else { - state = State.MSGSTR; // fall through (there may not have been any plural form) - } - } - - if (state == State.MSGSTR) { - final Matcher msgStrMatcher = state.pattern.matcher(line); - if (msgStrMatcher.matches()) { - msgstr = msgStrMatcher.group("value"); - } - append(translationsByKey); - return new Block(); - } - - if (state == State.MSGSTR0) { - final Matcher msgStr0Matcher = state.pattern.matcher(line); - if (msgStr0Matcher.matches()) { - msgstr = msgStr0Matcher.group("value"); - state = State.MSGSTR1; // next time, look for plural - } else { - append(translationsByKey); - return new Block(); - } - return this; - } - - if (state == State.MSGSTR1) { - final Matcher msgStr1Matcher = state.pattern.matcher(line); - if (msgStr1Matcher.matches()) { - msgstr_plural = msgStr1Matcher.group("value"); - } - append(translationsByKey); - return new Block(); - } - return this; - } - - void append(final Map<ContextAndMsgId, String> translationsByKey) { - for (String context : contextList) { - if(msgid != null && msgstr != null) { - 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, ContextAndMsgId.Type.PLURAL_ONLY); - translationsByKey.put(mc, msgstr_plural); - } - } - } - -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/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 deleted file mode 100644 index 2077c11..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/ContextAndMsgId.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.apache.isis.core.metamodel.services.i18n.po; - -/** - * The combination of a <tt>msgId</tt> and context (optionally null) that represents a key to a translatable resource. - * - * <p> - * For example, with this <i>.pot</i> file: - * </p> - * <pre> - * #: org.isisaddons.module.sessionlogger.dom.SessionLoggingServiceMenu#activeSessions() - msgid: "Active Sessions" - - #: org.isisaddons.module.audit.dom.AuditingServiceMenu - #: org.isisaddons.module.command.dom.CommandServiceMenu - #: org.isisaddons.module.publishing.dom.PublishingServiceMenu - msgid: "Activity" - - * </pre> - * - * <p> - * the combination of <code>{org.isisaddons.module.sessionlogger.dom.SessionLoggingServiceMenu#activeSessions(), "Active Sessions"}</code> represents such a key, as does <code>{org.isisaddons.module.audit.dom.AuditingServiceMenu, "Activity"}</code> - * </p> - */ -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, final Type type) { - this.context = context == null? "": context; - this.msgId = msgId; - this.type = type; - } - - public String getMsgId() { - return msgId; - } - - public String getContext() { - 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; - if (o == null || getClass() != o.getClass()) return false; - - final ContextAndMsgId that = (ContextAndMsgId) o; - - if (context != null ? !context.equals(that.context) : that.context != null) return false; - if (msgId != null ? !msgId.equals(that.msgId) : that.msgId != null) return false; - - return true; - } - - @Override - public int hashCode() { - int result = context != null ? context.hashCode() : 0; - result = 31 * result + (msgId != null ? msgId.hashCode() : 0); - return result; - } - - @Override - public int compareTo(final ContextAndMsgId o) { - final int i = msgId.compareTo(o.msgId); - if(i != 0) { - return i; - } - return context.compareTo(o.context); - } -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/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 deleted file mode 100644 index e732d46..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoAbstract.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.isis.core.metamodel.services.i18n.po; - -import org.apache.isis.applib.services.i18n.TranslationService; - -abstract class PoAbstract { - - protected final TranslationServicePo translationServicePo; - private final TranslationService.Mode mode; - - PoAbstract(final TranslationServicePo translationServicePo, final TranslationService.Mode mode) { - this.translationServicePo = translationServicePo; - this.mode = mode; - } - - - abstract void shutdown(); - - abstract String translate(final String context, final String msgId); - - abstract String translate(final String context, final String msgId, final String msgIdPlural, int num); - - public TranslationService.Mode getMode() { - return mode; - } -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/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 deleted file mode 100644 index e0bb2b7..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoReader.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.isis.core.metamodel.services.i18n.po; - -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import com.google.common.base.Strings; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.isis.applib.services.i18n.TranslationService; -import org.apache.isis.applib.services.i18n.TranslationsResolver; - -class PoReader extends PoAbstract { - - public static final String LOCATION_BASE_URL = "isis.services.translation.po.locationBaseUrl"; - public static Logger LOG = LoggerFactory.getLogger(PoReader.class); - - private final Map<Locale, Map<ContextAndMsgId, String>> translationByKeyByLocale = Maps.newHashMap(); - - /** - * The basename of the translations file, hard-coded to <tt>translations</tt>. - * - * <p> - * This means that the reader will search for <tt>translations_en-US.po</tt>, <tt>translations_en.po</tt>, - * <tt>translations.po</tt>, according to the location that the provided {@link org.apache.isis.applib.services.i18n.TranslationsResolver} searches. For example, if using the Wicket implementation, then will search for these files - * under <tt>/WEB-INF</tt> directory. - * </p> - */ - private final String basename = "translations"; - - private List<String> fallback; - - public PoReader(final TranslationServicePo translationServicePo) { - super(translationServicePo, TranslationService.Mode.READ); - } - - //region > init, shutdown - void init(final Map<String,String> config) { - fallback = readUrl(basename + ".po"); - if(fallback == null) { - LOG.warn("No fallback translations found"); - fallback = Collections.emptyList(); - } - } - - @Override - void shutdown() { - } - //endregion - - public String translate(final String context, final String msgId) { - return translate(context, msgId, ContextAndMsgId.Type.REGULAR); - } - - @Override - String translate(final String context, final String msgId, final String msgIdPlural, final int num) { - - 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); - } - - private String translate( - final String context, final String msgId, final ContextAndMsgId.Type type) { - - final Locale targetLocale; - try { - targetLocale = translationServicePo.getLocaleProvider().getLocale(); - } catch(final RuntimeException ex){ - LOG.warn("Failed to obtain locale, returning the original msgId"); - return msgId; - } - - final Map<ContextAndMsgId, String> translationsByKey = readAndCacheTranslationsIfRequired(targetLocale); - - final ContextAndMsgId key = new ContextAndMsgId(context, msgId, type); - final String translation = lookupTranslation(translationsByKey, key); - if (!Strings.isNullOrEmpty(translation)) { - return translation; - } - - final ContextAndMsgId keyNoContext = new ContextAndMsgId("", msgId, type); - final String translationNoContext = lookupTranslation(translationsByKey, keyNoContext); - if (!Strings.isNullOrEmpty(translationNoContext)) { - return translationNoContext; - } - - LOG.warn("No translation found for: " + key); - return msgId; - } - - private String lookupTranslation(final Map<ContextAndMsgId, String> translationsByKey, final ContextAndMsgId key) { - final String s = translationsByKey.get(key); - return s != null? s.trim(): null; - } - - private Map<ContextAndMsgId, String> readAndCacheTranslationsIfRequired(final Locale locale) { - Map<ContextAndMsgId, String> translationsByKey = translationByKeyByLocale.get(locale); - if(translationsByKey != null) { - return translationsByKey; - } - - translationsByKey = Maps.newHashMap(); - read(locale, translationsByKey); - translationByKeyByLocale.put(locale, translationsByKey); - - return translationsByKey; - } - - - /** - * @param locale - the .po file to load - * @param translationsByKey - the translations to be populated - */ - private void read(final Locale locale, final Map<ContextAndMsgId, String> translationsByKey) { - final List<String> contents = readPo(locale); - - Block block = new Block(); - for (final String line : contents) { - block = block.parseLine(line, translationsByKey); - } - } - - protected List<String> readPo(final Locale locale) { - final List<String> lines = readPoElseNull(locale); - if(lines != null) { - return lines; - } - LOG.warn("Could not locate translations for locale: " + locale + ", using fallback"); - return fallback; - } - - private List<String> readPoElseNull(final Locale locale) { - final String country = locale.getCountry().toUpperCase(Locale.ROOT); - final String language = locale.getLanguage().toLowerCase(Locale.ROOT); - - final List<String> candidates = Lists.newArrayList(); - if(!Strings.isNullOrEmpty(language)) { - if(!Strings.isNullOrEmpty(country)) { - candidates.add(basename + "_" + language + "-" + country+ ".po"); - } - candidates.add(basename + "_" + language + ".po"); - } - - for (final String candidate : candidates) { - final List<String> lines = readUrl(candidate); - if(lines != null) { - return lines; - } - } - return null; - } - - private List<String> readUrl(final String candidate) { - final TranslationsResolver translationsResolver = translationServicePo.getTranslationsResolver(); - if(translationsResolver == null) { - return null; - } - return translationsResolver.readLines(candidate); - } - -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/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 deleted file mode 100644 index 6ff8117..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/PoWriter.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.isis.core.metamodel.services.i18n.po; - -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; -import org.apache.isis.applib.services.i18n.TranslationService; - -class PoWriter extends PoAbstract { - - public static Logger LOG = LoggerFactory.getLogger(PoWriter.class); - - 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, TranslationService.Mode.WRITE); - } - - //region > shutdown - - @Override - void shutdown() { - final StringBuilder buf = new StringBuilder(); - buf.append("\n"); - buf.append("\n##############################################################################"); - buf.append("\n#"); - buf.append("\n# .pot file"); - buf.append("\n#"); - buf.append("\n# generated at: ").append(LocalDateTime.now().toString("yyyy-MM-dd HH:mm:ss")); - buf.append("\n# generated by: ").append(TranslationServicePo.class.getSimpleName()); - buf.append("\n#"); - buf.append("\n# Translate this file to each required language and place in WEB-INF, eg:"); - buf.append("\n#"); - buf.append("\n# /WEB-INF/translations_en-US.po"); - buf.append("\n# /WEB-INF/translations_en.po"); - buf.append("\n# /WEB-INF/translations_fr-FR.po"); - buf.append("\n# /WEB-INF/translations_fr.po"); - buf.append("\n# /WEB-INF/translations.po"); - buf.append("\n#"); - buf.append("\n# If the app uses TranslatableString (eg for internationalized validation"); - buf.append("\n# messages), or if the app calls the TranslationService directly, then ensure"); - buf.append("\n# that all text to be translated has been captured by running a full"); - buf.append("\n# integration test suite that exercises all relevant behaviour"); - buf.append("\n#"); - buf.append("\n##############################################################################"); - buf.append("\n"); - buf.append("\n"); - buf.append(toPot()); - buf.append("\n"); - buf.append("\n"); - buf.append("\n##############################################################################"); - buf.append("\n# end of .pot file"); - buf.append("\n##############################################################################"); - buf.append("\n"); - LOG.info(buf.toString()); - } - //endregion - - - public String translate(final String context, final String msgId) { - - final Block block = blockFor(msgId); - block.contexts.add(context); - - return msgId; - } - - @Override - String translate(final String context, final String msgId, final String msgIdPlural, final int num) { - - final Block block = blockFor(msgId); - block.contexts.add(context); - block.msgIdPlural = msgIdPlural; - - return null; - } - - private Block blockFor(final String msgId) { - Block block = blocksByMsgId.get(msgId); - if(block == null) { - block = new Block(msgId); - blocksByMsgId.put(msgId, block); - } - return block; - } - - /** - * Not API - */ - String toPot() { - final StringBuilder buf = new StringBuilder(); - for (final String msgId : blocksByMsgId.keySet()) { - final Block block = blocksByMsgId.get(msgId); - for (final String context : block.contexts) { - buf.append("#: ").append(context).append("\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(); - } - -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/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 deleted file mode 100644 index 5dda93b..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePo.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.isis.core.metamodel.services.i18n.po; - -import java.util.Map; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.apache.isis.applib.annotation.DomainService; -import org.apache.isis.applib.annotation.Programmatic; -import org.apache.isis.applib.services.i18n.LocaleProvider; -import org.apache.isis.applib.services.i18n.TranslationService; -import org.apache.isis.applib.services.i18n.TranslationsResolver; - -@DomainService -public class TranslationServicePo implements TranslationService { - - public static Logger LOG = LoggerFactory.getLogger(TranslationServicePo.class); - - public static final String KEY_DEPLOYMENT_TYPE = "isis.deploymentType"; - public static final String KEY_PO_MODE = "isis.services.translation.po.mode"; - - private PoAbstract po; - - /** - * Defaults to writer mode because the service won't have been init'd while the metamodel is bring instantiated, - * and we want to ensure that we capture all requests for translation. - */ - public TranslationServicePo() { - po = new PoWriter(this); - } - - //region > init, shutdown - - @Programmatic - @PostConstruct - public void init(final Map<String,String> config) { - - if(getLocaleProvider() == null || getTranslationsResolver() == null) { - // remain in write mode - return; - } - - final String deploymentType = config.get(KEY_DEPLOYMENT_TYPE); - boolean prototypeOrTest = deploymentType==null || - deploymentType.toLowerCase().contains("prototype") || - deploymentType.toLowerCase().contains("test") ; - - final String translationMode = config.get(KEY_PO_MODE); - final boolean forceRead = - translationMode != null && - ("read".equalsIgnoreCase(translationMode) || - "reader".equalsIgnoreCase(translationMode)); - - if(prototypeOrTest && !forceRead) { - // remain in write mode - return; - } - - // switch to read mode - final PoReader poReader = new PoReader(this); - poReader.init(config); - po = poReader; - } - - @Programmatic - @PreDestroy - public void shutdown() { - po.shutdown(); - } - //endregion - - - @Override - @Programmatic - public String translate(final String context, final String text) { - return po.translate(context, text); - } - - @Override - public String translate(final String context, final String singularText, final String pluralText, final int num) { - return po.translate(context, singularText, pluralText, num); - } - - @Override - public Mode getMode() { - return po.getMode(); - } - - /** - * Not API - */ - @Programmatic - public String toPo() { - if (!getMode().isWrite()) { - throw new IllegalStateException("Not in write mode"); - } - return ((PoWriter)po).toPot(); - } - - // ////////////////////////////////////// - - @Inject - private - TranslationsResolver translationsResolver; - - @Programmatic - TranslationsResolver getTranslationsResolver() { - return translationsResolver; - } - - @Inject - private - LocaleProvider localeProvider; - - @Programmatic - LocaleProvider getLocaleProvider() { - return localeProvider; - } -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePoMenu.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePoMenu.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePoMenu.java deleted file mode 100644 index c39f98a..0000000 --- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/i18n/po/TranslationServicePoMenu.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.isis.core.metamodel.services.i18n.po; - -import java.util.List; -import javax.inject.Inject; -import org.apache.isis.applib.Identifier; -import org.apache.isis.applib.IsisApplibModule; -import org.apache.isis.applib.annotation.Action; -import org.apache.isis.applib.annotation.ActionLayout; -import org.apache.isis.applib.annotation.DomainService; -import org.apache.isis.applib.annotation.DomainServiceLayout; -import org.apache.isis.applib.annotation.NatureOfService; -import org.apache.isis.applib.annotation.ParameterLayout; -import org.apache.isis.applib.annotation.RestrictTo; -import org.apache.isis.applib.annotation.SemanticsOf; -import org.apache.isis.applib.value.Clob; - -@DomainService( - nature = NatureOfService.VIEW_MENU_ONLY -) -@DomainServiceLayout( - menuBar = DomainServiceLayout.MenuBar.SECONDARY, - named = "Prototyping" -) -public class TranslationServicePoMenu { - - public static abstract class ActionDomainEvent extends IsisApplibModule.ActionDomainEvent<TranslationServicePoMenu> { - public ActionDomainEvent(final TranslationServicePoMenu source, final Identifier identifier) { - super(source, identifier); - } - - public ActionDomainEvent(final TranslationServicePoMenu source, final Identifier identifier, final Object... arguments) { - super(source, identifier, arguments); - } - - public ActionDomainEvent(final TranslationServicePoMenu source, final Identifier identifier, final List<Object> arguments) { - super(source, identifier, arguments); - } - } - - // ////////////////////////////////////// - - public static class DownloadPotFileDomainEvent extends ActionDomainEvent { - public DownloadPotFileDomainEvent(final TranslationServicePoMenu source, final Identifier identifier, final Object... arguments) { - super(source, identifier, arguments); - } - } - - @Action( - domainEvent = DownloadPotFileDomainEvent.class, - semantics = SemanticsOf.SAFE, - restrictTo = RestrictTo.PROTOTYPING - ) - @ActionLayout( - cssClassFa = "fa-download" - ) - public Clob downloadPotFile( - @ParameterLayout(named = ".pot file name") - final String potFileName) { - final String chars = translationService.toPo(); - return new Clob(potFileName, "text/plain", chars); - } - - public String default0DownloadPotFile() { - return "translations.pot"; - } - public boolean hideDownloadPotFile() { - return translationService.getMode().isRead(); - } - - // ////////////////////////////////////// - - - @Inject - private TranslationServicePo translationService; - -} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/i18n/po/PoReaderTest.java ---------------------------------------------------------------------- diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/i18n/po/PoReaderTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/i18n/po/PoReaderTest.java deleted file mode 100644 index 1889394..0000000 --- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/services/i18n/po/PoReaderTest.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.apache.isis.core.metamodel.services.i18n.po; - -import java.util.List; -import java.util.Locale; -import com.google.common.collect.Lists; -import org.jmock.Expectations; -import org.jmock.auto.Mock; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.apache.isis.applib.services.i18n.LocaleProvider; -import org.apache.isis.core.unittestsupport.jmocking.JUnitRuleMockery2; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -public class PoReaderTest { - - @Rule - public JUnitRuleMockery2 context = JUnitRuleMockery2.createFor(JUnitRuleMockery2.Mode.INTERFACES_AND_CLASSES); - - @Mock - TranslationServicePo mockTranslationServicePo; - - @Mock - LocaleProvider mockLocaleProvider; - - PoReader poReader; - - @Before - public void setUp() throws Exception { - context.checking(new Expectations() {{ - allowing(mockTranslationServicePo).getLocaleProvider(); - will(returnValue(mockLocaleProvider)); - - allowing(mockLocaleProvider).getLocale(); - will(returnValue(Locale.UK)); - }}); - } - - public static class Translate extends PoReaderTest { - - @Test - public void singleContext() throws Exception { - - // given - final String context = - "org.apache.isis.applib.services.bookmark.BookmarkHolderAssociationContributions#object()"; - final String msgId = "Work of art"; - final String msgStr = "Objet d'art"; - - poReader = new PoReader(mockTranslationServicePo) { - @Override - protected List<String> readPo(final Locale locale) { - final List<String> lines = Lists.newArrayList(); - lines.add(String.format("#: %s", context)); - lines.add(String.format("msgid \"%s\"", msgId)); - lines.add(String.format("msgstr \"%s\"", msgStr)); - return lines; - } - }; - - // when - final String translated = poReader.translate(context, msgId); - - // then - assertThat(translated, is(equalTo(msgStr))); - } - - @Test - public void multipleContext() throws Exception { - - // given - final String context1 = - "fixture.simple.SimpleObjectsFixturesService#runFixtureScript(org.apache.isis.applib.fixturescripts.FixtureScript,java.lang.String)"; - final String context2 = - "org.apache.isis.applib.fixturescripts.FixtureScripts#runFixtureScript(org.apache.isis.applib.fixturescripts.FixtureScript,java.lang.String)"; - final String msgId = "Parameters"; - final String msgStr = "Paramètres"; - - poReader = new PoReader(mockTranslationServicePo) { - @Override - protected List<String> readPo(final Locale locale) { - final List<String> lines = Lists.newArrayList(); - lines.add(String.format("#: %s", context1)); - lines.add(String.format("#: %s", context2)); - lines.add(String.format("msgid \"%s\"", msgId)); - lines.add(String.format("msgstr \"%s\"", msgStr)); - return lines; - } - }; - // when - final String translated = poReader.translate(context1, msgId); - - // then - assertThat(translated, is(equalTo(msgStr))); - - // when - final String translated2 = poReader.translate(context2, msgId); - - // then - assertThat(translated2, is(equalTo(msgStr))); - } - - @Test - public void multipleBlocks() throws Exception { - - // given - final String context1 = - "org.apache.isis.applib.services.bookmark.BookmarkHolderAssociationContributions#object()"; - final String msgid1 = "Work of art"; - final String msgstr1 = "Objet d'art"; - - final String context2 = - "org.apache.isis.applib.services.bookmark.BookmarkHolderAssociationContributions#lookup()"; - final String msgid2 = "Lookup"; - final String msgstr2 = "Look up"; - - poReader = new PoReader(mockTranslationServicePo) { - @Override - protected List<String> readPo(final Locale locale) { - final List<String> lines = Lists.newArrayList(); - lines.add(String.format("#: %s", context1)); - lines.add(String.format("msgid \"%s\"", msgid1)); - lines.add(String.format("msgstr \"%s\"", msgstr1)); - - lines.add(String.format("")); - lines.add(String.format("# ")); - - lines.add(String.format("#: %s", context2)); - lines.add(String.format("msgid \"%s\"", msgid2)); - lines.add(String.format("msgstr \"%s\"", msgstr2)); - - lines.add(String.format("")); - return lines; - } - }; - - // when - final String translated1 = poReader.translate(context1, msgid1); - - // then - assertThat(translated1, is(equalTo(msgstr1))); - - // when - final String translated2 = poReader.translate(context2, msgid2); - - // then - assertThat(translated2, is(equalTo(msgstr2))); - } - - @Test - public void withPlurals() throws Exception { - - // given - final String context = - "org.apache.isis.applib.services.bookmark.BookmarkHolderAssociationContributions#object()"; - final String msgid = "Work of art"; - final String msgid_plural = "Works of art"; - final String msgstr$0 = "Åuvre d'art"; - final String msgstr$1 = "Les Åuvres d'art"; - - poReader = new PoReader(mockTranslationServicePo) { - @Override - protected List<String> readPo(final Locale locale) { - final List<String> lines = Lists.newArrayList(); - lines.add(String.format("#: %s", context)); - lines.add(String.format("msgid \"%s\"", msgid)); - lines.add(String.format("msgid_plural \"%s\"", msgid_plural)); - lines.add(String.format("msgstr[0] \"%s\"", msgstr$0)); - lines.add(String.format("msgstr[1] \"%s\"", msgstr$1)); - return lines; - } - }; - - // when - final String translated1 = poReader.translate(context, msgid); - - // then - assertThat(translated1, is(equalTo(msgstr$0))); - - // when - final String translated2 = poReader.translate(context, msgid_plural); - - // then - assertThat(translated2, is(equalTo(msgstr$1))); - } - - - - @Test - public void noTranslation() throws Exception { - - // given - - poReader = new PoReader(mockTranslationServicePo) { - @Override - protected List<String> readPo(final Locale locale) { - return Lists.newArrayList(); - } - }; - - // when - final String translated = poReader.translate("someContext", "Something to translate"); - - // then - assertThat(translated, is(equalTo("Something to translate"))); - } - } - -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/Block.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/Block.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/Block.java new file mode 100644 index 0000000..a39ca1a --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/Block.java @@ -0,0 +1,114 @@ +package org.apache.isis.core.runtime.services.i18n.po; + +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import com.google.common.collect.Lists; + +class Block { + + private enum State { + CONTEXT("^#: (?<value>.+)$"), + MSGID("^msgid \"(?<value>.+)\"$"), + MSGID_PLURAL("^msgid_plural \"(?<value>.+)\"$"), + MSGSTR("^msgstr \"(?<value>.+)\"$"), + MSGSTR0("^msgstr\\[0\\] \"(?<value>.+)\"$"), + MSGSTR1("^msgstr\\[1\\] \"(?<value>.+)\"$"); + + private final Pattern pattern; + + private State(final String regex) { + pattern = Pattern.compile(regex); + } + } + + State state = State.CONTEXT; + + List<String> contextList = Lists.newArrayList(); + String msgid = null; + String msgid_plural = null; + String msgstr = null; // either from msgstr or msgstr[0] if there is a plural + String msgstr_plural = null; // from msgstr[1] + + Block parseLine(final String line, final Map<ContextAndMsgId, String> translationsByKey) { + if (state == State.CONTEXT) { + final Matcher contextMatcher = state.pattern.matcher(line); + if (contextMatcher.matches()) { + final String context = contextMatcher.group("value"); + contextList.add(context); + return this; + } else { + state = State.MSGID; + // fallthrough (there may not have been any more context) + } + } + + if (state == State.MSGID) { + final Matcher msgidMatcher = state.pattern.matcher(line); + if (msgidMatcher.matches()) { + msgid = msgidMatcher.group("value"); + state = State.MSGID_PLURAL; // found, next time look for plurals + } else { + return new Block(); + } + return this; + } + + if (state == State.MSGID_PLURAL) { + final Matcher msgIdPluralMatcher = state.pattern.matcher(line); + if (msgIdPluralMatcher.matches()) { + msgid_plural = msgIdPluralMatcher.group("value"); + state = State.MSGSTR0; // next time look for msgstr[0] + return this; + } else { + state = State.MSGSTR; // fall through (there may not have been any plural form) + } + } + + if (state == State.MSGSTR) { + final Matcher msgStrMatcher = state.pattern.matcher(line); + if (msgStrMatcher.matches()) { + msgstr = msgStrMatcher.group("value"); + } + append(translationsByKey); + return new Block(); + } + + if (state == State.MSGSTR0) { + final Matcher msgStr0Matcher = state.pattern.matcher(line); + if (msgStr0Matcher.matches()) { + msgstr = msgStr0Matcher.group("value"); + state = State.MSGSTR1; // next time, look for plural + } else { + append(translationsByKey); + return new Block(); + } + return this; + } + + if (state == State.MSGSTR1) { + final Matcher msgStr1Matcher = state.pattern.matcher(line); + if (msgStr1Matcher.matches()) { + msgstr_plural = msgStr1Matcher.group("value"); + } + append(translationsByKey); + return new Block(); + } + return this; + } + + void append(final Map<ContextAndMsgId, String> translationsByKey) { + for (String context : contextList) { + if(msgid != null && msgstr != null) { + 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, ContextAndMsgId.Type.PLURAL_ONLY); + translationsByKey.put(mc, msgstr_plural); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/ContextAndMsgId.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/ContextAndMsgId.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/ContextAndMsgId.java new file mode 100644 index 0000000..d00d08a --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/ContextAndMsgId.java @@ -0,0 +1,90 @@ +package org.apache.isis.core.runtime.services.i18n.po; + +/** + * The combination of a <tt>msgId</tt> and context (optionally null) that represents a key to a translatable resource. + * + * <p> + * For example, with this <i>.pot</i> file: + * </p> + * <pre> + * #: org.isisaddons.module.sessionlogger.dom.SessionLoggingServiceMenu#activeSessions() + msgid: "Active Sessions" + + #: org.isisaddons.module.audit.dom.AuditingServiceMenu + #: org.isisaddons.module.command.dom.CommandServiceMenu + #: org.isisaddons.module.publishing.dom.PublishingServiceMenu + msgid: "Activity" + + * </pre> + * + * <p> + * the combination of <code>{org.isisaddons.module.sessionlogger.dom.SessionLoggingServiceMenu#activeSessions(), "Active Sessions"}</code> represents such a key, as does <code>{org.isisaddons.module.audit.dom.AuditingServiceMenu, "Activity"}</code> + * </p> + */ +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, final Type type) { + this.context = context == null? "": context; + this.msgId = msgId; + this.type = type; + } + + public String getMsgId() { + return msgId; + } + + public String getContext() { + 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; + if (o == null || getClass() != o.getClass()) return false; + + final ContextAndMsgId that = (ContextAndMsgId) o; + + if (context != null ? !context.equals(that.context) : that.context != null) return false; + if (msgId != null ? !msgId.equals(that.msgId) : that.msgId != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = context != null ? context.hashCode() : 0; + result = 31 * result + (msgId != null ? msgId.hashCode() : 0); + return result; + } + + @Override + public int compareTo(final ContextAndMsgId o) { + final int i = msgId.compareTo(o.msgId); + if(i != 0) { + return i; + } + return context.compareTo(o.context); + } +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoAbstract.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoAbstract.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoAbstract.java new file mode 100644 index 0000000..15777cc --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoAbstract.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.core.runtime.services.i18n.po; + +import org.apache.isis.applib.services.i18n.TranslationService; + +abstract class PoAbstract { + + protected final TranslationServicePo translationServicePo; + private final TranslationService.Mode mode; + + PoAbstract(final TranslationServicePo translationServicePo, final TranslationService.Mode mode) { + this.translationServicePo = translationServicePo; + this.mode = mode; + } + + + abstract void shutdown(); + + abstract String translate(final String context, final String msgId); + + abstract String translate(final String context, final String msgId, final String msgIdPlural, int num); + + public TranslationService.Mode getMode() { + return mode; + } +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoReader.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoReader.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoReader.java new file mode 100644 index 0000000..03cb9ed --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoReader.java @@ -0,0 +1,190 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.core.runtime.services.i18n.po; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.isis.applib.services.i18n.TranslationService; +import org.apache.isis.applib.services.i18n.TranslationsResolver; + +class PoReader extends PoAbstract { + + public static final String LOCATION_BASE_URL = "isis.services.translation.po.locationBaseUrl"; + public static Logger LOG = LoggerFactory.getLogger(PoReader.class); + + private final Map<Locale, Map<ContextAndMsgId, String>> translationByKeyByLocale = Maps.newHashMap(); + + /** + * The basename of the translations file, hard-coded to <tt>translations</tt>. + * + * <p> + * This means that the reader will search for <tt>translations_en-US.po</tt>, <tt>translations_en.po</tt>, + * <tt>translations.po</tt>, according to the location that the provided {@link org.apache.isis.applib.services.i18n.TranslationsResolver} searches. For example, if using the Wicket implementation, then will search for these files + * under <tt>/WEB-INF</tt> directory. + * </p> + */ + private final String basename = "translations"; + + private List<String> fallback; + + public PoReader(final TranslationServicePo translationServicePo) { + super(translationServicePo, TranslationService.Mode.READ); + } + + //region > init, shutdown + void init(final Map<String,String> config) { + fallback = readUrl(basename + ".po"); + if(fallback == null) { + LOG.warn("No fallback translations found"); + fallback = Collections.emptyList(); + } + } + + @Override + void shutdown() { + } + //endregion + + public String translate(final String context, final String msgId) { + return translate(context, msgId, ContextAndMsgId.Type.REGULAR); + } + + @Override + String translate(final String context, final String msgId, final String msgIdPlural, final int num) { + + 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); + } + + private String translate( + final String context, final String msgId, final ContextAndMsgId.Type type) { + + final Locale targetLocale; + try { + targetLocale = translationServicePo.getLocaleProvider().getLocale(); + } catch(final RuntimeException ex){ + LOG.warn("Failed to obtain locale, returning the original msgId"); + return msgId; + } + + final Map<ContextAndMsgId, String> translationsByKey = readAndCacheTranslationsIfRequired(targetLocale); + + final ContextAndMsgId key = new ContextAndMsgId(context, msgId, type); + final String translation = lookupTranslation(translationsByKey, key); + if (!Strings.isNullOrEmpty(translation)) { + return translation; + } + + final ContextAndMsgId keyNoContext = new ContextAndMsgId("", msgId, type); + final String translationNoContext = lookupTranslation(translationsByKey, keyNoContext); + if (!Strings.isNullOrEmpty(translationNoContext)) { + return translationNoContext; + } + + LOG.warn("No translation found for: " + key); + return msgId; + } + + private String lookupTranslation(final Map<ContextAndMsgId, String> translationsByKey, final ContextAndMsgId key) { + final String s = translationsByKey.get(key); + return s != null? s.trim(): null; + } + + private Map<ContextAndMsgId, String> readAndCacheTranslationsIfRequired(final Locale locale) { + Map<ContextAndMsgId, String> translationsByKey = translationByKeyByLocale.get(locale); + if(translationsByKey != null) { + return translationsByKey; + } + + translationsByKey = Maps.newHashMap(); + read(locale, translationsByKey); + translationByKeyByLocale.put(locale, translationsByKey); + + return translationsByKey; + } + + + /** + * @param locale - the .po file to load + * @param translationsByKey - the translations to be populated + */ + private void read(final Locale locale, final Map<ContextAndMsgId, String> translationsByKey) { + final List<String> contents = readPo(locale); + + Block block = new Block(); + for (final String line : contents) { + block = block.parseLine(line, translationsByKey); + } + } + + protected List<String> readPo(final Locale locale) { + final List<String> lines = readPoElseNull(locale); + if(lines != null) { + return lines; + } + LOG.warn("Could not locate translations for locale: " + locale + ", using fallback"); + return fallback; + } + + private List<String> readPoElseNull(final Locale locale) { + final String country = locale.getCountry().toUpperCase(Locale.ROOT); + final String language = locale.getLanguage().toLowerCase(Locale.ROOT); + + final List<String> candidates = Lists.newArrayList(); + if(!Strings.isNullOrEmpty(language)) { + if(!Strings.isNullOrEmpty(country)) { + candidates.add(basename + "_" + language + "-" + country+ ".po"); + } + candidates.add(basename + "_" + language + ".po"); + } + + for (final String candidate : candidates) { + final List<String> lines = readUrl(candidate); + if(lines != null) { + return lines; + } + } + return null; + } + + private List<String> readUrl(final String candidate) { + final TranslationsResolver translationsResolver = translationServicePo.getTranslationsResolver(); + if(translationsResolver == null) { + return null; + } + return translationsResolver.readLines(candidate); + } + +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoWriter.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoWriter.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoWriter.java new file mode 100644 index 0000000..5b850d6 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/PoWriter.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.core.runtime.services.i18n.po; + +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; +import org.apache.isis.applib.services.i18n.TranslationService; + +class PoWriter extends PoAbstract { + + public static Logger LOG = LoggerFactory.getLogger(PoWriter.class); + + 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, TranslationService.Mode.WRITE); + } + + //region > shutdown + + @Override + void shutdown() { + final StringBuilder buf = new StringBuilder(); + buf.append("\n"); + buf.append("\n##############################################################################"); + buf.append("\n#"); + buf.append("\n# .pot file"); + buf.append("\n#"); + buf.append("\n# generated at: ").append(LocalDateTime.now().toString("yyyy-MM-dd HH:mm:ss")); + buf.append("\n# generated by: ").append(TranslationServicePo.class.getSimpleName()); + buf.append("\n#"); + buf.append("\n# Translate this file to each required language and place in WEB-INF, eg:"); + buf.append("\n#"); + buf.append("\n# /WEB-INF/translations_en-US.po"); + buf.append("\n# /WEB-INF/translations_en.po"); + buf.append("\n# /WEB-INF/translations_fr-FR.po"); + buf.append("\n# /WEB-INF/translations_fr.po"); + buf.append("\n# /WEB-INF/translations.po"); + buf.append("\n#"); + buf.append("\n# If the app uses TranslatableString (eg for internationalized validation"); + buf.append("\n# messages), or if the app calls the TranslationService directly, then ensure"); + buf.append("\n# that all text to be translated has been captured by running a full"); + buf.append("\n# integration test suite that exercises all relevant behaviour"); + buf.append("\n#"); + buf.append("\n##############################################################################"); + buf.append("\n"); + buf.append("\n"); + buf.append(toPot()); + buf.append("\n"); + buf.append("\n"); + buf.append("\n##############################################################################"); + buf.append("\n# end of .pot file"); + buf.append("\n##############################################################################"); + buf.append("\n"); + LOG.info(buf.toString()); + } + //endregion + + + public String translate(final String context, final String msgId) { + + final Block block = blockFor(msgId); + block.contexts.add(context); + + return msgId; + } + + @Override + String translate(final String context, final String msgId, final String msgIdPlural, final int num) { + + final Block block = blockFor(msgId); + block.contexts.add(context); + block.msgIdPlural = msgIdPlural; + + return null; + } + + private Block blockFor(final String msgId) { + Block block = blocksByMsgId.get(msgId); + if(block == null) { + block = new Block(msgId); + blocksByMsgId.put(msgId, block); + } + return block; + } + + /** + * Not API + */ + String toPot() { + final StringBuilder buf = new StringBuilder(); + for (final String msgId : blocksByMsgId.keySet()) { + final Block block = blocksByMsgId.get(msgId); + for (final String context : block.contexts) { + buf.append("#: ").append(context).append("\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(); + } + +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePo.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePo.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePo.java new file mode 100644 index 0000000..7bba5b2 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePo.java @@ -0,0 +1,149 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.core.runtime.services.i18n.po; + +import java.util.Map; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.isis.applib.annotation.DomainService; +import org.apache.isis.applib.annotation.Programmatic; +import org.apache.isis.applib.services.i18n.LocaleProvider; +import org.apache.isis.applib.services.i18n.TranslationService; +import org.apache.isis.applib.services.i18n.TranslationsResolver; +import org.apache.isis.core.runtime.system.DeploymentType; +import org.apache.isis.core.runtime.system.context.IsisContext; + +@DomainService +public class TranslationServicePo implements TranslationService { + + public static Logger LOG = LoggerFactory.getLogger(TranslationServicePo.class); + + public static final String KEY_DEPLOYMENT_TYPE = "isis.deploymentType"; + public static final String KEY_PO_MODE = "isis.services.translation.po.mode"; + + private PoAbstract po; + + /** + * Defaults to writer mode because the service won't have been init'd while the metamodel is bring instantiated, + * and we want to ensure that we capture all requests for translation. + */ + public TranslationServicePo() { + po = new PoWriter(this); + } + + //region > init, shutdown + + @Programmatic + @PostConstruct + public void init(final Map<String,String> config) { + + if(getLocaleProvider() == null || getTranslationsResolver() == null) { + // remain in write mode + return; + } + + final boolean prototypeOrTest = isPrototypeOrTest(); + + final String translationMode = config.get(KEY_PO_MODE); + final boolean forceRead = + translationMode != null && + ("read".equalsIgnoreCase(translationMode) || + "reader".equalsIgnoreCase(translationMode)); + + if(prototypeOrTest && !forceRead) { + // remain in write mode + return; + } + + // switch to read mode + final PoReader poReader = new PoReader(this); + poReader.init(config); + po = poReader; + } + + protected boolean isPrototypeOrTest() { + final DeploymentType deploymentType = getDeploymentType(); + return !deploymentType.isProduction(); + } + + @Programmatic + @PreDestroy + public void shutdown() { + po.shutdown(); + } + //endregion + + + @Override + @Programmatic + public String translate(final String context, final String text) { + return po.translate(context, text); + } + + @Override + public String translate(final String context, final String singularText, final String pluralText, final int num) { + return po.translate(context, singularText, pluralText, num); + } + + @Override + public Mode getMode() { + return po.getMode(); + } + + /** + * Not API + */ + @Programmatic + public String toPo() { + if (!getMode().isWrite()) { + throw new IllegalStateException("Not in write mode"); + } + return ((PoWriter)po).toPot(); + } + + // ////////////////////////////////////// + + DeploymentType getDeploymentType() { + return IsisContext.getDeploymentType(); + } + + + @Inject + private + TranslationsResolver translationsResolver; + + @Programmatic + TranslationsResolver getTranslationsResolver() { + return translationsResolver; + } + + @Inject + private + LocaleProvider localeProvider; + + @Programmatic + LocaleProvider getLocaleProvider() { + return localeProvider; + } + + +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePoMenu.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePoMenu.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePoMenu.java new file mode 100644 index 0000000..e349668 --- /dev/null +++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/i18n/po/TranslationServicePoMenu.java @@ -0,0 +1,94 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.isis.core.runtime.services.i18n.po; + +import java.util.List; +import javax.inject.Inject; +import org.apache.isis.applib.Identifier; +import org.apache.isis.applib.IsisApplibModule; +import org.apache.isis.applib.annotation.Action; +import org.apache.isis.applib.annotation.ActionLayout; +import org.apache.isis.applib.annotation.DomainService; +import org.apache.isis.applib.annotation.DomainServiceLayout; +import org.apache.isis.applib.annotation.NatureOfService; +import org.apache.isis.applib.annotation.ParameterLayout; +import org.apache.isis.applib.annotation.RestrictTo; +import org.apache.isis.applib.annotation.SemanticsOf; +import org.apache.isis.applib.value.Clob; + +@DomainService( + nature = NatureOfService.VIEW_MENU_ONLY +) +@DomainServiceLayout( + menuBar = DomainServiceLayout.MenuBar.SECONDARY, + named = "Prototyping" +) +public class TranslationServicePoMenu { + + public static abstract class ActionDomainEvent extends IsisApplibModule.ActionDomainEvent<TranslationServicePoMenu> { + public ActionDomainEvent(final TranslationServicePoMenu source, final Identifier identifier) { + super(source, identifier); + } + + public ActionDomainEvent(final TranslationServicePoMenu source, final Identifier identifier, final Object... arguments) { + super(source, identifier, arguments); + } + + public ActionDomainEvent(final TranslationServicePoMenu source, final Identifier identifier, final List<Object> arguments) { + super(source, identifier, arguments); + } + } + + // ////////////////////////////////////// + + public static class DownloadPotFileDomainEvent extends ActionDomainEvent { + public DownloadPotFileDomainEvent(final TranslationServicePoMenu source, final Identifier identifier, final Object... arguments) { + super(source, identifier, arguments); + } + } + + @Action( + domainEvent = DownloadPotFileDomainEvent.class, + semantics = SemanticsOf.SAFE, + restrictTo = RestrictTo.PROTOTYPING + ) + @ActionLayout( + cssClassFa = "fa-download" + ) + public Clob downloadPotFile( + @ParameterLayout(named = ".pot file name") + final String potFileName) { + final String chars = translationService.toPo(); + return new Clob(potFileName, "text/plain", chars); + } + + public String default0DownloadPotFile() { + return "translations.pot"; + } + public boolean hideDownloadPotFile() { + return translationService.getMode().isRead(); + } + + // ////////////////////////////////////// + + + @Inject + private TranslationServicePo translationService; + +} http://git-wip-us.apache.org/repos/asf/isis/blob/362a5bfc/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisWebAppBootstrapper.java ---------------------------------------------------------------------- diff --git a/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisWebAppBootstrapper.java b/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisWebAppBootstrapper.java index 7fae462..f16068b 100644 --- a/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisWebAppBootstrapper.java +++ b/core/runtime/src/main/java/org/apache/isis/core/webapp/IsisWebAppBootstrapper.java @@ -87,10 +87,10 @@ public class IsisWebAppBootstrapper implements ServletContextListener { new ResourceStreamSourceForWebInf(servletContext)) ; if ( configLocation != null ) { - LOG.info( "Config override location: " + configLocation ); - compositeSource.addResourceStreamSource(ResourceStreamSourceFileSystem.create(configLocation)); + LOG.info( "Config override location: " + configLocation ); + compositeSource.addResourceStreamSource(ResourceStreamSourceFileSystem.create(configLocation)); } else { - LOG.info( "Config override location: No override location configured" ); + LOG.info( "Config override location: No override location configured" ); } // will load either from WEB-INF, from the classpath or from config directory.
