[Yanel-commits] rev 32613 - in public/yanel/trunk/src: core/java/org/wyona/yanel/core core/java/org/wyona/yanel/core/i18n core/java/org/wyona/yanel/core/map core/java/org/wyona/yanel/core/transformation impl/java/org/wyona/yanel/impl/resources

simon at wyona.com simon at wyona.com
Thu Feb 28 09:52:29 CET 2008


Author: simon
Date: 2008-02-28 09:52:28 +0100 (Thu, 28 Feb 2008)
New Revision: 32613

Added:
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/I18nUtils.java
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageManager.java
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageProvider.java
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/ResourceBundleMessageProvider.java
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/XMLMessageProvider.java
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/transformation/I18nTransformer3.java
Modified:
   public/yanel/trunk/src/core/java/org/wyona/yanel/core/map/Realm.java
   public/yanel/trunk/src/impl/java/org/wyona/yanel/impl/resources/BasicXMLResource.java
Log:
see: http://bugzilla.wyona.com/cgi-bin/bugzilla/show_bug.cgi?id=6035
- support xml files (to avoid encoding issues with plain text property files)
- should not be necessary to copy i18n files to WEB-INF/classes
- allow a default i18n catalogue per realm

Added: public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/I18nUtils.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/I18nUtils.java	                        (rev 0)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/I18nUtils.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -0,0 +1,39 @@
+/*
+ * 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.wyona.yanel.core.i18n;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+/**
+ * This class holds utility methods useful when working with i18n.
+ * @author Mattias Jiderhamn
+ */
+public class I18nUtils {
+
+    private I18nUtils() {
+    }
+
+    public static Locale getParentLocale (Locale locale) {
+        if(locale.getVariant().length() != 0)
+          return new Locale(locale.getLanguage(), locale.getCountry());
+        else if(locale.getCountry().length() != 0)
+            return new Locale(locale.getLanguage());
+        else // Locale with only language have no parent
+            return null;
+    }
+}

Added: public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageManager.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageManager.java	                        (rev 0)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageManager.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -0,0 +1,177 @@
+/*
+ * 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.wyona.yanel.core.i18n;
+
+import java.text.MessageFormat;
+import java.util.*;
+
+/**
+ * The <code>MessageManager</code> provides methods for retrieving localized
+ * messages and adding custom message providers. 
+ * This class should not be called directly for other purposes than registering a custom
+ * {@link MessageProvider} or retrieving information about available
+ * message entries.
+ * <p>
+ * To access localized messages a subclass of the {@link LocalizedBundle} class
+ * such as <code>LocalizedText </code> should be used:
+ * 
+ * <pre>
+ * LocalizedText welcome = new LocalizedText(&quot;welcome&quot;); 
+ * // Get the german translacion of the retrieved welcome text 
+ * System.out.println(welcome.getText(Locale.GERMAN));       
+ * </pre>
+ * 
+ * <p>
+ * You can call {@link MessageManager#getText(String,String,Object[],Locale) getText} directly,
+ * but if you do so, you have to ensure that the given entry key really
+ * exists and to deal with the {@link MessageNotFoundException} exception that will
+ * be thrown if you try to access a not existing entry.</p>
+ * 
+ */
+public class MessageManager {
+
+    private Map messageProviders = new LinkedHashMap();
+
+    private Locale defaultLocale;
+    
+    public MessageManager(Locale defaultLocale) {
+        this.defaultLocale = defaultLocale;
+    }
+    
+    /**
+     * Add a custom <code>{@link MessageProvider}</code> to the
+     * <code>MessageManager</code>. It will be incorporated in later calls of
+     * the {@link MessageManager#getText(String,String,Object[],Locale) getText}
+     * or {@link #getEntries(String,Locale) getEntries}methods.
+     *
+     * @param providerId Id of the provider used for uninstallation and
+     *          qualified naming.
+     * @param messageProvider
+     *            The <code>MessageProvider</code> to be added.
+     */
+    public void addMessageProvider(String providerId, MessageProvider messageProvider) {
+        messageProviders.put(providerId, messageProvider);
+    }
+
+    /**
+     * Remove all <code>{@link MessageProvider}</code>s from the
+     * <code>MessageManager</code>. Used for tearing down unit tests.
+     */
+    public void clearMessageProviders() {
+        messageProviders.clear();
+    }
+
+    /**
+     * Remove custom <code>{@link MessageProvider}</code> from the
+     * <code>MessageManager</code>. Used for tearing down unit tests.
+     *
+     * @param providerId The ID of the provider to remove.
+     */
+    public void removeMessageProvider(String providerId) {
+        messageProviders.remove(providerId);
+    }
+
+    /**
+     * Iterates over all registered message providers in order to find the given
+     * entry in the requested message bundle.
+     * 
+     * @param key
+     *            The identifier that will be used to retrieve the message
+     *            bundle
+     * @param arguments
+     *            The dynamic parts of the message that will be evaluated using
+     *            the standard java text formatting abilities.
+     * @param locale
+     *            The locale in which the message will be printed
+     * @return The localized message or null if no message is found 
+     */
+    public String getText(String key, Object[] arguments, Locale locale) {
+        for (Iterator i = messageProviders.values().iterator(); i.hasNext();) {
+            String text = ((MessageProvider) i.next()).getText(key, locale, this.defaultLocale);
+            if(text != null) {
+                return (arguments != null && arguments.length > 0) ?
+                        MessageFormat.format(text, arguments) : text;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Iterates over all registered message providers in order to find the given
+     * entry in the requested message bundle.
+     * 
+     * @param key
+     *            The identifier that will be used to retrieve the message
+     *            bundle
+     * @param locale
+     *            The locale in which the message will be printed
+     * @return The localized message or null if no message is found 
+     */
+    public String getText(String key, Locale locale) {
+        Object[] arguments = new Object[0];
+        return getText(key, arguments, locale);
+    }
+
+    /**
+     * Iterates over all registered message providers in order to find the given
+     * entry in the requested message bundle.
+     * 
+     * @param key
+     *            The identifier that will be used to retrieve the message
+     *            bundle
+     * @param arguments
+     *            The dynamic parts of the message that will be evaluated using
+     *            the standard java text formatting abilities.
+     * @param locale
+     *            The locale in which the message will be printed
+     * @param defaultMessage
+     *            If no message bundle or message entry could be found for the
+     *            specified parameters, the default text will be returned.
+     * @return The localized text or the default text if the message could not
+     *         be found
+     */
+    public String getText(String key, Object[] arguments, Locale locale, String defaultText) {
+        String text = getText(key, arguments, locale);
+        if (text != null) {
+            return text;
+        } else {
+            return defaultText;
+        }
+    }
+
+    /**
+     * Iterates over all registered message providers in order to find the given
+     * entry in the requested message bundle.
+     * 
+     * @param key
+     *            The identifier that will be used to retrieve the message
+     *            bundle
+     * @param locale
+     *            The locale in which the message will be printed
+     * @param defaultMessage
+     *            If no message bundle or message entry could be found for the
+     *            specified parameters, the default text will be returned.
+     * @return The localized text or the default text if the message could not
+     *         be found
+     */
+    public String getText(String key, Locale locale, String defaultText) {
+        Object[] arguments = new Object[0];
+        return getText(key, arguments, locale, defaultText);
+    }
+
+}
\ No newline at end of file

Added: public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageProvider.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageProvider.java	                        (rev 0)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/MessageProvider.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -0,0 +1,40 @@
+/*
+ * 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.wyona.yanel.core.i18n;
+
+import java.util.Locale;
+
+/**
+ * The <code>MessageProvider</code> interface specifies the methods that
+ * must be implemented by each message provider in order to be pluggable 
+ * into the <code>MessageManager</code>.
+ *
+ */
+public interface MessageProvider {
+    /**
+     * Gets the text for a given key
+     * @param key unique id that specifies a particular message  
+     * @param locale the locale for which this message should be provided
+     * @param defaultLocale the default locale if the message is not found for the given locale
+     * @return returns the localized message entry matching the given message key and locale. If
+     * no match is found for the given locale, the parent locale is used, and finally the default.
+     * Returns null if no message is found. 
+     */
+    public String getText(String key, Locale locale, Locale defaultLocale);
+    
+}
\ No newline at end of file

Added: public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/ResourceBundleMessageProvider.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/ResourceBundleMessageProvider.java	                        (rev 0)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/ResourceBundleMessageProvider.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -0,0 +1,55 @@
+/*
+* 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.wyona.yanel.core.i18n;
+
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+import org.apache.log4j.Logger;
+
+/**
+ * The <code>ResourceBundleMessageProvider</code> deals with messages defined in 
+ * resource bundles. Messages defined in resource bundles can be grouped together
+ * by adding the entry key at the end of the message key separated by a dot.
+ */
+public class ResourceBundleMessageProvider implements MessageProvider {
+    private static Logger log = Logger.getLogger(ResourceBundleMessageProvider.class.getName());
+
+    private final String baseName;
+
+    /**
+     *
+     */
+    public ResourceBundleMessageProvider(String baseName) {
+        this.baseName = baseName;
+    }
+
+    public String getText(String key, Locale locale, Locale defaultLocale) {
+        ResourceBundle resourceBundle;
+        String text = null;
+        try {
+            resourceBundle = ResourceBundle.getBundle(baseName, locale);
+            text = resourceBundle.getObject(key).toString();
+        } catch (MissingResourceException e) {
+            resourceBundle = ResourceBundle.getBundle(baseName, defaultLocale);
+            text = resourceBundle.getObject(key).toString();
+        }
+        return text;
+    }
+}
\ No newline at end of file

Added: public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/XMLMessageProvider.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/XMLMessageProvider.java	                        (rev 0)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/i18n/XMLMessageProvider.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -0,0 +1,164 @@
+/*
+* 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.wyona.yanel.core.i18n;
+
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.log4j.Logger;
+
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * The <code>XMLMessageProvider</code> provides messages defined in an XML format.
+ *  
+ */
+public class XMLMessageProvider implements MessageProvider {
+    private static final Logger log = Logger.getLogger(XMLMessageProvider.class.getName());
+
+    private static SAXParserFactory factory = SAXParserFactory.newInstance();
+    
+    private Map messages = new HashMap();
+
+    public XMLMessageProvider(InputStream inputStream) {
+        try {
+            SAXParser parser = factory.newSAXParser();
+            ConfigurationHandler handler = new ConfigurationHandler();
+            parser.parse(new InputSource(inputStream), handler);
+            messages = handler.getMessages();
+        } catch (Exception e) {
+            log.error(e.getMessage(), e);
+            throw new RuntimeException(e.getMessage(), e);
+        }
+    }
+
+    public String getText(String key, Locale locale, Locale defaultLocale) {
+        String text = lookupText(key, locale);
+        if (text == null) {
+            text = lookupText(key, defaultLocale);
+        }
+        return text;
+    }
+
+    private String lookupText(String key, Locale locale) {
+        if (messages.containsKey(key)) {
+            Message message = (Message)messages.get(key);
+            while (locale != null) {
+                String localeStr = locale.toString();
+                String text = message.getText(localeStr);
+                if (text != null) {
+                    return text;
+                }
+                locale = I18nUtils.getParentLocale(locale);
+            }
+        }
+        return null;
+    }
+
+    class ConfigurationHandler extends DefaultHandler {
+        private String key;
+        private String language, country, variant;
+        private Message message;
+        private HashMap parsedMessages;
+        private StringBuffer cData;
+        private boolean insideText = false;;
+        
+        public ConfigurationHandler() {
+            parsedMessages = new HashMap();
+        }
+
+        public void startElement(String namespaceUri, String localeName, String qName, Attributes attributes)  {
+            if (qName.matches("message")) {
+                key = attributes.getValue("key");
+                message = new Message(key);
+            } else if (qName.matches("text")) {
+                language = attributes.getValue("language");
+                country = attributes.getValue("country");
+                variant = attributes.getValue("variant");
+                cData = new StringBuffer();
+                insideText = true;
+            }
+        }
+        public void characters(char[] ch, int start, int length) {
+            if (insideText && length > 0 ) {
+                cData.append(ch, start, length);
+            }
+        }
+        
+        public void endElement(String namespaceUri, String localeName, String qName) {
+            if (qName.matches("message")) {
+                parsedMessages.put(message.getKey(), message);
+            } else if (qName.matches("text")) {
+                String localeString = (language == null ? "" : language) 
+                              + (country == null ? "" : "_" + country)
+                              + (country == null && variant != null ? "_" : "")
+                              + (variant == null ? "" : "_" + variant);
+                message.setText(localeString, cData.toString());
+                insideText = false;
+            }
+        }
+        
+        Map getMessages() {
+            return parsedMessages;
+        }
+    }
+
+    static class Message {
+        private final String key;
+        private HashMap values;
+
+        public Message(String key) {
+            this.key = key;
+            this.values = new HashMap();
+        }
+
+        public String getText(String locale) {
+            return (String)values.get(locale);
+        }
+
+        public void setText(String locale, String value) {
+            values.put(locale, value);
+        }
+
+        public String getKey() {
+            return key;
+        }
+        
+        public String toString() {
+            StringBuffer sb = new StringBuffer(key + "(");
+            Iterator iter = values.keySet().iterator();
+            while (iter.hasNext()) {
+                String locale = (String)iter.next();
+                String value = (String)values.get(locale);
+                sb.append(locale + ":" + value);
+                if (iter.hasNext()) {
+                    sb.append(",");
+                }
+            }
+            sb.append(")");
+            return sb.toString();
+        }
+    }
+}
\ No newline at end of file

Modified: public/yanel/trunk/src/core/java/org/wyona/yanel/core/map/Realm.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/map/Realm.java	2008-02-28 08:30:22 UTC (rev 32612)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/map/Realm.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -64,6 +64,7 @@
     private File configFile;
     private File rootDir;
     private String[] languages;
+    private String i18nCatalogue;
 
     private boolean proxySet = false;
     private String proxyHostName;
@@ -247,6 +248,11 @@
                 Repository repo = extraRepoFactory.newRepository(id, repoConfig);
             }
         }
+        
+        configElement = config.getChild("i18n-catalogue", false);
+        if (configElement != null) {
+            this.i18nCatalogue = configElement.getValue();
+        }
     }
 
     /**
@@ -496,4 +502,15 @@
         String defaultWebAuthenticatorImplClassName = "org.wyona.yanel.servlet.security.impl.DefaultWebAuthenticatorImpl";
         return (WebAuthenticator) Class.forName(defaultWebAuthenticatorImplClassName).newInstance();
     }
+    
+    /**
+     * Gets the value of the i18n-catalogue config element.
+     * This value normally is a URI pointing to an i18n message catalogue. 
+     * @return i18n catalogue
+     */
+    public String getI18nCatalogue() {
+        return this.i18nCatalogue;
+    }
+
+
 }

Added: public/yanel/trunk/src/core/java/org/wyona/yanel/core/transformation/I18nTransformer3.java
===================================================================
--- public/yanel/trunk/src/core/java/org/wyona/yanel/core/transformation/I18nTransformer3.java	                        (rev 0)
+++ public/yanel/trunk/src/core/java/org/wyona/yanel/core/transformation/I18nTransformer3.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -0,0 +1,248 @@
+package org.wyona.yanel.core.transformation;
+
+import java.io.InputStream;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.URIResolver;
+import javax.xml.transform.sax.SAXSource;
+
+import org.apache.log4j.Category;
+import org.wyona.yanel.core.i18n.MessageManager;
+import org.wyona.yanel.core.i18n.MessageProvider;
+import org.wyona.yanel.core.i18n.ResourceBundleMessageProvider;
+import org.wyona.yanel.core.i18n.XMLMessageProvider;
+import org.wyona.yanel.core.source.SourceException;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+
+/**
+ * Transformer to translate content to a certain language using a message catalogue. 
+ * This i18n transformer supports two kinds of syntax:
+ * <ol>
+ * <li>old (deprecated):
+ * <pre>
+ *    &lt;i18n:message key="pageInfo"&gt;Page Info Default Text&lt;/i18n:message&gt;
+ *    &lt;i18n:message&gt;pageInfo&lt;/i18n:message&gt;
+ *    &lt;input type="submit" value="i18n:attr key=mySubmit"/&gt;
+ * </pre>
+ * </li>
+ * <li>new (cocoon-like):
+ * <pre>
+ *    &lt;i18n:text xmlns:i18n="http://www.wyona.org/yanel/i18n/1.0" key="pageInfo"&gt;Page Info Default Text&lt;/i18n:text&gt;
+ *    &lt;i18n:text&gt;pageInfo&lt;/i18n:text&gt;
+ *    &lt;input type="submit" value="mySubmit" i18n:attr="value"/&gt;
+ * </pre>
+ * </li>
+ * </ol>
+ * The namespace uri is http://www.wyona.org/yanel/i18n/1.0
+ * <br/>
+ * Version 3 of this transformer is able to use XML message catalogues and also 
+ * ResourceBundle properties. (XML message catalogues are the recommended option, 
+ * ResourceBundles are supported mainly for backwards compatibility).
+ * <br/>
+ * A ResourceBundle is a set of property files which will be found in the classpath. 
+ * (E.g. myi18n_de.properties, myi18n_en.properties)
+ * In this case the catalogue string is the base name of the resource bundle.
+ *  
+ * <br/>
+ * An XML message catalogue is an XML file which will be retrieved via SourceResolver.
+ * In this case the catalogue string is a URI (e.g. yanelrepo:/i18n/myi18n.xml)
+ * <br/>
+ * The format of the XML file is the following:
+ * 
+ * <pre>
+ * &lt;messages&gt;
+ *   &lt;message key="user"&gt;
+ *     &lt;text language="de"&gt;Benutzer&lt;/text&gt;
+ *     &lt;text language="en"&gt;User&lt;/text&gt;
+ *   &lt;/message&gt;
+ *   &lt;message key="logout"&gt;
+ *     &lt;text language="de"&gt;Abmelden&lt;/text>
+ *     &lt;text language="en"&gt;Log out&lt;/text>
+ *   &lt;/message&gt;
+ * &lt;/messages&gt;
+ * </pre>
+ */
+public class I18nTransformer3 extends AbstractTransformer {
+
+    private static Category log = Category.getInstance(I18nTransformer2.class);
+    private MessageManager messageManager;
+    private URIResolver resolver;
+    private boolean insideI18n;
+    private String key;
+    private StringBuffer textBuffer;
+    private Locale locale;
+
+    public static final String NS_URI = "http://www.wyona.org/yanel/i18n/1.0";
+    
+    public I18nTransformer3(String catalogue, String language, String defaultLanguage, URIResolver resolver) {
+        this.resolver = resolver;
+        this.locale = new Locale(language);
+        Locale defaultLocale = new Locale(defaultLanguage);
+        this.messageManager = new MessageManager(defaultLocale);
+        MessageProvider messageProvider = getMessageProvider(catalogue);
+        this.messageManager.addMessageProvider("catalogue-0", messageProvider);
+    }
+
+    public I18nTransformer3(String[] catalogues, String language, String defaultLanguage, URIResolver resolver) {
+        this.resolver = resolver;
+        this.locale = new Locale(language);
+        Locale defaultLocale = new Locale(defaultLanguage);
+        this.messageManager = new MessageManager(defaultLocale);
+        
+        for (int i = 0; i < catalogues.length; i++) {
+            MessageProvider messageProvider = getMessageProvider(catalogues[i]);
+            this.messageManager.addMessageProvider("catalogue-" + i, messageProvider);
+        }
+    }
+    
+    protected MessageProvider getMessageProvider(String catalogue) {
+        if (catalogue.indexOf(":") == -1) {
+            return new ResourceBundleMessageProvider(catalogue);
+        } else {
+            try {
+                Source source = resolver.resolve(catalogue, null);
+                InputStream is = SAXSource.sourceToInputSource(source).getByteStream();
+                return new XMLMessageProvider(is);
+            } catch (SourceException e) {
+                throw new RuntimeException(e.getMessage(), e);
+            } catch (TransformerException e) {
+                throw new RuntimeException(e.getMessage(), e);
+            }
+        }
+    }
+    
+    protected String getMessage(String key) {
+        String value = this.messageManager.getText(key, this.locale);
+        if (value == null) {
+            log.error("cannot find message for key: " + key);
+        }
+        return value;
+    }
+    
+    public void startElement(String namespaceURI, String localName, String qName, Attributes attrs) throws SAXException {
+        
+        if (this.insideI18n) {
+            throw new SAXException("no elements allowed inside of i18n element");
+        }
+        
+        if (isI18nElement(namespaceURI, localName, qName)) {
+            this.insideI18n = true;
+            this.textBuffer = new StringBuffer(); 
+            this.key = attrs.getValue("key");
+            
+        } else {
+            // translate attributes:
+            
+            int index = attrs.getIndex(NS_URI, "attr");
+            
+            if (index != -1) {
+                List i18nAttrs = Arrays.asList(attrs.getValue(index).split(" "));
+                AttributesImpl newAttrs = new AttributesImpl();
+                
+                for(int i = 0; i < attrs.getLength(); i++) {
+                    String attrUri = attrs.getURI(i);
+                    String attrLocalName = attrs.getLocalName(i);
+                    String attrQName = attrs.getQName(i);
+                    String attrValue = attrs.getValue(i);
+                    String attrType = attrs.getType(i);
+                    
+                    if (!attrLocalName.equals("attr") || !attrUri.equals(NS_URI)) {
+                        if (i18nAttrs.contains(attrQName)) {
+                            String i18nValue = getMessage(attrValue);
+                            if (i18nValue == null) {
+                                i18nValue = attrValue;
+                            }
+                            newAttrs.addAttribute(attrUri, attrLocalName, attrQName, attrType, i18nValue);
+                        } else {
+                            newAttrs.addAttribute(attrUri, attrLocalName, attrQName, attrType, attrValue);
+                        }
+                    }
+                }
+                super.startElement(namespaceURI, localName, qName, newAttrs);
+                
+            } else {
+                
+                // support old i18n attribute syntax for compatibility reasons: 
+                AttributesImpl newAttrs = new AttributesImpl();
+                for(int i = 0; i < attrs.getLength(); i++) {
+                    String attrUri = attrs.getURI(i);
+                    String attrLocalName = attrs.getLocalName(i);
+                    String attrQName = attrs.getQName(i);
+                    String attrValue = attrs.getValue(i);
+                    String attrType = attrs.getType(i);
+                    
+                    if (attrValue.indexOf("i18n:attr key=") != -1) {
+                        String key = attrValue.substring(14);
+
+                        String i18nValue = getMessage(key);
+                        if (i18nValue == null) {
+                            i18nValue = key;
+                        }
+                        newAttrs.addAttribute(attrUri, attrLocalName, attrQName, attrType, i18nValue);
+                    } else {
+                        newAttrs.addAttribute(attrUri, attrLocalName, attrQName, attrType, attrValue);
+                    }
+                }
+                super.startElement(namespaceURI, localName, qName, newAttrs);
+            }
+        }
+    }
+
+    public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+        if (isI18nElement(namespaceURI, localName, qName)) {
+            String defaultText = this.textBuffer.toString();
+            if (this.key == null) {
+                this.key = defaultText;
+            }
+            String i18nText = getMessage(key);
+            if (i18nText == null) {
+                i18nText = defaultText;
+            }
+            if (i18nText.length() == 0) {
+                i18nText = key;
+            }
+            
+            if (log.isDebugEnabled()) {
+                log.debug("TAG [key] " + this.key + " [message]" + i18nText);
+            }
+            char[] i18nMessage = i18nText.toCharArray(); 
+            this.insideI18n = false;
+            characters(i18nMessage, 0, i18nMessage.length);
+        } else {
+            super.endElement(namespaceURI, localName, qName);
+        }
+    }
+    
+    /**
+     * Decides whether a the given element is a i18n element.
+     * Suppports the &lt;text&gt; element and for backwards compatibility also
+     * the &lt;message&gt; element.
+     * @param namespaceURI
+     * @param localName
+     * @param qName
+     * @return true if the element is a i18n element
+     */
+    protected boolean isI18nElement(String namespaceURI, String localName, String qName) {
+        if (namespaceURI.equals(NS_URI) && (localName.equals("text") || localName.equals("message"))) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void characters(char[] buf, int offset, int len) throws SAXException {
+        if (this.insideI18n) {
+            this.textBuffer.append(buf, offset, len);
+        } else {
+            super.characters(buf, offset, len);
+        }
+    }
+
+}

Modified: public/yanel/trunk/src/impl/java/org/wyona/yanel/impl/resources/BasicXMLResource.java
===================================================================
--- public/yanel/trunk/src/impl/java/org/wyona/yanel/impl/resources/BasicXMLResource.java	2008-02-28 08:30:22 UTC (rev 32612)
+++ public/yanel/trunk/src/impl/java/org/wyona/yanel/impl/resources/BasicXMLResource.java	2008-02-28 08:52:28 UTC (rev 32613)
@@ -19,6 +19,7 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -47,6 +48,7 @@
 import org.wyona.yanel.core.serialization.SerializerFactory;
 import org.wyona.yanel.core.source.SourceResolver;
 import org.wyona.yanel.core.transformation.I18nTransformer2;
+import org.wyona.yanel.core.transformation.I18nTransformer3;
 import org.wyona.yanel.core.transformation.XIncludeTransformer;
 import org.wyona.yanel.core.util.PathUtil;
 import org.wyona.yanel.impl.resources.xml.ConfigurableViewDescriptor;
@@ -250,7 +252,7 @@
             }
 
             // create i18n transformer:
-            I18nTransformer2 i18nTransformer = new I18nTransformer2(getI18NCatalogueNames(), getRequestedLanguage(), getRealm().getDefaultLanguage());
+            I18nTransformer3 i18nTransformer = new I18nTransformer3(getI18NCatalogueNames(), getRequestedLanguage(), getRealm().getDefaultLanguage(), uriResolver);
             i18nTransformer.setEntityResolver(catalogResolver);
 
             // create xinclude transformer:
@@ -335,16 +337,24 @@
 
     /**
      * Gets the names of the i18n message catalogues used for the i18n transformation.
-     * Looks for an rc config property named 'i18n-catalogue'. Defaults to 'global'.
+     * Uses the following priorization:
+     * 1. rc config properties named 'i18n-catalogue'.
+     * 2. realm i18n-catalogue 
+     * 3. 'global'
      * @return i18n catalogue name
      */
     protected String[] getI18NCatalogueNames() throws Exception {
-        String[] catalogueNames = getResourceConfigProperties("i18n-catalogue");
-        if (catalogueNames == null || catalogueNames.length == 0) {
-            catalogueNames = new String[1];
-            catalogueNames[0] = "global";
+        ArrayList catalogues = new ArrayList();
+        String[] rcCatalogues = getResourceConfigProperties("i18n-catalogue");
+        for (int i = 0; i < rcCatalogues.length; i++) {
+            catalogues.add(rcCatalogues[i]);
         }
-        return catalogueNames;
+        String realmCatalogue = getRealm().getI18nCatalogue();
+        if (realmCatalogue != null) {
+            catalogues.add(realmCatalogue);
+        }
+        catalogues.add("global");
+        return (String [])catalogues.toArray(new String[catalogues.size()]);
     }
 
     /**



More information about the Yanel-commits mailing list