You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by di...@apache.org on 2001/07/19 13:22:22 UTC

cvs commit: xml-cocoon2/webapp/i18n/translations convert.xsl messages.xml messages_de.xml messages_en.xml messages_es.xml messages_hy.xml messages_pl.xml messages_ru.xml

dims        01/07/19 04:22:22

  Modified:    src/org/apache/cocoon/transformation I18nTransformer.java
               webapp   sitemap.xmap
               webapp/i18n simple.xml simple.xsp sitemap.xmap
  Added:       src/org/apache/cocoon/i18n XMLResourceBundle.java
                        XMLResourceBundleFactory.java
               webapp/i18n/translations convert.xsl messages.xml
                        messages_de.xml messages_en.xml messages_es.xml
                        messages_hy.xml messages_pl.xml messages_ru.xml
  Log:
  Patches from Marcus Crafter <cr...@fztig938.bank.dresdner.net> for i18n
  
  Revision  Changes    Path
  1.1                  xml-cocoon2/src/org/apache/cocoon/i18n/XMLResourceBundle.java
  
  Index: XMLResourceBundle.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  //package org.apache.avalon.excalibur.i18n;
  package org.apache.cocoon.i18n;
  
  /** JDK classes **/
  import java.io.IOException;
  import java.util.Map;
  import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.Locale;
  import java.util.Enumeration;
  import java.util.ResourceBundle;
  import java.util.MissingResourceException;
  
  /** W3C DOM classes **/
  import org.w3c.dom.Document;
  import org.w3c.dom.Node;
  import org.w3c.dom.NodeList;
  import org.w3c.dom.NamedNodeMap;
  
  /** SAX classes **/
  import org.xml.sax.SAXException;
  
  /** Parser classes **/
  import javax.xml.parsers.DocumentBuilder;
  import javax.xml.parsers.DocumentBuilderFactory;
  import javax.xml.parsers.ParserConfigurationException;
  
  /** XPath classes **/
  import org.apache.xpath.XPathAPI;
  
  /** TRaX classes **/
  import javax.xml.transform.TransformerException;
  
  /** Avalon classes **/
  import org.apache.avalon.framework.logger.Loggable;
  import org.apache.avalon.framework.component.Component;
  import org.apache.log.Logger;
  
  /**
   * @author <a href="mailto:mengelhart@earthtrip.com">Mike Engelhart</a>
   * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
   * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
   * @version $Id: XMLResourceBundle.java,v 1.1 2001/07/19 11:22:21 dims Exp $
   */
  public class XMLResourceBundle
      extends ResourceBundle
      implements Loggable, Component
  {
      /** DOM factory */
      protected static DocumentBuilderFactory docfactory =
      	DocumentBuilderFactory.newInstance();
  
      /** Cache for storing string values for existing XPaths */
      private Hashtable cache = new Hashtable();
  
      /** Cache for storing non-existing XPaths */
      private Map cacheNotFound = new HashMap();
  
      /** Bundle name */
      private String name = "";
  
      /** DOM-tree containing the bundle content */
      private Document doc;
  
      /** Locale of the bundle */
      private Locale locale;
  
      /** Parent of the current bundle */
      protected XMLResourceBundle parent = null;
  
      /** Logger */
      protected Logger logger;
  
      /**
       * Initalize the bundle
       *
       * @param name              name of the bundle
       * @param fileName          name of the XML source file
       * @param locale            locale
       * @param parent            parent bundle of this bundle
       * @param cacheAtStartup    cache all the keys when constructing?
       * @exception IOException   if an IO error occurs while reading the file
       * @exception ParserConfigurationException if no parser is configured
       * @exception SAXException  if an error occurs while parsing the file
       */
      public void init(String name, String fileName, Locale locale, XMLResourceBundle parent, boolean cacheAtStartup)
          throws IOException, ParserConfigurationException, SAXException
      {
          if (logger.isInfoEnabled()) logger.info("Constructing XMLResourceBundle: " + name + ", locale: " + locale);
          this.name = name;
          this.doc = loadResourceBundle(fileName);
          this.locale = locale;
          this.parent = parent;
          if (cacheAtStartup)
              cacheAll(doc.getDocumentElement(), "");
      }
  
      /**
       * Load the DOM tree, based on the file name.
       *
       * @param fileName          name of the XML source file
       * @return the DOM tree
       * @exception IOException   if an IO error occurs while reading the file
       * @exception ParserConfigurationException if no parser is configured
       * @exception SAXException  if an error occurs while parsing the file
       */
      protected static Document loadResourceBundle(String fileName)
          throws IOException, ParserConfigurationException, SAXException
      {
          DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
          DocumentBuilder builder = factory.newDocumentBuilder();
          return builder.parse(fileName);
      }
  
      /**
       * Gets the name of the bundle.
       *
       * @return the name
       */
      public String getName()
      {
          return this.name;
      }
  
      /**
       * Gets the source DOM tree of the bundle.
       *
       * @return the DOM tree
       */
      public Document getDocument()
      {
          return this.doc;
      }
  
      /**
       * Gets the locale of the bundle.
       *
       * @return the locale
       */
      public Locale getLocale()
      {
          return locale;
      }
  
      /**
       * Does the &quot;key-cache&quot; contain the value with such key?
       *
       * @param   key     the key to the value to be returned
       * @return  true if contains, false otherwise
       */
      private boolean cacheContains(String key)
      {
          if (logger.isDebugEnabled()) logger.debug(name + ": cache contains: " + key);
          return cache.containsKey(key);
      }
  
      /**
       * Does the &quot;key-not-found-cache&quot; contain such key?
       *
       * @param   key     the key to the value to be returned
       * @return  true if contains, false otherwise
       */
      private boolean cacheNotFoundContains(String key)
      {
          if (logger.isDebugEnabled()) logger.debug(name + ": cache_not_found contains: " + key);
          return cacheNotFound.containsKey(key);
      }
  
      /**
       * Cache the key and value in &quot;key-cache&quot;.
       *
       * @param   key     the key
       * @param   value   the value
       */
      private void cacheKey(String key, String value)
      {
          if (logger.isDebugEnabled()) logger.debug(name + ": caching: " + key + " = " + value);
          cache.put(key, value);
      }
  
      /**
       * Cache the key in &quot;key-not-found-cache&quot;.
       *
       * @param   key     the key
       */
      private void cacheNotFoundKey(String key)
      {
          if (logger.isDebugEnabled()) logger.debug(name + ": caching not_found: " + key);
          cacheNotFound.put(key, "");
      }
  
      /**
       * Gets the value by the key from the &quot;key-cache&quot;.
       *
       * @param   key     the key
       * @return          the value
       */
      private String getFromCache(String key)
      {
          if (logger.isDebugEnabled()) logger.debug(name + ": returning from cache: " + key);
          return (String) cache.get(key);
      }
  
      /**
       * Steps through the bundle tree and stores all text element values
       * in bundle's cache. Also stores attributes for all element nodes.
       *
       * @param   parent          parent node, must be an element
       * @param   pathToParent    XPath to the parent node
       */
      private void cacheAll(Node parent, String pathToParent)
      {
          NodeList children = parent.getChildNodes();
          int childnum = children.getLength();
  
          for(int i = 0; i < childnum; i++)
          {
              Node child = children.item(i);
  
              if(child.getNodeType() == Node.ELEMENT_NODE)
              {
                  String pathToChild = pathToParent + '/' + child.getNodeName();
  
                  NamedNodeMap attrs = child.getAttributes();
                  if(attrs != null)
                  {
                      Node temp = null;
                      String pathToAttr = null;
                      int attrnum = attrs.getLength();
                      for(int j = 0; j < attrnum; j++)
                      {
                          temp = attrs.item(j);
                          if (!temp.getNodeName().equalsIgnoreCase("xml:lang"))
                              pathToChild += "[@" + temp.getNodeName() + "='" + temp.getNodeValue() + "']";
                      }
                  }
  
                  String childValue = getTextValue(child);
                  if(childValue != null)
                      cacheKey(pathToChild, childValue);
                  else
                      cacheAll(child, pathToChild);
              }
          }
      }
  
      /**
       * Get value by key and substitute variables.
       *
       * @param key           key
       * @param dictionary    map with variable values
       * @return              value with variable values substituted
       * @exception MissingResourceException if resource was not found
       */
      public String getString(String key, Map dictionary)
          throws MissingResourceException
      {
          String value = _getString(key);
          if (value == null)
              throw new MissingResourceException(
                      "Unable to locate resource: " + key,
                      XMLResourceBundle.class.getName(),
                      key);
          else
              return substitute(value, dictionary);
      }
  
      /**
       * Get value by key.
       *
       * @param   key     the key
       * @return          the value
       */
      private String _getString(String key)
      {
          if (key == null) return null;
          String value = getFromCache(key);
  
          if (value == null && !cacheNotFoundContains(key))
          {
  	    if (doc != null)
                  value = _getString(this.doc.getDocumentElement(), key);
  
              if (value == null)
              {
                  if (this.parent != null)
                      value = this.parent._getString(key);
              }
  
              if (value != null)
                  cacheKey(key, value);
              else
                  cacheNotFoundKey(key);
          }
          return value;
      }
  
      /**
       * Get value by key from a concrete node.
       *
       * @param   node    the node
       * @param   key     the key
       * @return          the value
       */
      private String _getString(Node node, String key)
      {
          String value = null;
          try
          {
              value = getTextValue(_getNode(node, key));
          }
          catch (Exception e)
          {
              logger.error(name + ": error while locating resource: " + key, e);
          }
          return value;
      }
  
      /**
       * Get the text value of the node.
       *
       * @param   node    the node
       * @return          the value
       */
      private static String getTextValue(Node element)
      {
          if (element == null) return null;
          NodeList list = element.getChildNodes();
          int listsize = list.getLength();
  
          Node item = null;
          String itemValue = null;
  
          for(int i = 0; i < listsize; i++)
          {
              item = list.item(i);
  	    if(item.getNodeType() != Node.TEXT_NODE)
  	        return null;
  
              itemValue = item.getNodeValue();
  	    if(itemValue == null)
  	        return null;
  
              itemValue = itemValue.trim();
  	    if(itemValue.length() == 0)
  	        return null;
  
              return itemValue;
          }
          return null;
      }
  
      /**
       * Get the node with the supplied XPath key.
       *
       * @param   key     the key
       * @return          the node
       */
      private Node _getNode(String key)
      {
          return _getNode(this.doc.getDocumentElement(), key);
      }
  
      /**
       * Get the node with the supplied XPath key, starting from concrete
       * root node.
       *
       * @param   rootNode    the root node
       * @param   key         the key
       * @return              the node
       */
      private Node _getNode(Node rootNode, String key)
      {
          Node node = null;
          try
          {
              node = XPathAPI.selectSingleNode(rootNode, key);
          }
          catch (Exception e)
          {
              logger.error("Error while locating resource with key: " + key, e);
          }
          return node;
      }
  
      /**
       * Substitute the &quot;variables&quot; in the string with the values
       * provided in the map.
       *
       * @param value         value where to search for variables
       * @param dictionary    map with variable values
       * @return              value with variable values substituted
       */
      public String substitute(String value, Map dictionary)
      {
          if (value == null || dictionary == null) return value;
  
          StringBuffer result = new StringBuffer(value.length());
          int startPos = 0;
          int endPos = 0;
          int lastPos = value.length();
          Object varValue = "";
          String varKey = "", oldKey = "";
          while (endPos < lastPos)
          {
              startPos = endPos;
              endPos = value.indexOf('{', startPos);
              if (endPos == -1)
                  endPos = lastPos;
              result.append(value.substring(startPos, endPos));
              if (endPos < lastPos)
                  endPos++;
              if (endPos < lastPos)
              {
                  startPos = endPos;
                  endPos = value.indexOf('}', startPos);
                  if (endPos == -1)
                      endPos = lastPos;
                  oldKey = varKey;
                  varKey = value.substring(startPos, endPos);
                  if (!oldKey.equals(varKey))
                      varValue = dictionary.get(varKey);
                  if (varValue != null)
                  {
                      if (logger.isDebugEnabled()) logger.debug("Substituting var: " + varKey + " --> " + varValue);
                      result.append(varValue);
                  }
                  else
                  {
                      if (logger.isWarnEnabled()) logger.warn(name + ": var not found: " + varKey);
                      result.append('{').append(varKey).append('}');
                  }
                  if (endPos < lastPos)
                      endPos++;
              }
          }
          return result.toString();
      }
  
      /**
       * Set the logger.
       *
       * @param logger the logger
       */
      public void setLogger( final Logger logger )
      {
          this.logger = logger;
      }
  
      /**
       * Return an Object by key.
       * Implementation of the ResourceBundle abstract method.
       *
       * @param key the key
       * @return the object
       */
      protected Object handleGetObject(String key)
          throws MissingResourceException
      {
          return (Object) getString(key, null);
      }
  
      /**
       * Return an enumeration of the keys.
       * Implementation of the ResourceBundle abstract method.
       *
       * @return the enumeration of keys
       */
      public Enumeration getKeys()
      {
          return cache.keys();
      }
  }
  
  
  
  1.1                  xml-cocoon2/src/org/apache/cocoon/i18n/XMLResourceBundleFactory.java
  
  Index: XMLResourceBundleFactory.java
  ===================================================================
  /*
   * Copyright (C) The Apache Software Foundation. All rights reserved.
   *
   * This software is published under the terms of the Apache Software License
   * version 1.1, a copy of which has been included with this distribution in
   * the LICENSE file.
   */
  //package org.apache.avalon.excalibur.i18n;
  package org.apache.cocoon.i18n;
  
  import java.util.Map;
  import java.util.HashMap;
  import java.util.Locale;
  
  import org.xml.sax.SAXParseException;
  
  import org.apache.avalon.framework.configuration.Configurable;
  import org.apache.avalon.framework.configuration.Configuration;
  import org.apache.avalon.framework.configuration.ConfigurationException;
  
  import org.apache.avalon.framework.logger.Loggable;
  import org.apache.log.Logger;
  
  import org.apache.avalon.framework.component.Component;
  import org.apache.avalon.framework.component.DefaultComponentSelector;
  import org.apache.avalon.framework.component.ComponentException;
  import org.apache.avalon.framework.thread.ThreadSafe;
  
  /**
   * This is the XMLResourceBundleFactory, the method for getting and
   * creating XMLResourceBundles.
   *
   * @author <a href="mailto:mengelhart@earthtrip.com">Mike Engelhart</a>
   * @author <a href="mailto:neeme@one.lv">Neeme Praks</a>
   * @author <a href="mailto:oleg@one.lv">Oleg Podolsky</a>
   * @version $Id: XMLResourceBundleFactory.java,v 1.1 2001/07/19 11:22:21 dims Exp $
   */
  
  public class XMLResourceBundleFactory
      extends DefaultComponentSelector
      implements Configurable, Loggable, ThreadSafe
  {
      /** Should we load bundles to cache on startup or not? */
      protected boolean cacheAtStartup = false;
  
      /** Root directory to all bundle names */
      protected String directory;
  
      /** Cache for the names of the bundles that were not found */
      protected Map cacheNotFound = new HashMap();
  
      /** The logger */
      protected Logger logger;
  
      /** Constants for configuration keys */
      public static class ConfigurationKeys
      {
          public static final String CACHE_AT_STARTUP = "cache-at-startup";
          public static final String ROOT_DIRECTORY = "catalogue-location";
      }
  
      /**
       * Default constructor.
       */
      public XMLResourceBundleFactory()
      {
      }
  
      /**
       * Set the logger.
       *
       * @param logger the logger
       */
      public void setLogger( final Logger logger )
      {
          this.logger = logger;
      }
  
      /**
       * Configure the component.
       *
       * @param configuration the configuration
       */
      public void configure( Configuration configuration ) throws ConfigurationException
      {
          this.cacheAtStartup = configuration.getChild(ConfigurationKeys.CACHE_AT_STARTUP).getValueAsBoolean(false);
  
          try
          {
              this.directory = configuration.getChild(ConfigurationKeys.ROOT_DIRECTORY, true).getValue();
          }
          catch (ConfigurationException e)
          {
              if (logger.isWarnEnabled()) logger.warn("Root directory not provided in configuration, using default (root).");
              this.directory = "";
          }
  
          if (logger.isDebugEnabled())
          {
              logger.debug(
                  "XMLResourceBundleFactory configured with: cacheAtStartup = " +
                  cacheAtStartup + ", directory = '" + directory + "'"
              );
          }
      }
  
      /**
       * Select a bundle based on bundle name and locale.
       *
       * @param name              bundle name
       * @param locale            locale
       * @return                  the bundle
       * @exception ComponentException if a bundle is not found
       */
      public Component select(String name, Locale locale)
          throws ComponentException
      {
          return select(name, locale, cacheAtStartup);
      }
  
      /**
       * Select a bundle based on bundle name and locale.
       *
       * @param name              bundle name
       * @param locale            locale
       * @param cacheAtStartup    cache all the keys when constructing?
       * @return                  the bundle
       * @exception ComponentException if a bundle is not found
       */
      public Component select(String name, Locale loc, boolean cacheAtStartup)
          throws ComponentException
      {
          Component bundle = _select(name, loc, cacheAtStartup);
          if (bundle == null)
              throw new ComponentException("Unable to locate resource: " + name);
          return bundle;
      }
  
      /**
       * Select a bundle based on bundle name and locale.
       *
       * @param name              bundle name
       * @param locale            locale
       * @return                  the bundle
       */
      private Component _select(String name, Locale loc)
      {
          return _select(name, loc, cacheAtStartup);
      }
  
      /**
       * Select the parent bundle of the current bundle, based on
       * bundle name and locale.
       *
       * @param name              bundle name
       * @param locale            locale
       * @return                  the bundle
       */
      protected Component selectParent(String name, Locale loc)
      {
          return selectParent(name, loc, cacheAtStartup);
      }
  
      /**
       * Select the parent bundle of the current bundle, based on
       * bundle name and locale.
       *
       * @param name              bundle name
       * @param locale            locale
       * @param cacheAtStartup    cache all the keys when constructing?
       * @return                  the bundle
       */
      protected Component selectParent(String name, Locale loc, boolean cacheAtStartup)
      {
          return _select(name, getParentLocale(loc), cacheAtStartup);
      }
  
      /**
       * Select a bundle based on bundle name and locale name.
       *
       * @param name              bundle name
       * @param localeName        locale name
       * @return                  the bundle
       * @exception ComponentException if a bundle is not found
       */
      public Component select(String name, String localeName)
          throws ComponentException
      {
          return select(name, new Locale(localeName, localeName) );
      }
  
      /**
       * Select a bundle based on source XML file name.
       *
       * @param fileName          file name
       * @return                  the bundle
       * @exception ComponentException if a bundle is not found
       */
      public Component selectFromFilename(String fileName)
          throws ComponentException
      {
          return selectFromFilename(fileName, cacheAtStartup);
      }
  
      /**
       * Select a bundle based on source XML file name.
       *
       * @param fileName          file name
       * @param cacheAtStartup    cache all the keys when constructing?
       * @return                  the bundle
       * @exception ComponentException if a bundle is not found
       */
      public Component selectFromFilename(String fileName, boolean cacheAtStartup)
          throws ComponentException
      {
          Component bundle = _select(fileName, null, cacheAtStartup);
          if (bundle == null)
              throw new ComponentException("Unable to locate resource: " + fileName);
          return bundle;
      }
  
      /**
       * Select a bundle based on bundle name and locale.
       *
       * @param name              bundle name
       * @param locale            locale
       * @param cacheAtStartup    cache all the keys when constructing?
       * @return                  the bundle
       * @exception ComponentException if a bundle is not found
       */
      private Component _select(String name, Locale loc, boolean cacheAtStartup)
      {
          if (logger.isDebugEnabled()) logger.debug("_getBundle: " + name + ", locale " + loc);
          String fileName = getFileName(name, loc);
          XMLResourceBundle bundle = (XMLResourceBundle) selectCached(fileName);
          if (bundle == null && !isNotFoundBundle(fileName))
          {
              if (logger.isDebugEnabled()) logger.debug("not found in cache, loading: " + fileName);
              synchronized(this)
              {
                  bundle = (XMLResourceBundle) selectCached(fileName);
                  if (bundle == null && !isNotFoundBundle(fileName))
                  {
                      if (logger.isDebugEnabled()) logger.debug("synchronized: not found in cache, loading: " + fileName);
                      bundle = _loadBundle(name, fileName, loc, cacheAtStartup);
                      Locale parentLoc = loc;
                      String parentBundleName;
                      while (bundle == null && parentLoc != null && !parentLoc.getLanguage().equals(""))
                      {
                          if (logger.isDebugEnabled()) logger.debug("synchronized: still not found, trying parent: " + fileName);
                          parentLoc = getParentLocale(parentLoc);
                          parentBundleName = getFileName(name, parentLoc);
                          bundle = _loadBundle(name, parentBundleName, parentLoc, cacheAtStartup);
                          updateCache(parentBundleName, bundle);
                      }
                      updateCache(fileName, bundle);
                  }
              }
          }
          return (Component) bundle;
      }
  
      /**
       * Construct a bundle based on bundle name, file name and locale.
       *
       * @param name              bundle name
       * @param fileName          full path to source XML file
       * @param locale            locale
       * @param cacheAtStartup    cache all the keys when constructing?
       * @return                  the bundle, null if loading failed
       */
      private XMLResourceBundle _loadBundle(String name, String fileName, Locale loc, boolean cacheAtStartup)
      {
          if (logger.isDebugEnabled()) logger.debug("Trying to load bundle: " + name + ", locale " + loc + ", filename " + fileName);
          XMLResourceBundle bundle = null;
          XMLResourceBundle parentBundle = null;
          try
          {
              if (loc != null && !loc.getLanguage().equals(""))
                  parentBundle = (XMLResourceBundle) selectParent(name, loc);
              bundle = new XMLResourceBundle();
              bundle.setLogger(logger);
              bundle.init(name, fileName, loc, parentBundle, cacheAtStartup);
          }
          catch (SAXParseException e)
          {
              if (logger.isInfoEnabled()) logger.info("Resource loading failed: " + e.getMessage());
  	    bundle = null;
          }
          catch (Exception e)
          {
              logger.error("Error while loading resource: " + name + ", locale " + loc + ", bundleName " + fileName, e);
  	    bundle = null;
          }
          return bundle;
      }
  
      /**
       * Returns the next locale up the parent hierarchy.
       * E.g. the parent of new Locale("en","us","mac") would be
       * new Locale("en", "us", "").
       *
       * @param locale            the locale
       * @return                  the parent locale
       */
      protected Locale getParentLocale(Locale loc)
      {
          Locale newloc;
          if (loc.getVariant().equals(""))
          {
              if (loc.getCountry().equals(""))
                  newloc = new Locale("","","");
              else
                  newloc = new Locale(loc.getLanguage(), "", "");
          }
          else
              newloc = new Locale(loc.getLanguage(), loc.getCountry(), "");
  
          return newloc;
      }
  
      /**
       * Maps a bundle name and locale to a full path in the filesystem.
       * If you need a different mapping, then just override this method.
       *
       * @param locale            the locale
       * @return                  the parent locale
       */
      protected String getFileName(String name, Locale loc)
      {
          StringBuffer sb = new StringBuffer(getDirectory());
          sb.append('/').append(name);
          if (loc != null)
          {
              if (! loc.getLanguage().equals(""))
              {
                  sb.append("_");
                  sb.append(loc.getLanguage());
              }
              if (! loc.getCountry().equals(""))
              {
                  sb.append("_");
                  sb.append(loc.getCountry());
              }
              if (! loc.getVariant().equals(""))
              {
                  sb.append("_");
                  sb.append(loc.getVariant());
              }
          }
          sb.append(".xml");
  
          String result = sb.toString();
          if (logger.isDebugEnabled()) logger.debug("Resolving bundle name to file name: " + name + ", locale " + loc + " --> " + result);
          return result;
      }
  
      /**
       * Selects a bundle from the cache.
       *
       * @param fileName          file name of the bundle
       * @return                  the cached bundle; null, if not found
       */
      protected Component selectCached(String fileName)
      {
          Component bundle = null;
          try
          {
              bundle = super.select(fileName);
              if (logger.isDebugEnabled()) logger.debug("Returning from cache: " + fileName);
          }
          catch (ComponentException e)
          {
              if (logger.isDebugEnabled()) logger.debug("Not found in cache: " + fileName);
          }
          return bundle;
      }
  
      /**
       * Checks if the bundle is in the &quot;not-found&quot; cache.
       *
       * @param fileName          file name of the bundle
       * @return                  true, if the bundle wasn't found already before;
       *                          otherwise, false.
       */
      protected boolean isNotFoundBundle(String fileName)
      {
          String result = (String)(cacheNotFound.get(fileName));
          if (result != null)
          {
              if (logger.isDebugEnabled()) logger.debug("Returning from not_found_cache: " + fileName);
          }
          else
          {
              if (logger.isDebugEnabled()) logger.debug("Not found in not_found_cache: " + fileName);
          }
          return result != null;
      }
  
      /**
       * Checks if the bundle is in the &quot;not-found&quot; cache.
       *
       * @param fileName          file name of the bundle
       * @return                  true, if the bundle wasn't found already before;
       *                          otherwise, false.
       */
      protected void updateCache(String fileName, XMLResourceBundle bundle)
      {
          if (bundle == null)
          {
              if (logger.isDebugEnabled()) logger.debug("Updating not_found_cache: " + fileName);
              cacheNotFound.put(fileName, fileName);
          }
          else
          {
              if (logger.isDebugEnabled()) logger.debug("Updating cache: " + fileName);
              super.put((Object) fileName, (Component) bundle);
          }
      }
  
      /**
       * Returns the root directory to all bundles.
       *
       * @return the directory path
       */
      public String getDirectory()
      {
          return directory;
      }
  
      /**
       * Should we load bundles to cache on startup or not?
       *
       * @return true if pre-loading all resources; false otherwise
       */
      public boolean cacheAtStartup()
      {
          return cacheAtStartup;
      }
  }
  
  
  
  1.14      +428 -319  xml-cocoon2/src/org/apache/cocoon/transformation/I18nTransformer.java
  
  Index: I18nTransformer.java
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/org/apache/cocoon/transformation/I18nTransformer.java,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- I18nTransformer.java	2001/07/07 11:43:35	1.13
  +++ I18nTransformer.java	2001/07/19 11:22:21	1.14
  @@ -10,10 +10,14 @@
   package org.apache.cocoon.transformation;
   
   import org.apache.cocoon.ProcessingException;
  -import org.apache.cocoon.acting.LangSelect;
  +import org.apache.cocoon.acting.LocaleAction;
  +import org.apache.cocoon.components.parser.Parser;
   import org.apache.cocoon.environment.Source;
   import org.apache.cocoon.environment.SourceResolver;
   
  +import org.apache.cocoon.i18n.XMLResourceBundle;
  +import org.apache.cocoon.i18n.XMLResourceBundleFactory;
  +
   import org.apache.avalon.excalibur.pool.Poolable;
   import org.apache.avalon.framework.component.ComponentManager;
   import org.apache.avalon.framework.component.ComponentException;
  @@ -22,6 +26,13 @@
   import org.apache.avalon.framework.parameters.Parameters;
   import org.apache.avalon.framework.logger.Loggable;
   
  +import org.apache.avalon.framework.configuration.Configurable;
  +import org.apache.avalon.framework.configuration.Configuration;
  +import org.apache.avalon.framework.configuration.ConfigurationException;
  +import org.apache.avalon.framework.configuration.DefaultConfiguration;
  +
  +import org.apache.regexp.RE;
  +
   import org.xml.sax.Attributes;
   import org.xml.sax.InputSource;
   import org.xml.sax.SAXException;
  @@ -36,6 +47,7 @@
   import java.util.ArrayList;
   import java.util.Locale;
   import java.util.Date;
  +import java.util.MissingResourceException;
   
   import java.text.Format;
   import java.text.MessageFormat;
  @@ -49,121 +61,199 @@
   import java.net.MalformedURLException;
   
   /**
  - * I18nTransformer. Cocoon2 port of Infozone groups I18nProcessor.
  + * Internationalisation transformer. Used to transform i18n markup into text
  + * based on a particular locale.
  + *
    * <p>
  - * Sitemap configuration:
  - * </p>
  + *
  + * The i18n transformer works by obtaining the users locale via
  + * <code>getLocale()</code> in the LocaleAction.
  + * (@see org.apache.cocoon.acting.LocaleAction). It them attempts to find a
  + * message catalogue that satisifies the particular locale, and use it for
  + * for text replacement within i18n markup.
  + *
    * <p>
  - * &lt;map:transformer<br>
  - *	name="translate"<br>
  - *	src="org.apache.cocoon.transformation.I18nTransformer"/&gt;<br>
  - * </p>
  + *
  + * Catalogues are maintained in separate files, with a naming convention
  + * similar to that of ResourceBundle (@see java.util.ResourceBundle). ie.
  + * <strong>basename</strong>_<strong>locale</strong>, where <i>basename</i>
  + * can be any name, and <i>locale</i> can be any locale specified using
  + * ISO 639/3166 characters (eg. en_AU, de_AT, es).
  + *
    * <p>
  - * &lt;map:match pattern="file"&gt;<br>
  - *	&lt;map:generate src="file.xml"/&gt;<br>
  - * 	&lt;map:transform type="translate"&gt;<br>
  - *		&lt;parameter name="default_lang" value="fi"/&gt;<br>
  - *		&lt;parameter name="available_lang_1" value="fi"/&gt;<br>
  - *		&lt;parameter name="available_lang_2" value="en"/&gt;<br>
  - *		&lt;parameter name="available_lang_3" value="sv"/&gt;<br>
  - *		&lt;parameter name="src"<br>
  - *			value="translations/file_trans.xml"/&gt;<br>
  - *	&lt;/map:transform&gt;<br>
  - * </p>
  + *
  + * Catalogues are of the following format:
  + *
  + * <pre>
  + * &lt;?xml version="1.0"?&gt;
  + * &lt;!-- message catalogue file for locale ... --&gt;
  + *
  + * &lt;catalogue xml:lang=&quot;locale&quot;&gt;
  + *        &lt;message key="key"&gt;text&lt;/message&gt;
  + *        ....
  + * &lt;/catalogue&gt;
  + * </pre>
  + *
  + * Where <strong>key</strong> specifies a particular message for that
  + * language.
  + *
    * <p>
  - * When user requests .../file?lang=fi<br>
  - * transformer substitutes text surrounded &lt;i18n:text&gt; with
  - * translations from file_trans.xml.<br>
  - * Attributes listed in &lt;i18n:attr&gt; attribute are also translated
  - * </p>
  + *
  + * Files to be translated contain the following markup:
  + *
  + * <pre>
  + * &lt;?xml version="1.0"?&gt;
  + *
  + * ... some text, translate &lt;i18n:text&gt;key&lt;/i18n:text&gt;
  + * </pre>
  + *
  + * At runtime, the i18n transformer will find a message catalogue for the
  + * user's locale, and will appropriately replace the text between the
  + * <code>&lt;i18n:text&gt;</code> markup, using the value between the tags as
  + * the lookup key.
  + *
    * <p>
  - * file.xml:<br>
  - * &lt;root xmlns:i18n="http://apache.org/cocoon/i18n/2.0"&gt;<br>
  - * 	&lt;elem i18n:attr="title" title="translate_me"&gt;Text&lt;/elem&gt;<br>
  - * 	&lt;elem&gt;&lt;i18n:text&gt;Translate me&lt;/i18n:text&gt;&lt;/elem&gt;<br>
  - * &lt;/root&gt;
  - * </p>
  + *
  + * If the i18n transformer cannot find an appropriate message catalogue for
  + * the user's given locale, it will recursively try to locate a <i>parent</i>
  + * message catalogue, until a valid catalogue can be found.
  + *
  + * ie: 
  + *
  + * <ul>
  + *  <li><strong>catalogue</strong>_<i>language</i>_<i>country</i>_<i>variant</i>.xml
  + *  <li><strong>catalogue</strong>_<i>language</i>_<i>country</i>.xml
  + *  <li><strong>catalogue</strong>_<i>language</i>.xml
  + *  <li><strong>catalogue</strong>.xml
  + * </ul>
  + *
  + * eg: Assuming a basename of <i>messages</i> and a locale of <i>en_AU</i>
  + * (no variant), the following search will occur:
  + *
  + * <ul>
  + *  <li><strong>messages</strong>_<i>en</i>_<i>AU</i>.xml
  + *  <li><strong>messages</strong>_<i>en</i>.xml
  + *  <li><strong>messages</strong>.xml
  + * </ul>
  + *
  + * This allows the developer to write a hierarchy of message catalogues,
  + * at each defining messages with increasing depth of variation.
  + *
    * <p>
  - * file_trans.xml:<br>
  - * &lt;translations&gt;<br>
  - * 	&lt;entry&gt;&lt;key&gt;Translate me&lt;/key&gt;<br>
  - * 		&lt;translation lang="sv"&gt;�vers�tta mej&lt;/translation&gt;<br>
  - * 		&lt;translation lang="fi"&gt;K��nn� minut&lt;/translation&gt;<br>
  - *	&lt;/entry&gt;<br>
  - * &lt;/translations&gt;<br>
  - * </p>
  + *
  + * Sitemap configuration:
  + *
  + * <pre>
  + * &lt;map:transformer name="i18n" src="org.apache.cocoon.transformation.I18nTransformer"&gt;
  + *  &lt;catalogue-name&gt;messages&lt;/catalogue-name&gt;
  + *  &lt;catalogue-location&gt;translations&lt;/catalogue-location&gt;
  + *  &lt;untranslated-text&gt;untranslated&lt;/untranslated-text&gt;
  + *  &lt;cache-at-startup&gt;true&lt;/cache-at-startup&gt;
  + * &lt;/map:transformer&gt;
  + * </pre>
  + *
  + * <ul>
  + *  <li>&lt;strong&gt;catalogue-name&lt;/strong&gt;: base name of the message
  + *      catalogue (<i>mandatory</i>).
  + *  <li>&lt;strong&gt;catalogue-location&lt;/strong&gt;: location of the
  + *      message catalogues (<i>mandatory</i>).
  + *  <li>&lt;strong&gt;untranslated-text&lt;/strong&gt;: default text used for
  + *      untranslated keys (default is 'untranslated-text').
  + *  <li>&lt;strong&gt;cache-at-startup&lt;/strong&gt;: flag whether to cache
  + *      messages at startup (false by default).
  + * </ul>
  + *
    * <p>
  + *
  + * To use the transformer in a pipeline, simply specify it in a particular
  + * transform. eg:
    *
  - * @todo Caching dictionaries in memory.<br>
  - * @todo Date and Number i18n.<br>
  - * @todo Multiple dictionary support. <br>
  -*
  + * <pre>
  + * &lt;map:match pattern="file"&gt;
  + *  &lt;map:generate src="file.xml"/&gt;
  + *  &lt;map:transform type="i18n"/&gt;
  + *  &lt;map:serialize/&gt;
  + * &lt;/map:match&gt;
  + * </pre>
  + *
  + * Future work coming:
  + *
  + * <ul>
  + *  <li>Ability to override definition parameters in the pipeline
  + *  <li>Many clean ups :-)
  + * </ul>
  + *
  + * @author <a href="mailto:Marcus.Crafter@osa.de">Marcus Crafter</a>
    * @author <a href="mailto:kpiroumian@flagship.ru">Konstantin Piroumian</a>
    * @author <a href="mailto:lassi.immonen@valkeus.com">Lassi Immonen</a>
    */
   public class I18nTransformer extends AbstractTransformer
  -implements Composable, Poolable {
  +    implements Composable, Poolable, Configurable {
   
       protected ComponentManager manager;
   
       /**
  -     * The parsed dictionary data.
  -     */
  -    public Map dictionary;
  -
  -    /**
        * The namespace for i18n is "http://apache.org/cocoon/i18n/2.0"
        */
  -    public final static String I18N_NAMESPACE_URI =
  -            "http://apache.org/cocoon/i18n/2.0";
  +    public static final String I18N_NAMESPACE_URI =
  +        "http://apache.org/cocoon/i18n/2.0";
   
       //
       // Dictionary elements and attributes
       //
  -    public final static String I18N_DICTIONARY_ELEMENT = "dictionary";
  -    public final static String I18N_ENTRY_ELEMENT = "entry";
  -    public final static String I18N_KEY_ELEMENT = "key";
  -    public final static String I18N_TRANSLATION_ELEMENT = "translation";
  +    public static final String I18N_DICTIONARY_ELEMENT = "dictionary";
  +    public static final String I18N_ENTRY_ELEMENT = "entry";
  +    public static final String I18N_KEY_ELEMENT = "key";
  +    public static final String I18N_TRANSLATION_ELEMENT = "translation";
   
       //
       // Text elements and attributes
       //
  -    public final static String I18N_LANG = "lang";
  -    public final static String I18N_KEY_ATTRIBUTE = "key";
  -    public final static String I18N_ATTR_ATTRIBUTE = "attr";
  -    public final static String I18N_TEXT_ELEMENT = "text";
  -    public final static String I18N_TRANSLATE_ELEMENT = "translate";
  -    public final static String I18N_PARAM_ELEMENT = "param";
  -    public final static String I18N_DATE_ELEMENT = "date";
  -    public final static String I18N_NUMBER_ELEMENT = "number";
  +    public static final String I18N_LANG = "lang";
  +    public static final String I18N_KEY_ATTRIBUTE = "key";
  +    public static final String I18N_ATTR_ATTRIBUTE = "attr";
  +    public static final String I18N_TEXT_ELEMENT = "text";
  +    public static final String I18N_TRANSLATE_ELEMENT = "translate";
  +    public static final String I18N_PARAM_ELEMENT = "param";
  +    public static final String I18N_DATE_ELEMENT = "date";
  +    public static final String I18N_NUMBER_ELEMENT = "number";
   
       // number and date formatting attributes
  -    public final static String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
  -    public final static String I18N_PATTERN_ATTRIBUTE = "pattern";
  -    public final static String I18N_VALUE_ATTRIBUTE = "value";
  +    public static final String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
  +    public static final String I18N_PATTERN_ATTRIBUTE = "pattern";
  +    public static final String I18N_VALUE_ATTRIBUTE = "value";
  +
  +    // configuration parameters
  +    public static final String I18N_CATALOGUE_NAME = "catalogue-name";
  +    public static final String I18N_CATALOGUE_LOCATION = "catalogue-location";
  +    public static final String I18N_CATALOGUE_PREFIX = "/catalogue/message";
  +    public static final String I18N_UNTRANSLATED = "untranslated-text";
  +    public static final String I18N_CACHE_STARTUP = "cache-at-startup";
  +
       /**
  -     * <code>sub-type</code> attribute is used with <code>i18:number</code> to indicate
  -     * a sub-type: <code>currency</code> or <code>percent</code>.
  +     * <code>sub-type</code> attribute is used with <code>i18:number</code> to
  +     * indicate a sub-type: <code>currency</code> or <code>percent</code>.
        */
  -    public final static String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
  +    public static final String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
  +
       /**
  -     * <code>type</code> attribute is used with <code>i18:param</code> to indicate
  -     * the parameter type: <code>date</code> or <code>number</code>.
  +     * <code>type</code> attribute is used with <code>i18:param</code> to
  +     * indicate the parameter type: <code>date</code> or <code>number</code>.
        * If <code>type</code> is <code>number</code> then a <code>sub-type</code>
        * can be used.
        */
  -    public final static String I18N_TYPE_ATTRIBUTE = "type";
  +    public static final String I18N_TYPE_ATTRIBUTE = "type";
   
       // States of the transformer
  -    private final static int STATE_OUTSIDE = 0;
  -    private final static int STATE_INSIDE_TEXT = 1;
  -    private final static int STATE_INSIDE_PARAM = 2;
  -    private final static int STATE_INSIDE_TRANSLATE = 3;
  -    private final static int STATE_INSIDE_TRANSLATE_TEXT = 4;
  -    private final static int STATE_TRANSLATE_KEY = 5;
  -    private final static int STATE_TRANSLATE_TEXT_KEY = 6;
  -    private final static int STATE_INSIDE_DATE = 7;
  -    private final static int STATE_INSIDE_NUMBER = 8;
  +    private static final int STATE_OUTSIDE = 0;
  +    private static final int STATE_INSIDE_TEXT = 1;
  +    private static final int STATE_INSIDE_PARAM = 2;
  +    private static final int STATE_INSIDE_TRANSLATE = 3;
  +    private static final int STATE_INSIDE_TRANSLATE_TEXT = 4;
  +    private static final int STATE_TRANSLATE_KEY = 5;
  +    private static final int STATE_TRANSLATE_TEXT_KEY = 6;
  +    private static final int STATE_INSIDE_DATE = 7;
  +    private static final int STATE_INSIDE_NUMBER = 8;
   
       /**
        * Current state of the transformer.
  @@ -175,7 +265,7 @@
        * Previous state.
        * Used to translate text inside params and translate elements.
        */
  -     private int prev_state = STATE_OUTSIDE;
  +    private int prev_state = STATE_OUTSIDE;
   
       /**
        * The i18n:key attribute is stored for the current element.
  @@ -224,10 +314,7 @@
       private String lang;
   
       /**
  -     * @todo Locale full support.
  -     * Do we really need it? We can use a combination of
  -     * language code and country code (en_US) instead. <br>
  -     * Also, different encodings can be specified: ru_RU_koi8
  +     * Locale setting.
        */
       private Locale locale;
   
  @@ -235,70 +322,148 @@
        * Date element attributes and their values.
        */
       private HashMap formattingParams;
  -
  -    public static Locale parseLocale(String locale) {
  -        StringTokenizer st = new StringTokenizer(locale, "_");
  -        String lang = null;
  -        String country = null;
  -        String variant = null;
  -        if (!st.hasMoreTokens()) {
  -            return Locale.ENGLISH;
  -        }
  -        else {
  -            lang = st.nextToken();
  -        }
   
  -        country = st.hasMoreTokens() ? st.nextToken() : "";
  -        variant = st.hasMoreTokens() ? st.nextToken() : "";
  +    /**
  +     * Dictionary data.
  +     */
  +    private XMLResourceBundle dictionary;
  +    private XMLResourceBundleFactory factory = new XMLResourceBundleFactory();
   
  -        return new Locale(lang, country, variant);
  -    }
  +    /*
  +     * i18n configuration variables
  +     */
  +    private String catalogueName;
  +    private String catalogueLocation;
  +    private String untranslated;
  +    private boolean cacheAtStartup;
  +
  +    /**
  +     * Configure this transformer.
  +     */
  +    public void configure(Configuration conf)
  +    throws ConfigurationException {
  +        if (conf != null) {
  +
  +            // read in the config options from the transformer definition
  +
  +            // obtain the base name of the message catalogue
  +            Configuration child = conf.getChild(I18N_CATALOGUE_NAME);
  +            catalogueName = child.getValue(null);
  +            debug("Default catalogue name is " + catalogueName);
  +
  +            // obtain the directory location of message catalogues
  +            child = conf.getChild(I18N_CATALOGUE_LOCATION);
  +            catalogueLocation = child.getValue(null);
  +            debug("Default catalogue location is " + catalogueLocation);
  +
  +            // check our mandatory parameters
  +            if (catalogueName == null || catalogueLocation == null)
  +	        throw new ConfigurationException(
  +		    "I18nTransformer requires the name and location of " +
  +		    "the message catalogues"
  +		);
  +
  +            // obtain default text to use for untranslated messages
  +            child = conf.getChild(I18N_UNTRANSLATED);
  +            untranslated = child.getValue(I18N_UNTRANSLATED);
  +            debug("Default untranslated text is '" + untranslated + "'");
  +
  +            // obtain config option, whether to cache messages at startup time
  +            child = conf.getChild(I18N_CACHE_STARTUP);
  +            cacheAtStartup = child.getValueAsBoolean(false);
  +            debug((cacheAtStartup ? "will" : "won't") +
  +                " cache messages during startup, by default"
  +            );
   
  -    public void setLocale(Locale locale) {
  -        this.locale = locale;
  -        this.lang = locale.getLanguage();
  +            // activate resource bundle logging
  +            factory.setLogger(getLogger());
  +        }
       }
   
       /**
  -     *  Uses <code>org.apache.cocoon.acting.LangSelect.getLang()</code>
  -     *  to get language user has selected. First it checks is lang set in
  -     *  objectModel.
  +     *  Uses <code>org.apache.cocoon.acting.LocaleAction.getLocale()</code>
  +     *  to get language user has selected. 
        */
       public void setup(SourceResolver resolver, Map objectModel, String source,
  -            Parameters parameters)
  -            throws ProcessingException, SAXException, IOException {
  +                      Parameters parameters)
  +    throws ProcessingException, SAXException, IOException {
   
  -        // Set current language and locale
  -        String lang = (String)(objectModel.get("lang"));
  -        if (lang == null) {
  -            lang = LangSelect.getLang(objectModel, parameters);
  -        }
  +        try {
   
  -        setLocale(parseLocale(lang));
  -        formatter.setLocale(locale);
  +            // Set current language and locale
  +            String lc = LocaleAction.getLocale(objectModel);
  +
  +            // configure the factory
  +            _setup(resolver);
   
  -        // FIXME (KP)
  -        // We need another way of specifying dictionaries, e.g.
  -        // <parameter name="dictionary.en" src="dict.xml" />
  -        // <parameter name="dictionary.de" src="dict_de.xml" />
  -        // etc.
  -        String translations_file = parameters.getParameter("src", null);
  -
  -        // FIXME (KP)
  -        // Add multiple dictionary support!
  -        initialiseDictionary(resolver.resolve(translations_file));
  +            // setup everything for the current locale
  +            String[] matches = new RE("_").split(lc);
  +
  +            String l = matches.length > 0
  +                       ? matches[0] : Locale.getDefault().getLanguage();
  +            String c = matches.length > 1 ? matches[1] : "";
  +            String v = matches.length > 2 ? matches[2] : "";
  +            Locale locale = new Locale(l, c, v);
  +
  +            debug("using locale " + locale.toString());
  +
  +            dictionary =
  +	        (XMLResourceBundle) factory.select(catalogueName, locale);
  +
  +            debug("selected dictionary " + dictionary);
  +
  +            setLocale(locale);
  +
  +        } catch(Exception e) {
  +            debug("exception generated, leaving unconfigured");
  +            throw new ProcessingException(e.getMessage(), e);
  +        }
       }
   
  +    /**
  +     * Internal setup.
  +     *
  +     * REVISIT: when we can get the resolver anywhere, we can pass the
  +     * configuration object directly to XMLResourceBundle.
  +     */
  +    private void _setup(SourceResolver resolver) throws Exception {
  +
  +        // configure the factory to log correctly and cache catalogues
  +        DefaultConfiguration configuration =
  +            new DefaultConfiguration("name", "location");
  +        DefaultConfiguration cacheConf =
  +            new DefaultConfiguration(
  +                XMLResourceBundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
  +                "location"
  +            );
  +        cacheConf.setValue(new Boolean(cacheAtStartup).toString());
  +        configuration.addChild(cacheConf);
  +
  +        // set the root location for message catalogues
  +        DefaultConfiguration dirConf =
  +            new DefaultConfiguration(
  +                XMLResourceBundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
  +                "location"
  +            );
  +        String cR =
  +            resolver.resolve(catalogueLocation).getFile().getCanonicalPath();
  +        dirConf.setValue(cR);
  +
  +        configuration.addChild(dirConf);
  +        factory.configure(configuration);
  +
  +        debug("configured");
  +    }
   
       public void compose(ComponentManager manager) {
           this.manager = manager;
       }
   
       public void startElement(String uri, String name, String raw,
  -            Attributes attr) throws SAXException {
  +                             Attributes attr) throws SAXException {
   
           if (I18N_NAMESPACE_URI.equals(uri)) {
  -            this.getLogger().debug("Starting i18n element: " + name);
  +            debug("Starting i18n element: " + name);
               startI18NElement(name, attr);
               return;
           }
  @@ -308,7 +473,7 @@
   
   
       public void endElement(String uri, String name, String raw)
  -            throws SAXException {
  +    throws SAXException {
   
           if (I18N_NAMESPACE_URI.equals(uri)) {
               endI18NElement(name);
  @@ -333,51 +498,47 @@
   
       private void startI18NElement(String name, Attributes attr)
       throws SAXException {
  -        this.getLogger().debug("Start i18n element: " + name);
  +        debug("Start i18n element: " + name);
           try {
               if (I18N_TEXT_ELEMENT.equals(name)) {
                   if (current_state != STATE_OUTSIDE
  -                    && current_state != STATE_INSIDE_PARAM
  -                    && current_state != STATE_INSIDE_TRANSLATE) {
  +                        && current_state != STATE_INSIDE_PARAM
  +                        && current_state != STATE_INSIDE_TRANSLATE) {
                       throw new SAXException(this.getClass().getName()
  -                        + ": nested i18n:text elements are not allowed. Current state: " + current_state);
  +                                           + ": nested i18n:text elements are not allowed. Current state: " + current_state);
                   }
                   prev_state = current_state;
                   current_state = STATE_INSIDE_TEXT;
                   current_key = attr.getValue(I18N_NAMESPACE_URI, I18N_KEY_ATTRIBUTE);
  -            }
  -            else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
  +            } else if (I18N_TRANSLATE_ELEMENT.equals(name)) {
                   if (current_state != STATE_OUTSIDE) {
                       throw new SAXException(this.getClass().getName()
  -                        + ": i18n:translate element must be used "
  -                        + "outside of other i18n elements. Current state: " + current_state);
  +                                           + ": i18n:translate element must be used "
  +                                           + "outside of other i18n elements. Current state: " + current_state);
                   }
                   current_state = STATE_INSIDE_TRANSLATE;
  -            }
  -            else if (I18N_PARAM_ELEMENT.equals(name)) {
  +            } else if (I18N_PARAM_ELEMENT.equals(name)) {
                   if (current_state != STATE_INSIDE_TRANSLATE) {
                       throw new SAXException(this.getClass().getName()
  -                        + ": i18n:param element can be used only inside "
  -                        + "i18n:translate element. Current state: " + current_state);
  +                                           + ": i18n:param element can be used only inside "
  +                                           + "i18n:translate element. Current state: " + current_state);
                   }
                   setFormattingParams(attr);
                   current_state = STATE_INSIDE_PARAM;
  -            }
  -            else if (I18N_DATE_ELEMENT.equals(name)) {
  +            } else if (I18N_DATE_ELEMENT.equals(name)) {
                   if (current_state != STATE_OUTSIDE) {
                       throw new SAXException(this.getClass().getName()
  -                        + ": i18n:date elements are not allowed "
  -                        + "inside of other i18n elements.");
  +                                           + ": i18n:date elements are not allowed "
  +                                           + "inside of other i18n elements.");
                   }
   
                   setFormattingParams(attr);
                   current_state = STATE_INSIDE_DATE;
  -            }
  -            else if (I18N_NUMBER_ELEMENT.equals(name)) {
  +            } else if (I18N_NUMBER_ELEMENT.equals(name)) {
                   if (current_state != STATE_OUTSIDE) {
                       throw new SAXException(this.getClass().getName()
  -                        + ": i18n:number elements are not allowed "
  -                        + "inside of other i18n elements.");
  +                                           + ": i18n:number elements are not allowed "
  +                                           + "inside of other i18n elements.");
                   }
   
                   setFormattingParams(attr);
  @@ -387,7 +548,7 @@
               // we need it to avoid further errors if an exception occurs
               current_state = STATE_OUTSIDE;
               throw new SAXException(this.getClass().getName()
  -                + ": error in format", e);
  +                                   + ": error in format", e);
           }
       }
   
  @@ -425,26 +586,31 @@
       }
   
       private void endI18NElement(String name) throws SAXException {
  -        this.getLogger().debug("End i18n element: " + name);
  +        debug("End i18n element: " + name);
           try {
               switch (current_state) {
  -                case STATE_INSIDE_TEXT: {
  +            case STATE_INSIDE_TEXT:
  +                {
                       endTextElement();
                       break;
                   }
  -                case STATE_INSIDE_TRANSLATE: {
  +            case STATE_INSIDE_TRANSLATE:
  +                {
                       endTranslateElement();
                       break;
                   }
  -                case STATE_INSIDE_PARAM: {
  +            case STATE_INSIDE_PARAM:
  +                {
                       endParamElement();
                       break;
                   }
  -                case STATE_INSIDE_DATE: {
  +            case STATE_INSIDE_DATE:
  +                {
                       endDateElement();
                       break;
                   }
  -                case STATE_INSIDE_NUMBER: {
  +            case STATE_INSIDE_NUMBER:
  +                {
                       endNumberElement();
                       break;
                   }
  @@ -453,7 +619,7 @@
               // we need it to avoid further errors if an exception occurs
               current_state = STATE_OUTSIDE;
               throw new SAXException(this.getClass().getName()
  -                + ": error in format", e);
  +                                   + ": error in format", e);
           }
       }
   
  @@ -472,70 +638,83 @@
       }
   
       private void i18nCharacters(char[] ch, int start, int len)
  -    throws SAXException{
  +    throws SAXException {
   
  -        String text2translate = new String(ch, start, len);
  -        text2translate = stripWhitespace(text2translate);
  -        if (text2translate == null || text2translate.length() == 0) {
  +        String key = new String(ch, start, len);
  +        key = stripWhitespace(key);
  +        if (key == null || key.length() == 0) {
               return;
           }
   
  -        this.getLogger().debug("Text 2 translate: '" + text2translate + "'");
  +        debug("i18n message key = '" + key + "'");
   
           switch (current_state) {
  -            case STATE_INSIDE_TEXT: {
  +        case STATE_INSIDE_TEXT:
  +            {
                   if (current_key != null) {
  -                    translated_text = (String)(dictionary.get(current_key));
  +                    try {
  +                        translated_text = getString(current_key);
  +                    } catch (MissingResourceException e) {
  +                        translated_text = untranslated;
  +                    }
                       if (translated_text == null) {
  -                        translated_text = text2translate;
  +                        translated_text = key;
                       }
                       current_key = null;
  -                }
  -                else {
  -                    translated_text = (String)(dictionary.get(text2translate));
  +                } else {
  +                    try {
  +                        translated_text = getString(key);
  +                    } catch (MissingResourceException e) {
  +                        translated_text = untranslated;
  +                    }
                   }
   
                   break;
               }
  -            case STATE_INSIDE_TRANSLATE: {
  +        case STATE_INSIDE_TRANSLATE:
  +            {
                   // Store text for param substitution (do not translate)
                   if (substitute_text == null) {
  -                    substitute_text = text2translate;
  +                    substitute_text = key;
                   }
                   break;
               }
  -            case STATE_INSIDE_PARAM: {
  +        case STATE_INSIDE_PARAM:
  +            {
                   // Store translation for param substitution
                   if (param_value == null) {
  -                    param_value = text2translate;
  +                    param_value = key;
                   }
                   break;
               }
  -            case STATE_INSIDE_DATE: {
  +        case STATE_INSIDE_DATE:
  +            {
                   if (formattingParams != null) {
                       if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null) {
  -                        formattingParams.put(I18N_VALUE_ATTRIBUTE, text2translate);
  -                    }
  -                    else {
  +                        formattingParams.put(I18N_VALUE_ATTRIBUTE, key);
  +                    } else {
                           // how to use the text inside of date element?
                       }
  +
                   }
                   break;
               }
  -            case STATE_INSIDE_NUMBER: {
  +        case STATE_INSIDE_NUMBER:
  +            {
                   if (formattingParams != null) {
                       if (formattingParams.get(I18N_PATTERN_ATTRIBUTE) == null) {
  -                        formattingParams.put(I18N_PATTERN_ATTRIBUTE, text2translate);
  -                    }
  -                    else {
  +                        formattingParams.put(I18N_PATTERN_ATTRIBUTE, key);
  +                    } else {
                           // how to use the text inside of number element?
                       }
  +
                   }
                   break;
               }
  -            default: {
  +        default:
  +            {
                   throw new SAXException(this.getClass().getName()
  -                    + "Something's really wrong!!!");
  +                                       + "Something's really wrong!!!");
               }
           }
       }
  @@ -560,25 +739,29 @@
               // remove the i18n:attr attribute - we don't need it
               temp_attr.removeAttribute(i18n_attr_index);
               while (st.hasMoreElements()) {
  -            // translate all listed attributes
  +                // translate all listed attributes
                   String attr_name = st.nextToken();
                   int attr_index = temp_attr.getIndex(attr_name);
   
                   if (attr_index != -1) {
                       String text2translate = temp_attr.getValue(attr_index);
  -                    String result = (String)(dictionary.get(text2translate));
  +                    String result;
  +
  +                    try {
  +                        result = getString(text2translate);
  +                    } catch (MissingResourceException e) {
  +                        result = untranslated;
  +                    }
                       // set the translated value
                       if (result != null) {
                           temp_attr.setValue(attr_index, result);
  -                    }
  -                    else {
  +                    } else {
                           getLogger().warn("translation not found for attribute "
  -                        + attr_name + " in element: " + name);
  +                                         + attr_name + " in element: " + name);
                       }
  -                }
  -                else {
  +                } else {
                       getLogger().warn("i18n attribute '" + attr_name
  -                        + "' not found in element: " + name);
  +                                     + "' not found in element: " + name);
                   }
               }
               return temp_attr;
  @@ -588,25 +771,27 @@
       }
   
       private void endTextElement() throws SAXException {
  -        this.getLogger().debug("End text element, translated_text: " + translated_text);
  +        debug("End text element, translated_text: " + translated_text);
           switch (prev_state) {
  -            case STATE_OUTSIDE: {
  +        case STATE_OUTSIDE:
  +            {
                   // simply translate text (key translation already performed)
                   if (translated_text != null) {
                       super.contentHandler.characters(translated_text.toCharArray(),
  -                        0, translated_text.length());
  -                }
  -                else {
  -                 // else - translation not found
  -                    this.getLogger().debug("--- Translation not found! ---");
  +                                                    0, translated_text.length());
  +                } else {
  +                    // else - translation not found
  +                    debug("--- Translation not found! ---");
                   }
                   break;
               }
  -            case STATE_INSIDE_TRANSLATE: {
  +        case STATE_INSIDE_TRANSLATE:
  +            {
                   substitute_text = translated_text;
                   break;
               }
  -            case STATE_INSIDE_PARAM: {
  +        case STATE_INSIDE_PARAM:
  +            {
                   param_value = translated_text;
                   break;
               }
  @@ -617,27 +802,26 @@
       }
   
       private void endParamElement() throws SAXException {
  -        this.getLogger().debug("Substitution param: " + param_value);
  +        debug("Substitution param: " + param_value);
           if (formattingParams != null) {
               String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
               if (paramType != null) {
  -                this.getLogger().debug("Param type: " + paramType);
  +                debug("Param type: " + paramType);
                   if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null
  -                    && param_value != null) {
  -                    this.getLogger().debug("Put param value: " + param_value);
  +                        && param_value != null) {
  +                    debug("Put param value: " + param_value);
                       formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
                   }
                   if ("date".equals(paramType)) {
  -                    this.getLogger().debug("Formatting date param: " + formattingParams);
  +                    debug("Formatting date param: " + formattingParams);
                       param_value = formatDate(formattingParams);
  -                }
  -                else if ("number".equals(paramType)) {
  -                    this.getLogger().debug("Formatting number param: " + formattingParams);
  +                } else if ("number".equals(paramType)) {
  +                    debug("Formatting number param: " + formattingParams);
                       param_value = formatNumber(formattingParams);
                   }
               }
           }
  -        this.getLogger().debug("Added substitution param: " + param_value);
  +        debug("Added substitution param: " + param_value);
           indexedParams.add(param_value);
           param_value = null;
           current_state = STATE_INSIDE_TRANSLATE;
  @@ -651,11 +835,10 @@
   
           String result;
           if (indexedParams.size() > 0 && substitute_text.length() > 0) {
  -            this.getLogger().debug("Text for susbtitution: " + substitute_text);
  +            debug("Text for susbtitution: " + substitute_text);
               result = formatter.format(substitute_text, indexedParams.toArray());
  -            this.getLogger().debug("Result of susbtitution: " + result);
  -        }
  -        else {
  +            debug("Result of susbtitution: " + result);
  +        } else {
               result = substitute_text;
           }
   
  @@ -674,7 +857,7 @@
       private String formatDate(Map params) throws SAXException {
           if (params == null) {
               throw new SAXException(this.getClass().getName()
  -                + ": i18n:date - error in element attributes.");
  +                                   + ": i18n:date - error in element attributes.");
           }
           // from pattern
           String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
  @@ -694,7 +877,7 @@
   
           // result pattern is localized
           SimpleDateFormat to_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
  -            DateFormat.DEFAULT, DateFormat.DEFAULT, locale);
  +                                      DateFormat.DEFAULT, DateFormat.DEFAULT, locale);
           if (pattern != null) {
               to_fmt.applyPattern(pattern);
           }
  @@ -702,19 +885,18 @@
           // get current date and time by default
           if (value == null) {
               dateValue = new Date();
  -        }
  -        else {
  +        } else {
               try {
                   dateValue = from_fmt.parse(value);
               } catch (ParseException pe) {
                   throw new SAXException(this.getClass().getName()
  -                    + "i18n:date - parsing error.", pe);
  +                                       + "i18n:date - parsing error.", pe);
               }
           }
   
           // we have all necessary data here: do formatting.
           String result = to_fmt.format(dateValue);
  -        this.getLogger().debug("i18n:date result: " + result);
  +        debug("i18n:date result: " + result);
           return result;
       }
   
  @@ -727,7 +909,7 @@
       private String formatNumber(Map params) throws SAXException {
           if (params == null) {
               throw new SAXException(this.getClass().getName()
  -                + ": i18n:number - error in element attributes.");
  +                                   + ": i18n:number - error in element attributes.");
           }
           // from pattern
           String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
  @@ -751,11 +933,9 @@
           DecimalFormat to_fmt = null;
           if (subType == null) {
               to_fmt = (DecimalFormat)NumberFormat.getInstance(locale);
  -        }
  -        else if (subType.equals("currency")) {
  +        } else if (subType.equals("currency")) {
               to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(locale);
  -        }
  -        else if (subType.equals("percent")) {
  +        } else if (subType.equals("percent")) {
               to_fmt = (DecimalFormat)NumberFormat.getPercentInstance(locale);
           }
           if (pattern != null) {
  @@ -765,118 +945,46 @@
           // get current date and time by default
           if (value == null) {
               numberValue = new Long(0);
  -        }
  -        else {
  +        } else {
               try {
                   numberValue = from_fmt.parse(value);
               } catch (ParseException pe) {
                   throw new SAXException(this.getClass().getName()
  -                    + "i18n:number - parsing error.", pe);
  +                                       + "i18n:number - parsing error.", pe);
               }
           }
   
           // we have all necessary data here: do formatting.
           String result = to_fmt.format(numberValue);
  -        this.getLogger().debug("i18n:number result: " + result);
  +        debug("i18n:number result: " + result);
           return result;
       }
   
       /**
  -     * Gets translations from xml file to dictionary.
  +     * Helper method to retrieve a message from the dictionary
        */
  -    class I18nContentHandler extends DefaultHandler {
  -        boolean in_entry = false;
  -        boolean in_key = false;
  -        boolean in_translation = false;
  -
  -        String key = null;
  -        String translation = null;
  -
  -
  -        public void startElement(String namespace, String name, String raw,
  -                Attributes attr) throws SAXException {
  -
  -            if (name.equals(I18N_ENTRY_ELEMENT)) {
  -                in_entry = true;
  -            } else {
  -                if (in_entry) {
  -                    if (name.equals(I18N_KEY_ELEMENT)) {
  -                        in_key = true;
  -                    } else {
  -                        if (name.equals(I18N_TRANSLATION_ELEMENT)
  -                                && attr.getValue(I18N_LANG).equals(lang)) {
  -                            in_translation = true;
  -                        }
  -                    }
  -                }
  -            }
  -        }
  -
  -
  -        public void endElement(String namespace, String name, String raw)
  -                throws SAXException {
  -
  -            if (name.equals(I18N_ENTRY_ELEMENT)) {
  -                if (key != null && translation != null) {
  -                    dictionary.put(key, translation);
  -                    key = null;
  -                    translation = null;
  -                }
  -                in_entry = false;
  -            } else if (name.equals(I18N_KEY_ELEMENT)) {
  -                in_key = false;
  -            } else {
  -                if (name.equals(I18N_TRANSLATION_ELEMENT)) {
  -                    in_translation = false;
  -                }
  -            }
  -
  -        }
  -
  -
  -        public void characters(char[] ary, int start, int length)
  -                throws SAXException {
  -            if (in_key) {
  -                key = new String(ary, start, length);
  -
  -            } else {
  -                if (in_translation) {
  -                    translation = new String(ary, start, length);
  -                }
  -            }
  -        }
  +    private String getString(String key) {
   
  +    	return dictionary.getString(
  +		I18N_CATALOGUE_PREFIX + "[@key='" + key + "']"
  +	);
       }
   
  +    private void setLocale(Locale locale) {
  +        this.locale = locale;
  +        lang = locale.getLanguage();
  +        formatter.setLocale(locale);
  +    }
   
       /**
  -     *Loads translations from given URL
  +     * Helper method to debug messages
        */
  -    private void initialiseDictionary(Source inputSource)
  -    throws ProcessingException, SAXException, MalformedURLException, IOException {
  -
  -
  -        try
  -        {
  -            // How this could be cached?
  -            this.dictionary = new HashMap();
  -            I18nContentHandler i18n_handler = new I18nContentHandler();
  -            inputSource.stream(i18n_handler);
  -        } catch(SAXException e) {
  -            getLogger().error("Error in initialiseDictionary", e);
  -            throw e;
  -        } catch(MalformedURLException e) {
  -            getLogger().error("Error in initialiseDictionary", e);
  -            throw e;
  -        } catch(IOException e) {
  -            getLogger().error("Error in initialiseDictionary", e);
  -            throw e;
  -        }
  +    private void debug(String msg) {
  +    	getLogger().debug("I18nTransformer: " + msg);
       }
   
       /**
        *
  -     */
       static public void main(String[] args) {
   
           Locale locale = null;
  @@ -885,8 +993,8 @@
           for (int i = 0; i < locales.length; i++) {
               locale = locales[i];
               SimpleDateFormat fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
  -                DateFormat.DEFAULT, DateFormat.DEFAULT, locale
  -            );
  +                                       DateFormat.DEFAULT, DateFormat.DEFAULT, locale
  +                                   );
   
               String localized = fmt.format(new Date());
   
  @@ -894,13 +1002,14 @@
               String money = n_fmt.format(1210.5);
   
               System.out.println("Locale ["
  -                + locale.getLanguage() + ", "
  -                + locale.getCountry() + ", "
  -                + locale.getVariant() + "] : "
  -                + locale.getDisplayName()
  -                + " \t Date: " + localized
  -                + " \t Money: " + money);
  +                               + locale.getLanguage() + ", "
  +                               + locale.getCountry() + ", "
  +                               + locale.getVariant() + "] : "
  +                               + locale.getDisplayName()
  +                               + " \t Date: " + localized
  +                               + " \t Money: " + money);
           }
       }
  +     */
   
   }
  
  
  
  1.37      +4 -1      xml-cocoon2/webapp/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/sitemap.xmap,v
  retrieving revision 1.36
  retrieving revision 1.37
  diff -u -r1.36 -r1.37
  --- sitemap.xmap	2001/07/18 12:14:18	1.36
  +++ sitemap.xmap	2001/07/19 11:22:21	1.37
  @@ -29,7 +29,10 @@
      <map:transformer     name="log"       src="org.apache.cocoon.transformation.LogTransformer"/>
      <map:transformer     name="sql"       src="org.apache.cocoon.transformation.SQLTransformer"/>
      <map:transformer     name="extractor" src="org.apache.cocoon.transformation.FragmentExtractorTransformer"/>
  -   <map:transformer     name="i18n"      src="org.apache.cocoon.transformation.I18nTransformer"/>
  +   <map:transformer     name="i18n"      src="org.apache.cocoon.transformation.I18nTransformer">
  +    <catalogue-name>messages</catalogue-name>
  +    <catalogue-location>translations</catalogue-location>
  +   </map:transformer>
      <map:transformer     name="xinclude"  src="org.apache.cocoon.transformation.XIncludeTransformer"/>
      <map:transformer     name="cinclude"  src="org.apache.cocoon.transformation.CIncludeTransformer"/>
      <map:transformer     name="filter"  src="org.apache.cocoon.transformation.FilterTransformer"/>
  
  
  
  1.5       +12 -12    xml-cocoon2/webapp/i18n/simple.xml
  
  Index: simple.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/i18n/simple.xml,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- simple.xml	2001/06/15 15:01:09	1.4
  +++ simple.xml	2001/07/19 11:22:21	1.5
  @@ -1,13 +1,13 @@
   <?xml version="1.0" encoding="UTF-8"?>
   <root xmlns:i18n="http://apache.org/cocoon/i18n/2.0">
   	<title>
  -		<i18n:text>Hello, internationalization!</i18n:text> 
  +		<i18n:text>titletext</i18n:text> 
   	</title>
   	<sub-title>
   		<i18n:date pattern="EEE, MMMM dd, yyyy zzz" />
   	</sub-title>	
   	<annotation>
  -		<i18n:text>Documentation link:</i18n:text> 
  +		<i18n:text>doclink</i18n:text> 
   		<link>
   			<href>http://xml.apache.org/cocoon2/i18n.html</href>
   			<title>Cocoon 2 Web Site</title>
  @@ -19,7 +19,7 @@
   		</item>
   		<item>
   			<link>
  -				<href>?lang=<i18n:text>lang_id1</i18n:text></href>
  +				<href>simple.xml?locale=<i18n:text>lang_id1</i18n:text></href>
   				<title>
   					<i18n:text>language1</i18n:text>
   				</title>
  @@ -27,7 +27,7 @@
   		</item>
   		<item>
   			<link>
  -				<href>?lang=<i18n:text>lang_id2</i18n:text></href>
  +				<href>simple.xml?locale=<i18n:text>lang_id2</i18n:text></href>
   				<title>
   					<i18n:text>language2</i18n:text>
   				</title>
  @@ -35,7 +35,7 @@
   		</item>
   		<item>
   			<link>
  -				<href>?lang=<i18n:text>lang_id3</i18n:text></href>
  +				<href>simple.xml?locale=<i18n:text>lang_id3</i18n:text></href>
   				<title>
   					<i18n:text>language3</i18n:text>
   				</title>
  @@ -43,7 +43,7 @@
   		</item>		
   		<item>
   			<link>
  -				<href>?lang=<i18n:text>lang_id4</i18n:text></href>
  +				<href>simple.xml?locale=<i18n:text>lang_id4</i18n:text></href>
   				<title>
   					<i18n:text>language4</i18n:text>
   				</title>
  @@ -51,7 +51,7 @@
   		</item>				
   		<item>
   			<link>
  -				<href>?lang=<i18n:text>lang_id5</i18n:text></href>
  +				<href>simple.xml?locale=<i18n:text>lang_id5</i18n:text></href>
   				<title>
   					<i18n:text>language5</i18n:text>
   				</title>
  @@ -61,25 +61,25 @@
   	<menu>
   		<item>
   			<link>
  -				<href>?lang=en_US</href>
  +				<href>simple.xml?locale=en_US</href>
   				<title>English (US)</title>
   			</link>
   		</item>
   		<item>
   			<link>
  -				<href>?lang=en_GB</href>
  +				<href>simple.xml?locale=en_GB</href>
   				<title>English (GB)</title>
   			</link>
   		</item>		
   		<item>
   			<link>
  -				<href>?lang=ru_RU</href>
  +				<href>simple.xml?locale=ru_RU</href>
   				<title>Russian (Russia)</title>
   			</link>
   		</item>		
   		<item>
   			<link>
  -				<href>?lang=de_AT_EURO</href>
  +				<href>simple.xml?locale=de_AT_EURO</href>
   				<title>German (Austria, Euro)</title>
   			</link>
   		</item>
  @@ -93,7 +93,7 @@
   		</para>
   		<para title="third" name="article" i18n:attr="title name">
   			<i18n:translate>
  -				<i18n:text>Hello, {0}! Glad to see you!</i18n:text>
  +				<i18n:text>Hello</i18n:text>
   				<i18n:param name="username">
   					<i18n:text>Kot</i18n:text>
   				</i18n:param>
  
  
  
  1.7       +13 -17    xml-cocoon2/webapp/i18n/simple.xsp
  
  Index: simple.xsp
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/i18n/simple.xsp,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- simple.xsp	2001/06/15 18:08:04	1.6
  +++ simple.xsp	2001/07/19 11:22:21	1.7
  @@ -14,19 +14,15 @@
   			count++;
   		}
   	
  -		Locale loc = null;
  -		String lang = <xsp-request:get-attribute name="lang"/>;
  -		if (lang != null) {
  -			loc = org.apache.cocoon.transformation.I18nTransformer.parseLocale(lang);
  -		}
  -			
  +		Locale loc = request.getLocale();
  +
   		SimpleDateFormat df = new SimpleDateFormat("EEEE, MMMM dd, yyyy H:mm:ss", loc);
   	</xsp:logic>
   		<title>
  -			<i18n:text>Hello, internationalization!</i18n:text>
  +			<i18n:text>titletext</i18n:text>
   		</title>
   		<annotation>
  -			<i18n:text>Documentation link:</i18n:text>
  +			<i18n:text>doclink</i18n:text>
   			<link>
   				<href>http://xml.apache.org/cocoon2/i18n.html</href>
   				<title>Cocoon 2 Web Site</title>
  @@ -51,7 +47,7 @@
   			</item>
   			<item>
   				<link>
  -					<href>?lang=<i18n:text>lang_id1</i18n:text></href>
  +					<href>simple.xsp?locale=<i18n:text>lang_id1</i18n:text></href>
   					<title>
   						<i18n:text>language1</i18n:text>
   					</title>
  @@ -59,7 +55,7 @@
   			</item>
   			<item>
   				<link>
  -					<href>?lang=<i18n:text>lang_id2</i18n:text></href>
  +					<href>simple.xsp?locale=<i18n:text>lang_id2</i18n:text></href>
   					<title>
   						<i18n:text>language2</i18n:text>
   					</title>
  @@ -67,7 +63,7 @@
   			</item>
   			<item>
   				<link>
  -					<href>?lang=<i18n:text>lang_id3</i18n:text></href>
  +					<href>simple.xsp?locale=<i18n:text>lang_id3</i18n:text></href>
   					<title>
   						<i18n:text>language3</i18n:text>
   					</title>
  @@ -75,7 +71,7 @@
   			</item>
   			<item>
   				<link>
  -					<href>?lang=<i18n:text>lang_id4</i18n:text></href>
  +					<href>simple.xsp?locale=<i18n:text>lang_id4</i18n:text></href>
   					<title>
   						<i18n:text>language4</i18n:text>
   					</title>
  @@ -83,7 +79,7 @@
   			</item>
   			<item>
   				<link>
  -					<href>?lang=<i18n:text>lang_id5</i18n:text></href>
  +					<href>simple.xsp?locale=<i18n:text>lang_id5</i18n:text></href>
   					<title>
   						<i18n:text>language5</i18n:text>
   					</title>
  @@ -93,25 +89,25 @@
   		<menu>
   			<item>
   				<link>
  -					<href>?lang=en_US</href>
  +					<href>simple.xsp?locale=en_US</href>
   					<title>English (US)</title>
   				</link>
   			</item>
   			<item>
   				<link>
  -					<href>?lang=en_GB</href>
  +					<href>simple.xsp?locale=en_GB</href>
   					<title>English (GB)</title>
   				</link>
   			</item>		
   			<item>
   				<link>
  -					<href>?lang=ru_RU</href>
  +					<href>simple.xsp?locale=ru_RU</href>
   					<title>Russian (Russia)</title>
   				</link>
   			</item>					
   			<item>
   				<link>
  -					<href>?lang=de_AT_EURO</href>
  +					<href>simple.xsp?locale=de_AT_EURO</href>
   					<title>German (Austria, Euro)</title>
   				</link>
   			</item>			
  
  
  
  1.7       +7 -32     xml-cocoon2/webapp/i18n/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/i18n/sitemap.xmap,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- sitemap.xmap	2001/06/15 18:08:05	1.6
  +++ sitemap.xmap	2001/07/19 11:22:21	1.7
  @@ -3,52 +3,27 @@
   	<!-- =========================== Components ================================ -->
   	<map:components>
   		<map:generators default="file"/>
  -		<map:transformers default="xslt">
  -			<map:transformer name="i18n" src="org.apache.cocoon.transformation.I18nTransformer">
  -				<map:parameter name="available_lang_1" value="en"/>
  -				<map:parameter name="available_lang_2" value="ru"/>
  -				<map:parameter name="available_lang_3" value="de"/>
  -				<map:parameter name="available_lang_4" value="pl"/>
  -				<map:parameter name="available_lang_5" value="sp"/>
  -				<map:parameter name="available_lang_6" value="hy"/>
  -			</map:transformer>
  -		</map:transformers>
  +		<map:transformers default="xslt"/>
   		<map:readers default="resource"/>
   		<map:serializers default="html"/>
   		<map:selectors default="browser"/>
   		<map:matchers default="wildcard">
   			<map:matcher name="wildcard" src="org.apache.cocoon.matching.WildcardURIMatcherFactory"/>
   		</map:matchers>
  -		<map:actions>
  -			<map:action name="lang-select" src="org.apache.cocoon.acting.LangSelect">
  -				<store-in-session>true</store-in-session>
  -				<create-session>true</create-session>
  -				<store-in-cookie>true</store-in-cookie>
  -				<store-in-request>true</store-in-request>
  -			</map:action>
  -		</map:actions>
   
   	</map:components>
   	<!-- =========================== Pipelines ================================= -->
   	<map:pipelines>
   		<map:pipeline>
  -			<map:match pattern="**.xml">
  -				<map:act type="lang-select">
  -				<map:generate src="{../1}.xml"/>
  -				<map:transform type="i18n">
  -					<map:parameter name="src" value="translations/{../1}_dict.xml"/>
  -				</map:transform>
  -				</map:act>
  +			<map:match pattern="*.xml">
  +				<map:generate src="{1}.xml"/>
  +				<map:transform type="i18n"/>
   				<map:transform src="simple.xsl"/>
   				<map:serialize/>
   			</map:match>
  -			<map:match pattern="**.xsp">
  -				<map:act type="lang-select">
  -				<map:generate type="serverpages" src="{../1}.xsp"/>
  -				<map:transform type="i18n">
  -					<map:parameter name="src" value="translations/{../1}_dict.xml"/>
  -				</map:transform>
  -				</map:act>
  +			<map:match pattern="*.xsp">
  +				<map:generate type="serverpages" src="{1}.xsp"/>
  +				<map:transform type="i18n"/>
   				<map:transform src="simple.xsl"/>
   				<map:serialize/>
   			</map:match>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/convert.xsl
  
  Index: convert.xsl
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <xsl:output indent="yes" method="xml"/>
  
  <!-- specify here the language of the catalogue you would like to create -->
  <xsl:param name="lang">hy</xsl:param>
  
  <xsl:template match="translations">
  	<catalogue xml:lang="{$lang}">
  		<xsl:apply-templates select="entry"/>
  	</catalogue>
  </xsl:template>
  
  <xsl:template match="entry">
  	<message key="{key}">
  		<xsl:value-of select="translation[@lang=$lang]"/>
  	</message>
  </xsl:template>
  
  </xsl:stylesheet>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages.xml
  
  Index: messages.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- Default English message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="en">
  	<message key="a_key">This is a key value.</message>
  	<message key="lang_id1">ru</message>
  	<message key="lang_id2">de</message>
  	<message key="lang_id3">pl</message>
  	<message key="lang_id4">es</message>
  	<message key="lang_id5">hy</message>
  	<message key="language">English</message>
  	<message key="language1">Russian</message>
  	<message key="language2">German</message>
  	<message key="language3">Polish</message>
  	<message key="language4">Spanish</message>
  	<message key="language5">Armenian</message>
  	<message key="titletext">Hello, internationalization!</message>
  	<message key="doclink">See i18n documentation for details:</message>
  	<message key="first">First</message>
  	<message key="second">Second</message>
  	<message key="third">Third</message>
  	<message key="forth">Forth</message>
  	<message key="article">Article</message>
  	<message key="article_text1">This is a i18n paragraph.</message>
  	<message key="article_text2">This is another i18n paragraph and is also a cool one.</message>
  	<message key="copyright">Copyright © 2001 Konstantin Piroumian. No rights are reserved.</message>
  	<message key="Hello">Hello, {0}! Glad to see you!</message>
  	<message key="Kot">Tomcat</message>
  	<message key="none">None</message>
  	<message key="one">one</message>
  	<message key="two">two</message>
  	<message key="count_title">This page was accessed {0} times. Last at: {1}.</message>
  </catalogue>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages_de.xml
  
  Index: messages_de.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages_de.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- German message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="de">
  	<message key="count_title">Diese Seite wurde {0}mal aufgerufen. Letzter Aufruf: {1}.</message>
  	<message key="a_key">Dies ist der Wert eines Schlüssels.</message>
  	<message key="lang_id1">en</message>
  	<message key="lang_id2">ru</message>
  	<message key="lang_id3">pl</message>
  	<message key="lang_id4">es</message>
  	<message key="lang_id5">hy</message>
  	<message key="language">Deutsch</message>
  	<message key="language1">Englische</message>
  	<message key="language2">Russe</message>
  	<message key="language3">Polnisch</message>
  	<message key="language4">Spanisch</message>
  	<message key="language5">Armenier</message>
  	<message key="titletext">Herzlich willkommen, Internationalisierung!</message>
  	<message key="doclink">Näheres unter der i18n Dokumentation:</message>
  	<message key="first">Erstens</message>
  	<message key="second">Zweitens</message>
  	<message key="third">Drittens</message>
  	<message key="forth">Viertens</message>
  	<message key="article">Artikel</message>
  	<message key="article_text1">Dies ist ein Absatz nach i18n.</message>
  	<message key="article_text2">Dies ist ein weiterer Absatz nach i18n und auch noch ein ziemlich cooler dazu.</message>
  	<message key="copyright">Copyright © 2001 Konstantin Piroumian. Deutsche Übersetzung von Jörg Prante.</message>
  	<message key="Hello, {0}! Glad to see you!">Hallo {0}! Schön, dich zu sehen!</message>
  	<message key="Kot">Tomcat</message>
  	<message key="none">nichts</message>
  	<message key="one">eins</message>
  	<message key="two">zwei</message>
  </catalogue>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages_en.xml
  
  Index: messages_en.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages_en.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- English message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="en">
  	<message key="a_key">This is a key value.</message>
  	<message key="lang_id1">ru</message>
  	<message key="lang_id2">de</message>
  	<message key="lang_id3">pl</message>
  	<message key="lang_id4">es</message>
  	<message key="lang_id5">hy</message>
  	<message key="language">English</message>
  	<message key="language1">Russian</message>
  	<message key="language2">German</message>
  	<message key="language3">Polish</message>
  	<message key="language4">Spanish</message>
  	<message key="language5">Armenian</message>
  	<message key="titletext">Hello, internationalization!</message>
  	<message key="doclink">See i18n documentation for details:</message>
  	<message key="first">First</message>
  	<message key="second">Second</message>
  	<message key="third">Third</message>
  	<message key="forth">Forth</message>
  	<message key="article">Article</message>
  	<message key="article_text1">This is a i18n paragraph.</message>
  	<message key="article_text2">This is another i18n paragraph and is also a cool one.</message>
  	<message key="copyright">Copyright © 2001 Konstantin Piroumian. No rights are reserved.</message>
  	<message key="Hello">Hello, {0}! Glad to see you!</message>
  	<message key="Kot">Tomcat</message>
  	<message key="none">None</message>
  	<message key="one">one</message>
  	<message key="two">two</message>
  	<message key="count_title">This page was accessed {0} times. Last at: {1}.</message>
  </catalogue>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages_es.xml
  
  Index: messages_es.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages_es.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- Spanish message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="es">
  	<message key="count_title">Esta página fue tenida acceso {0} veces. Pasado en: {1}.</message>
  	<message key="a_key">Esto es un valor clave.</message>
  	<message key="lang_id1">en</message>
  	<message key="lang_id2">ru</message>
  	<message key="lang_id3">de</message>
  	<message key="lang_id4">pl</message>
  	<message key="lang_id5">hy</message>
  	<message key="language">Español</message>
  	<message key="language1">Inglés</message>
  	<message key="language2">Ruso</message>
  	<message key="language3">Alemán</message>
  	<message key="language4">Polaco</message>
  	<message key="language5">Armenio</message>
  	<message key="titletext">¡¡Hola!, internacionalización!</message>
  	<message key="doclink">Visto la documentación i18n para detalles:</message>
  	<message key="first">Primero</message>
  	<message key="second">Segundo</message>
  	<message key="third">Tercio</message>
  	<message key="forth">En adelante</message>
  	<message key="article">Artículo</message>
  	<message key="article_text1">Esto es un párrafo i18n.</message>
  	<message key="article_text2">Esto es otro párrafo i18n y es también uno 'cool'.</message>
  	<message key="copyright">Copyright © 2001 Konstantin Piroumian. Ningunos derechos son reservados.</message>
  	<message key="Hello, {0}! Glad to see you!">¡¡Hola!, {0}! ¡Alegre de verle!</message>
  	<message key="Kot">Gato</message>
  	<message key="none">Ninguno</message>
  	<message key="one">un</message>
  	<message key="two">dos</message>
  </catalogue>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages_hy.xml
  
  Index: messages_hy.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages_hy.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- Armenian message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="hy">
  	<message key="count_title">²Ûë ¿çÁ ³Ûó»É»É »Ý {0} ³Ý·³Ù. ì»ñçÇÝÁ {1}.</message>
  	<message key="a_key">ê³ µ³Ý³ÉÇÇ ³éÅ»ùÝ ¿£</message>
  	<message key="lang_id1">en</message>
  	<message key="lang_id2">ru</message>
  	<message key="lang_id3">de</message>
  	<message key="lang_id4">pl</message>
  	<message key="lang_id5">es</message>
  	<message key="language">гۻñ»Ý</message>
  	<message key="language1">²Ý·É»ñ»Ý</message>
  	<message key="language2">èáõë»ñ»Ý</message>
  	<message key="language3">¶»ñٳݻñ»Ý</message>
  	<message key="language4">Ȼѻñ»Ý</message>
  	<message key="language5">Æëå³Ý»ñ»Ý</message>
  	<message key="titletext">´³ñ¢°, ÇÝï»ñݳóÛáݳÉáõÃÛáõÝ£</message>
  	<message key="doclink">سÝñ³Ù³ë µ³ó³ïñáõÃÛ³Ý Ñ³Ù³ñ ݳÇñª</message>
  	<message key="first">²é³çÇÝ</message>
  	<message key="second">ºñÏñáñ¹</message>
  	<message key="third">ºññáñ¹</message>
  	<message key="forth">¼áñáñ¹</message>
  	<message key="article">Ðá¹í³Í</message>
  	<message key="article_text1">ê³ ÇÝï»ñݳóÛáÝ³É å³ñ³·ñ³ý ¿£</message>
  	<message key="article_text2">ê³ ÙÇ áõñÇß ÇÝï»ñݳóÛáÝ³É å³ñ³·ñ³ý ¿, ¢ ÝáõÛÝ å»ë ó»Ýïñ£</message>
  	<message key="copyright">Copyright © 2001 Konstantin Piroumian. àãÇÝã ãÇ å³Ñå³Ýí³Í£</message>
  	<message key="Hello, {0}! Glad to see you!">´³ñ¢¯ {0}: àõñ³Ë »Ù ù»½ ï»ëݻɣ</message>
  	<message key="Kot">γïáõ</message>
  	<message key="none">àã áù</message>
  	<message key="one">Ù»Ï</message>
  	<message key="two">»ñÏáõë</message>
  </catalogue>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages_pl.xml
  
  Index: messages_pl.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages_pl.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- Polish message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="pl">
  	<message key="count_title">Ta strona była pobierana {0} razy. Ostatnio {1}</message>
  	<message key="a_key">To jest klucz.</message>
  	<message key="lang_id1">en</message>
  	<message key="lang_id2">ru</message>
  	<message key="lang_id3">de</message>
  	<message key="lang_id4">es</message>
  	<message key="lang_id5">hy</message>
  	<message key="language">Polski</message>
  	<message key="language1">Angielski</message>
  	<message key="language2">Rosyjski</message>
  	<message key="language3">Niemiecki</message>
  	<message key="language4">Hiszpañski</message>
  	<message key="language5">Armeñski</message>
  	<message key="titletext">Witam, oto przykład wielojęzycznej strony!</message>
  	<message key="doclink">Widzą i18n dokumentacja dla szczegółów:</message>
  	<message key="first">Pierwszy</message>
  	<message key="second">Drugi</message>
  	<message key="third">Trzeci</message>
  	<message key="forth">Czwarty</message>
  	<message key="article">Artykuł</message>
  	<message key="article_text1">To jest paragraf w i18n.</message>
  	<message key="article_text2">To jest następny paragraf w i18n i jest takze fajny.</message>
  	<message key="copyright">Copyright © 2001 Konstantin Piroumian i Krzysztof Zieliński. Żadne prawa nie są zastrzeżone:)</message>
  	<message key="Hello, {0}! Glad to see you!">Witam, {0}! Miło Cię widzieć!</message>
  	<message key="Kot">Tomcat</message>
  	<message key="none">Nic</message>
  	<message key="one">raz</message>
  	<message key="two">dwa</message>
  </catalogue>
  
  
  
  1.1                  xml-cocoon2/webapp/i18n/translations/messages_ru.xml
  
  Index: messages_ru.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  
  <!-- CVS $Id: messages_ru.xml,v 1.1 2001/07/19 11:22:22 dims Exp $ -->
  
  <!-- Russian message catalogue file for cocoon2 sample webapp -->
  
  <catalogue xml:lang="ru">
  	<message key="count_title">На эту страницу заходили {0} раз(а). В последний раз {1}.</message>
  	<message key="a_key">Это значение по ключу.</message>
  	<message key="lang_id1">en</message>
  	<message key="lang_id2">de</message>
  	<message key="lang_id3">pl</message>
  	<message key="lang_id4">es</message>
  	<message key="lang_id5">hy</message>
  	<message key="language">Русский</message>
  	<message key="language1">Английский</message>
  	<message key="language2">Немецкий</message>
  	<message key="language3">Польский</message>
  	<message key="language4">Испанский</message>
  	<message key="language5">Армянский</message>
  	<message key="titletext">Привет, многоязычность!</message>
  	<message key="doclink">Для дополнительной информации по i18n смотри:</message>
  	<message key="first">Первый</message>
  	<message key="second">Второй</message>
  	<message key="third">Третий</message>
  	<message key="forth">Четвертый</message>
  	<message key="article">Статья</message>
  	<message key="article_text1">Это интернационализированный абзац.</message>
  	<message key="article_text2">Это тоже интернационализированный абзац и такой же классный.</message>
  	<message key="copyright">Авторские права © 2001 Константин Пирумян. Ничто не защищено.</message>
  	<message key="Hello, {0}! Glad to see you!">Привет, {0}! Рад тебя видеть!</message>
  	<message key="Kot">Кот</message>
  	<message key="none">Никто</message>
  	<message key="one">раз</message>
  	<message key="two">два</message>
  </catalogue>
  
  
  

----------------------------------------------------------------------
In case of troubles, e-mail:     webmaster@xml.apache.org
To unsubscribe, e-mail:          cocoon-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: cocoon-cvs-help@xml.apache.org