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 net.sf.saxon.value.StringValue;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.libreccm.l10n.GlobalizationHelper;
|
||||||
import org.libreccm.theming.ProcessesThemes;
|
import org.libreccm.theming.ProcessesThemes;
|
||||||
import org.libreccm.theming.Themes;
|
import org.libreccm.theming.Themes;
|
||||||
import org.libreccm.theming.manifest.ThemeTemplate;
|
import org.libreccm.theming.manifest.ThemeTemplate;
|
||||||
|
|
@ -67,7 +68,13 @@ import java.io.Reader;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import java.nio.charset.StandardCharsets;
|
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.Optional;
|
||||||
|
import java.util.PropertyResourceBundle;
|
||||||
|
import java.util.ResourceBundle;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.xml.transform.ErrorListener;
|
import javax.xml.transform.ErrorListener;
|
||||||
|
|
@ -97,6 +104,9 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
||||||
private static final Logger LOGGER = LogManager
|
private static final Logger LOGGER = LogManager
|
||||||
.getLogger(XsltThemeProcessor.class);
|
.getLogger(XsltThemeProcessor.class);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private GlobalizationHelper globalizationHelper;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
private SettingsUtils settingsUtils;
|
private SettingsUtils settingsUtils;
|
||||||
|
|
||||||
|
|
@ -137,7 +147,7 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
||||||
} catch (ParserConfigurationException ex) {
|
} catch (ParserConfigurationException ex) {
|
||||||
throw new UnexpectedErrorException(ex);
|
throw new UnexpectedErrorException(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
final Document document;
|
final Document document;
|
||||||
try {
|
try {
|
||||||
final InputStream xmlBytesStream = new ByteArrayInputStream(
|
final InputStream xmlBytesStream = new ByteArrayInputStream(
|
||||||
|
|
@ -213,6 +223,9 @@ public class XsltThemeProcessor implements ThemeProcessor {
|
||||||
configuration
|
configuration
|
||||||
.registerExtensionFunction(
|
.registerExtensionFunction(
|
||||||
new GetSettingFunctionDefinition(theme, themeProvider));
|
new GetSettingFunctionDefinition(theme, themeProvider));
|
||||||
|
configuration
|
||||||
|
.registerExtensionFunction(
|
||||||
|
new LocalizeFunctionDefinition(theme, themeProvider));
|
||||||
configuration
|
configuration
|
||||||
.registerExtensionFunction(new TruncateTextFunctionDefinition());
|
.registerExtensionFunction(new TruncateTextFunctionDefinition());
|
||||||
final Transformer transformer;
|
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
|
private class TruncateTextFunctionDefinition
|
||||||
extends ExtensionFunctionDefinition {
|
extends ExtensionFunctionDefinition {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,8 @@
|
||||||
</xsl:for-each>
|
</xsl:for-each>
|
||||||
</div>
|
</div>
|
||||||
<!--<xsl:apply-templates select="greetingItem" />-->
|
<!--<xsl:apply-templates select="greetingItem" />-->
|
||||||
|
|
||||||
|
<xsl:call-template name="themeFunctionsExamples" />
|
||||||
</main>
|
</main>
|
||||||
<xsl:call-template name="footer" />
|
<xsl:call-template name="footer" />
|
||||||
<!--<footer>
|
<!--<footer>
|
||||||
|
|
@ -81,6 +83,9 @@
|
||||||
</p>
|
</p>
|
||||||
<xsl:value-of disable-output-escaping="true" select="./text" />
|
<xsl:value-of disable-output-escaping="true" select="./text" />
|
||||||
|
|
||||||
|
</xsl:template>
|
||||||
|
|
||||||
|
<xsl:template name="themeFunctionsExamples">
|
||||||
<h2>Example of Theme Utils</h2>
|
<h2>Example of Theme Utils</h2>
|
||||||
<dl>
|
<dl>
|
||||||
<dt>
|
<dt>
|
||||||
|
|
@ -107,8 +112,31 @@
|
||||||
<xsl:value-of select="ccm:truncateText('0123456789 123456789 123456789', 20)" />
|
<xsl:value-of select="ccm:truncateText('0123456789 123456789 123456789', 20)" />
|
||||||
</code>
|
</code>
|
||||||
</dd>
|
</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>
|
</dl>
|
||||||
|
|
||||||
</xsl:template>
|
</xsl:template>
|
||||||
|
|
||||||
</xsl:stylesheet>
|
</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