CCM NG: Lokalisierung Themes über ResourceBundle (#2795)
git-svn-id: https://svn.libreccm.org/ccm/ccm_ng@5262 8810af33-2d31-482b-a856-94f89814c4df
Former-commit-id: ee857ec4c8
pull/2/head
parent
db40133722
commit
e15e952b21
|
|
@ -53,6 +53,7 @@ import net.sf.saxon.value.SequenceType;
|
|||
import net.sf.saxon.value.StringValue;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.libreccm.l10n.GlobalizationHelper;
|
||||
import org.libreccm.theming.ProcessesThemes;
|
||||
import org.libreccm.theming.Themes;
|
||||
import org.libreccm.theming.manifest.ThemeTemplate;
|
||||
|
|
@ -67,7 +68,13 @@ import java.io.Reader;
|
|||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.PropertyResourceBundle;
|
||||
import java.util.ResourceBundle;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.xml.transform.ErrorListener;
|
||||
|
|
@ -97,6 +104,9 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
|||
private static final Logger LOGGER = LogManager
|
||||
.getLogger(XsltThemeProcessor.class);
|
||||
|
||||
@Inject
|
||||
private GlobalizationHelper globalizationHelper;
|
||||
|
||||
@Inject
|
||||
private SettingsUtils settingsUtils;
|
||||
|
||||
|
|
@ -213,6 +223,9 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
|||
configuration
|
||||
.registerExtensionFunction(
|
||||
new GetSettingFunctionDefinition(theme, themeProvider));
|
||||
configuration
|
||||
.registerExtensionFunction(
|
||||
new LocalizeFunctionDefinition(theme, themeProvider));
|
||||
configuration
|
||||
.registerExtensionFunction(new TruncateTextFunctionDefinition());
|
||||
final Transformer transformer;
|
||||
|
|
@ -389,6 +402,173 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition for XSL function for localising texts in the theme. Allows use
|
||||
* of {@link ResourceBundle}s instead of a custom XML format which was used
|
||||
* in legacy versions.
|
||||
*
|
||||
* The XSL function {@code ccm:localize} expects one mandatory parameter,
|
||||
* the {@code key} of the text to localise. The optional second parameter
|
||||
* {@code bundle} identifies the {@link ResourceBundle} to use. The
|
||||
* {@code bundle} parameter is passed to
|
||||
* {@link ResourceBundle#getBundle(java.lang.String)}. All bundle paths are
|
||||
* resolved relative to the root of the theme using {@link ThemeProvider}.
|
||||
* If the {@code bundle} parameter is omitted the function will look for
|
||||
* {@link PropertyResourceBundle} named {@code theme-bundle.properties} in
|
||||
* the root of the theme. Examples:
|
||||
*
|
||||
* {@code <xsl:value-of select="ccm:localize('footer.privacy')" />}
|
||||
*
|
||||
* In this case this function will load the file
|
||||
* {@code theme-bundle.properties} from the root of the theme and use it to
|
||||
* create an instance of {@link PropertyResourceBundle}. If this is
|
||||
* successful the key {@code footer.privacy} is lookup in the resource
|
||||
* bundle.
|
||||
*
|
||||
* {@code <xsl:value-of select="ccm:localize('footer.privacy', '/texts/footer')" />}
|
||||
*
|
||||
* In this case the function tries find a property file called
|
||||
* {@code footer.properties} in the texts directory in the theme.
|
||||
*
|
||||
* Of course the function, or better {@link ResourceBundle} will also take
|
||||
* into account the current locale, therefore in both examples the first
|
||||
* file name will be {@code footer_$locale.properties} where {@code $locale}
|
||||
* is the the locale returned by
|
||||
* {@link GlobalizationHelper#getNegotiatedLocale()}.
|
||||
*/
|
||||
private class LocalizeFunctionDefinition
|
||||
extends ExtensionFunctionDefinition {
|
||||
|
||||
private final ThemeInfo theme;
|
||||
private final ThemeProvider themeProvider;
|
||||
|
||||
public LocalizeFunctionDefinition(final ThemeInfo theme,
|
||||
final ThemeProvider themeProvider) {
|
||||
super();
|
||||
this.theme = theme;
|
||||
this.themeProvider = themeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StructuredQName getFunctionQName() {
|
||||
return new StructuredQName(FUNCTION_XMLNS_PREFIX,
|
||||
FUNCTION_XMLNS,
|
||||
"localize");
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType[] getArgumentTypes() {
|
||||
return new SequenceType[]{SequenceType.SINGLE_STRING};
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaximumNumberOfArguments() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SequenceType getResultType(final SequenceType[] arguments) {
|
||||
return SequenceType.SINGLE_STRING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExtensionFunctionCall makeCallExpression() {
|
||||
return new ExtensionFunctionCall() {
|
||||
|
||||
@Override
|
||||
public Sequence call(final XPathContext xPathContext,
|
||||
final Sequence[] arguments)
|
||||
throws XPathException {
|
||||
|
||||
final String bundle;
|
||||
if (arguments.length > 1) {
|
||||
bundle = ((Item) arguments[1]).getStringValue();
|
||||
} else {
|
||||
bundle = "theme-bundle";
|
||||
}
|
||||
final String key = ((Item) arguments[0]).getStringValue();
|
||||
|
||||
LOGGER.debug("Localizing key \"{}\" from bundle \"{}\"...",
|
||||
key,
|
||||
bundle);
|
||||
|
||||
final ResourceBundle resourceBundle = ResourceBundle
|
||||
.getBundle(
|
||||
bundle,
|
||||
globalizationHelper.getNegotiatedLocale(),
|
||||
new LocalizedResourceBundleControl(theme,
|
||||
themeProvider));
|
||||
|
||||
return StringValue
|
||||
.makeStringValue(resourceBundle.getString(key));
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class LocalizedResourceBundleControl
|
||||
extends ResourceBundle.Control {
|
||||
|
||||
private final ThemeInfo theme;
|
||||
private final ThemeProvider themeProvider;
|
||||
|
||||
public LocalizedResourceBundleControl(
|
||||
final ThemeInfo theme,
|
||||
final ThemeProvider themeProvider) {
|
||||
|
||||
this.theme = theme;
|
||||
this.themeProvider = themeProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getFormats(final String baseName) {
|
||||
Objects.requireNonNull(baseName);
|
||||
|
||||
return Arrays.asList("java.properties");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ResourceBundle newBundle(final String baseName,
|
||||
final Locale locale,
|
||||
final String format,
|
||||
final ClassLoader classLoader,
|
||||
final boolean reload)
|
||||
throws IllegalAccessException,
|
||||
InstantiationException,
|
||||
IOException {
|
||||
|
||||
if ("java.properties".equals(format)) {
|
||||
|
||||
final String bundleName = toBundleName(baseName, locale);
|
||||
|
||||
final Optional<InputStream> inputStream = themeProvider
|
||||
.getThemeFileAsStream(theme.getName(),
|
||||
theme.getVersion(),
|
||||
String.format("%s.properties",
|
||||
bundleName));
|
||||
if (inputStream.isPresent()) {
|
||||
return new PropertyResourceBundle(inputStream.get());
|
||||
} else {
|
||||
return super.newBundle(baseName,
|
||||
locale,
|
||||
format,
|
||||
classLoader,
|
||||
reload);
|
||||
}
|
||||
|
||||
} else {
|
||||
return super.newBundle(baseName,
|
||||
locale,
|
||||
format,
|
||||
classLoader,
|
||||
reload);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TruncateTextFunctionDefinition
|
||||
extends ExtensionFunctionDefinition {
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@
|
|||
</xsl:for-each>
|
||||
</div>
|
||||
<!--<xsl:apply-templates select="greetingItem" />-->
|
||||
|
||||
<xsl:call-template name="themeFunctionsExamples" />
|
||||
</main>
|
||||
<xsl:call-template name="footer" />
|
||||
<!--<footer>
|
||||
|
|
@ -81,6 +83,9 @@
|
|||
</p>
|
||||
<xsl:value-of disable-output-escaping="true" select="./text" />
|
||||
|
||||
</xsl:template>
|
||||
|
||||
<xsl:template name="themeFunctionsExamples">
|
||||
<h2>Example of Theme Utils</h2>
|
||||
<dl>
|
||||
<dt>
|
||||
|
|
@ -107,8 +112,31 @@
|
|||
<xsl:value-of select="ccm:truncateText('0123456789 123456789 123456789', 20)" />
|
||||
</code>
|
||||
</dd>
|
||||
<dt>
|
||||
<code>localized('label.critical')</code>
|
||||
</dt>
|
||||
<dd>
|
||||
<xsl:value-of select="ccm:localize('label.critical', 'texts/labels')" />
|
||||
</dd>
|
||||
<dt>
|
||||
<code>localized('label.error')</code>
|
||||
</dt>
|
||||
<dd>
|
||||
<xsl:value-of select="ccm:localize('label.error', 'texts/labels')" />
|
||||
</dd>
|
||||
<dt>
|
||||
<code>localized('label.ok')</code>
|
||||
</dt>
|
||||
<dd>
|
||||
<xsl:value-of select="ccm:localize('label.ok', 'texts/labels')" />
|
||||
</dd>
|
||||
<dt>
|
||||
<code>localized('label.warning')</code>
|
||||
</dt>
|
||||
<dd>
|
||||
<xsl:value-of select="ccm:localize('label.warning', 'texts/labels')" />
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0"?>
|
||||
<xsl:stylesheet version="2.0"
|
||||
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
|
||||
xmlns:ccm="http://xmlns.libreccm.org"
|
||||
exclude-result-prefixes="ccm xsl">
|
||||
|
||||
<xsl:template name="footer">
|
||||
<footer>
|
||||
<ul>
|
||||
<li>
|
||||
<a href="/impressum">
|
||||
<!--Impressum-->
|
||||
<xsl:value-of select="ccm:localize('footer.impressum')" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/privacy">
|
||||
<!--Privacy-->
|
||||
<xsl:value-of select="ccm:localize('footer.privacy')" />
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<code>imported</code>
|
||||
</li>
|
||||
</ul>
|
||||
</footer>
|
||||
</xsl:template>
|
||||
|
||||
</xsl:stylesheet>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
label.critical=Critical
|
||||
label.error=Error
|
||||
label.ok=OK
|
||||
label.warning=Warning
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
footer.impressum=Impressum
|
||||
footer.privacy=Privacy
|
||||
Loading…
Reference in New Issue