cocoon-cvs mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d...@apache.org
Subject cvs commit: xml-cocoon2/webapp/i18n simple.xsp sitemap.xmap
Date Fri, 15 Jun 2001 18:08:07 GMT
dims        01/06/15 11:08:06

  Modified:    .        build.xml
               src/org/apache/cocoon/transformation I18nTransformer.java
               webapp   sitemap.xmap
               webapp/i18n simple.xsp sitemap.xmap
  Removed:     src/org/apache/cocoon/transformation I18nTransformer2.java
  Log:
  - i18nTransformer2 is now i18nTransformer
  - Fixed build.xml to not filter i18n directory.
  
  Revision  Changes    Path
  1.17      +7 -0      xml-cocoon2/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/build.xml,v
  retrieving revision 1.16
  retrieving revision 1.17
  diff -u -r1.16 -r1.17
  --- build.xml	2001/06/14 16:53:18	1.16
  +++ build.xml	2001/06/15 18:07:58	1.17
  @@ -358,6 +358,7 @@
           <exclude name="**/*.gif"/>
           <exclude name="**/*.jpg"/>
           <exclude name="**/*.png"/>
  +        <exclude name="**/i18n/*"/>
         </fileset>
       </copy>
   
  @@ -368,6 +369,12 @@
           <include name="**/*.png"/>
         </fileset>
       </copy>
  +
  +    <copy todir="${build.war}/i18n" filtering="off">
  +      <fileset dir="${webapp.dir}/i18n">
  +      </fileset>
  +    </copy>
  +
     </target>
   
     <!-- =================================================================== -->
  
  
  
  1.9       +717 -167  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.8
  retrieving revision 1.9
  diff -u -r1.8 -r1.9
  --- I18nTransformer.java	2001/05/31 17:38:59	1.8
  +++ I18nTransformer.java	2001/06/15 18:08:00	1.9
  @@ -1,45 +1,55 @@
  -/*****************************************************************************
  +/**
  + ****************************************************************************
    * 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.cocoon.transformation;
   
  -import java.io.BufferedInputStream;
  -import java.io.BufferedReader;
  -import java.io.File;
  -import java.io.IOException;
  -import java.io.InputStream;
  -import java.io.Reader;
  -import java.net.MalformedURLException;
  -import java.net.URL;
  -import java.util.Hashtable;
  -import java.util.Map;
  -import org.apache.avalon.framework.component.Component;
  -import org.apache.avalon.framework.component.ComponentException;
  -import org.apache.avalon.framework.component.ComponentManager;
  -import org.apache.avalon.framework.component.Composable;
  -import org.apache.avalon.framework.logger.Loggable;
  -import org.apache.avalon.framework.parameters.Parameters;
  -import org.apache.cocoon.ProcessingException;
   import org.apache.cocoon.Roles;
  +import org.apache.cocoon.ProcessingException;
   import org.apache.cocoon.acting.LangSelect;
  -import org.apache.cocoon.caching.CacheValidity;
  -import org.apache.cocoon.caching.Cacheable;
  +import org.apache.cocoon.components.parser.Parser;
   import org.apache.cocoon.environment.Source;
   import org.apache.cocoon.environment.SourceResolver;
  -import org.apache.cocoon.caching.TimeStampCacheValidity;
  -import org.apache.cocoon.components.parser.Parser;
  -import org.apache.cocoon.util.HashUtil;
  -import org.apache.avalon.excalibur.pool.Recyclable;
  +
  +import org.apache.avalon.excalibur.pool.Poolable;
  +import org.apache.avalon.framework.component.ComponentManager;
  +import org.apache.avalon.framework.component.ComponentException;
  +import org.apache.avalon.framework.component.Composable;
  +import org.apache.avalon.framework.component.Component;
  +import org.apache.avalon.framework.parameters.Parameters;
  +import org.apache.avalon.framework.logger.Loggable;
  +
   import org.xml.sax.Attributes;
   import org.xml.sax.InputSource;
   import org.xml.sax.SAXException;
   import org.xml.sax.helpers.AttributesImpl;
   import org.xml.sax.helpers.DefaultHandler;
   
  +import java.io.IOException;
  +
  +import java.util.Map;
  +import java.util.HashMap;
  +import java.util.StringTokenizer;
  +import java.util.ArrayList;
  +import java.util.Locale;
  +import java.util.Date;
  +
  +import java.text.Format;
  +import java.text.MessageFormat;
  +import java.text.DateFormat;
  +import java.text.SimpleDateFormat;
  +import java.text.NumberFormat;
  +import java.text.DecimalFormat;
  +import java.text.ParseException;
  +
  +import java.net.URL;
  +import java.net.MalformedURLException;
  +
   /**
    * I18nTransformer. Cocoon2 port of Infozone groups I18nProcessor.
    * <p>
  @@ -47,194 +57,734 @@
    * </p>
    * <p>
    * &lt;map:transformer<br>
  - *        name="translate"<br>
  - *        src="org.apache.cocoon.transformation.I18nTransformer"/&gt;<br>
  + *	name="translate"<br>
  + *	src="org.apache.cocoon.transformation.I18nTransformer"/&gt;<br>
    * </p>
    * <p>
    * &lt;map:match pattern="file"&gt;<br>
  - *   &lt;map:generate src="file.xml"/&gt;<br>
  - *   &lt;map:transform type="translate"<br>
  - *                     src"translations/file_trans.xml"&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;/map:transform&gt;<br>
  - *   ...<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>
    * <p>
    * When user requests .../file?lang=fi<br>
  - * transformer substitutes text surrounded &lt;i:tr&gt; or &lt;some-elem i:tr="y"&gt;
with
  - * translations from file_trans.xml.
  + * 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>
    * <p>
    * file.xml:<br>
  - * &lt;root xmlns:i="http://apache.org/cocoon/i18n/2.0"&gt;<br>
  - *         &lt;elem i:tr="y"&gt;Translate me&lt;/elem&gt;<br>
  - *         &lt;elem&gt;&lt;i:tr&gt;Translate me&lt;/i:tr&gt;&lt;/elem&gt;<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>
    * <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;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>
    * <p>
  - *It also provides path substitution to images that has to be translated:
  - * </p>
  - * <p>
  - *&lt;elem&gt;&lt;i:image&gt;image.jpg&lt;/i:image&gt;&lt;/elem&gt;
  - * </p>
  - * <p>
  - * is substituted to be according to language
  - * </p>
  - * <p>
  - *&lt;elem&gt;en/image.jpg&lt;/elem&gt;,&lt;elem&gt;fi/image.jpg&lt;/elem&gt;,etc
  - * </p>
  - *
    *
  - *TODO         -Caching dictionaries in memory.<br>
  - *                 -Implementing Infozone group I18nProcessors param substitutions
  - *                 where you can enter params in the translated text.
  - *
  - *
  + * @todo Caching dictionaries in memory.<br>
  + * @todo Date and Number i18n.<br>
  + * @todo Multiple dictionary support. <br>
  +*
  + * @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, Recyclable,
Cacheable {
  +public class I18nTransformer extends AbstractTransformer
  +implements Composable, Poolable {
   
       protected ComponentManager manager;
   
  +    /**
  +     * The parsed dictionary data.
  +     */
       public Map dictionary;
   
  -    //apache.org/cocoon/i18n/2.0";
  +    /**
  +     * 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 final static String I18N_ELEMENT = "i18n";
   
  -    public final static String I18N_ELEMENT_KEY_ATTRIBUTE = "key";
  +    //
  +    // 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";
  +
  +    //
  +    // Text elements and attributes
  +    //
       public final static String I18N_LANG = "lang";
  -    public final static String I18N_KEY_ELEMENT = "key";
  -    public final static String I18N_TR_ATTRIBUTE = "tr";
  -    public final static String I18N_TR_ELEMENT = "tr";
  -    public final static String I18N_IMAGE_ELEMENT = "image";
  -
  -    protected boolean translate_image = false;
  -    protected boolean translate = false;
  -    protected boolean is_element = false;
  -    protected String lang;
  -    protected String source;
  +    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";
  +
  +    // 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";
  +    /**
  +     * <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";
  +    /**
  +     * <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";
  +
  +    // 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;
  +
  +    /**
  +     * Current state of the transformer.
  +     * The value is STATE_OUTSIDE by default.
  +     */
  +    private int current_state = STATE_OUTSIDE;
  +
  +    /**
  +     * Previous state.
  +     * Used to translate text inside params and translate elements.
  +     */
  +     private int prev_state = STATE_OUTSIDE;
  +
  +    /**
  +     * The i18n:key attribute is stored for the current element.
  +     * If no translation found for the key then the character
  +     * data of element is used as default value.
  +     */
  +    private String current_key = null;
   
  -    /** The input source */
  -    private Source inputSource;
  +    /**
  +     * Translated text inside the i18n:text element.
  +     */
  +    private String translated_text = null;
  +
  +    /**
  +     * Translated text, ready for param substitution.
  +     */
  +    private String substitute_text = null;
  +
  +    /**
  +     * Current parameter value (translated or not)
  +     */
  +    private String param_value = null;
  +
  +    /**
  +     * @todo Named parameter substitution.
  +     * i18n:params are stored in a HashMap for named substitutions, <br>
  +     * name attribute is used as a key.<br>
  +     * <i>MessageFormat class does not support named params.
  +     * Some kind of mapping (name to index) must be used.</i>
  +     */
  +    private HashMap namedParams = null;
  +
  +    /**
  +     * i18n:params are stored for index substitutions.
  +     */
  +    private ArrayList indexedParams = new ArrayList();
  +
  +    /**
  +     * Message formatter for param substitution.
  +     */
  +    private MessageFormat formatter = new MessageFormat("");
  +
  +    /**
  +     * Current language id.
  +     */
  +    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
  +     */
  +    private Locale locale;
  +
  +    /**
  +     * 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() : "";
  +
  +        return new Locale(lang, country, variant);
  +    }
  +
  +    public void setLocale(Locale locale) {
  +        this.locale = locale;
  +        this.lang = locale.getLanguage();
  +    }
   
       /**
        *  Uses <code>org.apache.cocoon.acting.LangSelect.getLang()</code>
        *  to get language user has selected. First it checks is lang set in
        *  objectModel.
        */
  -
       public void setup(SourceResolver resolver, Map objectModel, String source,
               Parameters parameters)
               throws ProcessingException, SAXException, IOException {
   
  -        this.inputSource = resolver.resolve(source);
  -
  -        lang = (String)(objectModel.get("lang"));
  +        // Set current language and locale
  +        String lang = (String)(objectModel.get("lang"));
           if (lang == null) {
               lang = LangSelect.getLang(objectModel, parameters);
           }
   
  -        this.source = source;
  +        setLocale(parseLocale(lang));
  +        formatter.setLocale(locale);
   
  -        initialiseDictionary();
  +        // 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));
       }
   
  -    /**
  -     * Generate the unique key.
  -     * This key must be unique inside the space of this component.
  -     *
  -     * @return The generated key hashes the src
  -     */
  -    public long generateKey() {
  -        if (this.inputSource.getLastModified() != 0) {
  -            return HashUtil.hash(this.inputSource.getSystemId());
  +
  +    public void compose(ComponentManager manager) {
  +        this.manager = manager;
  +    }
  +
  +    public void startElement(String uri, String name, String raw,
  +            Attributes attr) throws SAXException {
  +
  +        if (I18N_NAMESPACE_URI.equals(uri)) {
  +            this.getLogger().debug("Starting i18n element: " + name);
  +            startI18NElement(name, attr);
  +            return;
  +        }
  +
  +        super.startElement(uri, name, raw, translateAttributes(name, attr));
  +    }
  +
  +
  +    public void endElement(String uri, String name, String raw)
  +            throws SAXException {
  +
  +        if (I18N_NAMESPACE_URI.equals(uri)) {
  +            endI18NElement(name);
  +            return;
  +        }
  +
  +        super.endElement(uri, name, raw);
  +    }
  +
  +    public void characters(char[] ch, int start, int len) throws SAXException {
  +
  +        if (current_state != STATE_OUTSIDE) {
  +            i18nCharacters(ch, start, len);
  +            return;
           }
  -        return 0;
  +
  +        super.characters(ch, start, len);
  +
       }
   
  +    // My own content handlers
  +
  +    private void startI18NElement(String name, Attributes attr)
  +    throws SAXException {
  +        this.getLogger().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) {
  +                    throw new SAXException(this.getClass().getName()
  +                        + ": 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)) {
  +                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);
  +                }
  +                current_state = STATE_INSIDE_TRANSLATE;
  +            }
  +            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);
  +                }
  +                setFormattingParams(attr);
  +                current_state = STATE_INSIDE_PARAM;
  +            }
  +            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.");
  +                }
  +
  +                setFormattingParams(attr);
  +                current_state = STATE_INSIDE_DATE;
  +            }
  +            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.");
  +                }
  +
  +                setFormattingParams(attr);
  +                current_state = STATE_INSIDE_NUMBER;
  +            }
  +        } catch (Exception e) {
  +            // 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);
  +        }
  +    }
  +
       /**
  -     * Generate the validity object.
  -     *
  -     * @return The generated validity object or <code>null</code> if the
  -     *         component is currently not cacheable.
  +     * Get src-pattern, pattern and value attribute values and store in a Map
        */
  -    public CacheValidity generateValidity() {
  -        if (this.inputSource.getLastModified() != 0) {
  -            return new TimeStampCacheValidity(this.inputSource.getLastModified());
  +    private void setFormattingParams(Attributes attr) throws SAXException {
  +        formattingParams = new HashMap(3);
  +
  +        String attr_value = attr.getValue(I18N_SRC_PATTERN_ATTRIBUTE);
  +        if (attr_value != null) {
  +            formattingParams.put(I18N_SRC_PATTERN_ATTRIBUTE, attr_value);
           }
  -        return null;
  -    }
   
  +        attr_value = attr.getValue(I18N_PATTERN_ATTRIBUTE);
  +        if (attr_value != null) {
  +            formattingParams.put(I18N_PATTERN_ATTRIBUTE, attr_value);
  +        }
   
  -    public void compose(ComponentManager manager) {
  -        this.manager = manager;
  +        attr_value = attr.getValue(I18N_VALUE_ATTRIBUTE);
  +        if (attr_value != null) {
  +            formattingParams.put(I18N_VALUE_ATTRIBUTE, attr_value);
  +        }
  +
  +        attr_value = attr.getValue(I18N_TYPE_ATTRIBUTE);
  +        if (attr_value != null) {
  +            formattingParams.put(I18N_TYPE_ATTRIBUTE, attr_value);
  +        }
  +
  +        attr_value = attr.getValue(I18N_SUB_TYPE_ATTRIBUTE);
  +        if (attr_value != null) {
  +            formattingParams.put(I18N_SUB_TYPE_ATTRIBUTE, attr_value);
  +        }
  +
       }
   
  +    private void endI18NElement(String name) throws SAXException {
  +        this.getLogger().debug("End i18n element: " + name);
  +        try {
  +            switch (current_state) {
  +                case STATE_INSIDE_TEXT: {
  +                    endTextElement();
  +                    break;
  +                }
  +                case STATE_INSIDE_TRANSLATE: {
  +                    endTranslateElement();
  +                    break;
  +                }
  +                case STATE_INSIDE_PARAM: {
  +                    endParamElement();
  +                    break;
  +                }
  +                case STATE_INSIDE_DATE: {
  +                    endDateElement();
  +                    break;
  +                }
  +                case STATE_INSIDE_NUMBER: {
  +                    endNumberElement();
  +                    break;
  +                }
  +            }
  +        } catch (Exception e) {
  +            // 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);
  +        }
  +    }
   
  -    public void startElement(String uri, String name, String raw,
  -            Attributes attr) throws SAXException {
   
  -        if (I18N_NAMESPACE_URI.equals(uri) && I18N_TR_ELEMENT.equals(name)) {
  -            translate = true;
  -            is_element = true;
  -            return;
  +    /*
  +     */
  +    private String stripWhitespace(String s) {
  +        // FIXME (KP) Must be a better way to determine whitespace-only nodes.
  +        // trim() function does not remove spaces if string does not contain
  +        // anything else.
  +        if (s == null) {
  +            return null;
           }
  -        if (I18N_NAMESPACE_URI.equals(uri) && I18N_IMAGE_ELEMENT.equals(name))
{
  -            translate_image = true;
  -            is_element = true;
  +        String result = (s + "!").trim();
  +        return result.substring(0, result.length() - 1);
  +    }
  +
  +    private void i18nCharacters(char[] ch, int start, int len)
  +    throws SAXException{
  +
  +        String text2translate = new String(ch, start, len);
  +        text2translate = stripWhitespace(text2translate);
  +        if (text2translate == null || text2translate.length() == 0) {
               return;
           }
  -        if (attr != null) {
  -            AttributesImpl temp_attr = new AttributesImpl(attr);
  -            int attr_index =
  -                    temp_attr.getIndex(I18N_NAMESPACE_URI, I18N_TR_ATTRIBUTE);
  -            if (attr_index != -1) {
  -                translate = true;
  -                temp_attr.removeAttribute(attr_index);
  -                super.startElement(uri, name, raw, temp_attr);
  -                return;
  +
  +        this.getLogger().debug("Text 2 translate: '" + text2translate + "'");
  +
  +        switch (current_state) {
  +            case STATE_INSIDE_TEXT: {
  +                if (current_key != null) {
  +                    translated_text = (String)(dictionary.get(current_key));
  +                    if (translated_text == null) {
  +                        translated_text = text2translate;
  +                    }
  +                    current_key = null;
  +                }
  +                else {
  +                    translated_text = (String)(dictionary.get(text2translate));
  +                }
  +
  +                break;
  +            }
  +            case STATE_INSIDE_TRANSLATE: {
  +                // Store text for param substitution (do not translate)
  +                if (substitute_text == null) {
  +                    substitute_text = text2translate;
  +                }
  +                break;
  +            }
  +            case STATE_INSIDE_PARAM: {
  +                // Store translation for param substitution
  +                if (param_value == null) {
  +                    param_value = text2translate;
  +                }
  +                break;
  +            }
  +            case STATE_INSIDE_DATE: {
  +                if (formattingParams != null) {
  +                    if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null) {
  +                        formattingParams.put(I18N_VALUE_ATTRIBUTE, text2translate);
  +                    }
  +                    else {
  +                        // how to use the text inside of date element?
  +                    }
  +                }
  +                break;
  +            }
  +            case STATE_INSIDE_NUMBER: {
  +                if (formattingParams != null) {
  +                    if (formattingParams.get(I18N_PATTERN_ATTRIBUTE) == null) {
  +                        formattingParams.put(I18N_PATTERN_ATTRIBUTE, text2translate);
  +                    }
  +                    else {
  +                        // how to use the text inside of number element?
  +                    }
  +                }
  +                break;
  +            }
  +            default: {
  +                throw new SAXException(this.getClass().getName()
  +                    + "Something's really wrong!!!");
               }
           }
  -        super.startElement(uri, name, raw, attr);
       }
   
  +    private Attributes translateAttributes(String name, Attributes attr)
  +    throws SAXException {
  +        if (attr == null) {
  +            return attr;
  +        }
  +
  +        AttributesImpl temp_attr = new AttributesImpl(attr);
  +
  +        // Translate all attributes from i18n:attr="name1 name2 ..."
  +        // using their values as keys
  +        int i18n_attr_index =
  +            temp_attr.getIndex(I18N_NAMESPACE_URI, I18N_ATTR_ATTRIBUTE);
  +
  +        if (i18n_attr_index != -1) {
  +
  +            StringTokenizer st =
  +                new StringTokenizer(temp_attr.getValue(i18n_attr_index));
  +            // remove the i18n:attr attribute - we don't need it
  +            temp_attr.removeAttribute(i18n_attr_index);
  +            while (st.hasMoreElements()) {
  +            // 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));
  +                    // set the translated value
  +                    if (result != null) {
  +                        temp_attr.setValue(attr_index, result);
  +                    }
  +                    else {
  +                        getLogger().warn("translation not found for attribute "
  +                        + attr_name + " in element: " + name);
  +                    }
  +                }
  +                else {
  +                    getLogger().warn("i18n attribute '" + attr_name
  +                        + "' not found in element: " + name);
  +                }
  +            }
  +            return temp_attr;
  +        }
  +
  +        return attr;
  +    }
   
  -    public void endElement(String uri, String name, String raw)
  -            throws SAXException {
  -        if (translate) {
  -            translate = false;
  +    private void endTextElement() throws SAXException {
  +        this.getLogger().debug("End text element, translated_text: " + translated_text);
  +        switch (prev_state) {
  +            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! ---");
  +                }
  +                break;
  +            }
  +            case STATE_INSIDE_TRANSLATE: {
  +                substitute_text = translated_text;
  +                break;
  +            }
  +            case STATE_INSIDE_PARAM: {
  +                param_value = translated_text;
  +                break;
  +            }
           }
  -        if (translate_image) {
  -            translate_image = false;
  +        translated_text = null;
  +        current_state = prev_state;
  +        prev_state = STATE_OUTSIDE;
  +    }
  +
  +    private void endParamElement() throws SAXException {
  +        this.getLogger().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);
  +                if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null
  +                    && param_value != null) {
  +                    this.getLogger().debug("Put param value: " + param_value);
  +                    formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
  +                }
  +                if ("date".equals(paramType)) {
  +                    this.getLogger().debug("Formatting date param: " + formattingParams);
  +                    param_value = formatDate(formattingParams);
  +                }
  +                else if ("number".equals(paramType)) {
  +                    this.getLogger().debug("Formatting number param: " + formattingParams);
  +                    param_value = formatNumber(formattingParams);
  +                }
  +            }
           }
  -        if (is_element) {
  -            is_element = false;
  +        this.getLogger().debug("Added substitution param: " + param_value);
  +        indexedParams.add(param_value);
  +        param_value = null;
  +        current_state = STATE_INSIDE_TRANSLATE;
  +    }
  +
  +    private void endTranslateElement() throws SAXException {
  +
  +        if (substitute_text == null) {
               return;
           }
   
  -        super.endElement(uri, name, raw);
  +        String result;
  +        if (indexedParams.size() > 0 && substitute_text.length() > 0) {
  +            this.getLogger().debug("Text for susbtitution: " + substitute_text);
  +            result = formatter.format(substitute_text, indexedParams.toArray());
  +            this.getLogger().debug("Result of susbtitution: " + result);
  +        }
  +        else {
  +            result = substitute_text;
  +        }
  +
  +        super.contentHandler.characters(result.toCharArray(), 0, result.length());
  +        indexedParams.clear();
  +        substitute_text = null;
  +        current_state = STATE_OUTSIDE;
  +    }
  +
  +    private void endDateElement() throws SAXException {
  +        String result = formatDate(formattingParams);
  +        super.contentHandler.characters(result.toCharArray(), 0, result.length());
  +        current_state = STATE_OUTSIDE;
  +    }
  +
  +    private String formatDate(Map params) throws SAXException {
  +        if (params == null) {
  +            throw new SAXException(this.getClass().getName()
  +                + ": i18n:date - error in element attributes.");
  +        }
  +        // from pattern
  +        String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
  +        // to pattern
  +        String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
  +        // the date value
  +        String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
  +
  +        // parsed date object
  +        Date dateValue = null;
  +
  +        // src format
  +        SimpleDateFormat from_fmt = (SimpleDateFormat)DateFormat.getInstance();
  +        if (srcPattern != null) {
  +            from_fmt.applyPattern(srcPattern);
  +        }
  +
  +        // result pattern is localized
  +        SimpleDateFormat to_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
  +            DateFormat.DEFAULT, DateFormat.DEFAULT, locale);
  +        if (pattern != null) {
  +            to_fmt.applyPattern(pattern);
  +        }
  +
  +        // get current date and time by default
  +        if (value == null) {
  +            dateValue = new Date();
  +        }
  +        else {
  +            try {
  +                dateValue = from_fmt.parse(value);
  +            } catch (ParseException pe) {
  +                throw new SAXException(this.getClass().getName()
  +                    + "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);
  +        return result;
  +    }
  +
  +    private void endNumberElement() throws SAXException {
  +        String result = formatNumber(formattingParams);
  +        super.contentHandler.characters(result.toCharArray(), 0, result.length());
  +        current_state = STATE_OUTSIDE;
       }
   
  +    private String formatNumber(Map params) throws SAXException {
  +        if (params == null) {
  +            throw new SAXException(this.getClass().getName()
  +                + ": i18n:number - error in element attributes.");
  +        }
  +        // from pattern
  +        String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
  +        // to pattern
  +        String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
  +        // the number value
  +        String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
  +        // sub-type
  +        String subType = (String)params.get(I18N_SUB_TYPE_ATTRIBUTE);
  +
  +        // parsed number
  +        Number numberValue = null;
  +
  +        // src format
  +        DecimalFormat from_fmt = (DecimalFormat)NumberFormat.getInstance();
  +        if (srcPattern != null) {
  +            from_fmt.applyPattern(srcPattern);
  +        }
  +
  +        // result pattern is localized
  +        DecimalFormat to_fmt = null;
  +        if (subType == null) {
  +            to_fmt = (DecimalFormat)NumberFormat.getInstance(locale);
  +        }
  +        else if (subType.equals("currency")) {
  +            to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(locale);
  +        }
  +        else if (subType.equals("percent")) {
  +            to_fmt = (DecimalFormat)NumberFormat.getPercentInstance(locale);
  +        }
  +        if (pattern != null) {
  +            to_fmt.applyPattern(pattern);
  +        }
  +
  +        // get current date and time by default
  +        if (value == null) {
  +            numberValue = new Long(0);
  +        }
  +        else {
  +            try {
  +                numberValue = from_fmt.parse(value);
  +            } catch (ParseException pe) {
  +                throw new SAXException(this.getClass().getName()
  +                    + "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);
  +        return result;
  +    }
   
       /**
  -     *Gets translations from xml file to dictionary.
  +     * Gets translations from xml file to dictionary.
        */
       class I18nContentHandler extends DefaultHandler {
           boolean in_entry = false;
  @@ -301,41 +851,21 @@
       }
   
   
  -    public void characters(char[] ch, int start, int len) throws SAXException {
  -        if (translate) {
  -
  -            String text2translate = new String(ch, start, len);
  -            String result = (String)(dictionary.get(text2translate));
  -            if (result != null) {
  -                super.contentHandler.characters(result.toCharArray(), 0, result.length());
  -                return;
  -            }
  -        }
  -        if (translate_image) {
  -            String image_name = new String(ch, start, len);
  -            String result = lang + "/" + image_name;
  -            super.contentHandler.characters(result.toCharArray(), 0, result.length());
  -            return;
  -        }
  -        super.characters(ch, start, len);
  -
  -    }
  -
  -
       /**
        *Loads translations from given URL
        */
  -    private void initialiseDictionary()
  -    throws SAXException, MalformedURLException, IOException {
  +    private void initialiseDictionary(Source inputSource)
  +            throws SAXException, MalformedURLException, IOException {
  +
           Parser parser = null;
   
           try
           {
               parser = (Parser)(manager.lookup(Roles.PARSER));
  -            InputSource input = this.inputSource.getInputSource();
  +            InputSource input = new InputSource(inputSource.getInputStream());
   
               // How this could be cached?
  -            dictionary = new Hashtable();
  +            this.dictionary = new HashMap();
               I18nContentHandler i18n_handler = new I18nContentHandler();
               parser.setContentHandler(i18n_handler);
               parser.parse(input);
  @@ -352,17 +882,37 @@
               getLogger().error("Error in initialiseDictionary", e);
               throw new SAXException("ComponentException in initialiseDictionary");
           } finally {
  -            if(parser != null) this.manager.release(parser);
  +            if(parser != null) this.manager.release((Component) parser);
           }
       }
   
       /**
  -     * Recycle this component.
  -     * All instance variables are set to <code>null</code>.
  +     *
        */
  -    public void recycle() {
  -        super.recycle();
  -        this.inputSource = null;
  -        this.source = null;
  +    static public void main(String[] args) {
  +
  +        Locale locale = null;
  +
  +        Locale[] locales = Locale.getAvailableLocales();
  +        for (int i = 0; i < locales.length; i++) {
  +            locale = locales[i];
  +            SimpleDateFormat fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
  +                DateFormat.DEFAULT, DateFormat.DEFAULT, locale
  +            );
  +
  +            String localized = fmt.format(new Date());
  +
  +            NumberFormat n_fmt = NumberFormat.getCurrencyInstance(locale);
  +            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);
  +        }
       }
  -}
  +
  +}
  \ No newline at end of file
  
  
  
  1.15      +1 -1      xml-cocoon2/webapp/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/sitemap.xmap,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- sitemap.xmap	2001/06/15 16:28:55	1.14
  +++ sitemap.xmap	2001/06/15 18:08:03	1.15
  @@ -29,7 +29,7 @@
      <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.I18nTransformer2"/>
  +   <map:transformer     name="i18n"      src="org.apache.cocoon.transformation.I18nTransformer"/>
     </map:transformers>
   
     <map:readers default="resource">
  
  
  
  1.6       +1 -1      xml-cocoon2/webapp/i18n/simple.xsp
  
  Index: simple.xsp
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/i18n/simple.xsp,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- simple.xsp	2001/06/15 15:01:11	1.5
  +++ simple.xsp	2001/06/15 18:08:04	1.6
  @@ -17,7 +17,7 @@
   		Locale loc = null;
   		String lang = <xsp-request:get-attribute name="lang"/>;
   		if (lang != null) {
  -			loc = org.apache.cocoon.transformation.I18nTransformer2.parseLocale(lang);
  +			loc = org.apache.cocoon.transformation.I18nTransformer.parseLocale(lang);
   		}
   			
   		SimpleDateFormat df = new SimpleDateFormat("EEEE, MMMM dd, yyyy H:mm:ss", loc);
  
  
  
  1.6       +1 -1      xml-cocoon2/webapp/i18n/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/webapp/i18n/sitemap.xmap,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- sitemap.xmap	2001/06/01 16:14:59	1.5
  +++ sitemap.xmap	2001/06/15 18:08:05	1.6
  @@ -4,7 +4,7 @@
   	<map:components>
   		<map:generators default="file"/>
   		<map:transformers default="xslt">
  -			<map:transformer name="i18n" src="org.apache.cocoon.transformation.I18nTransformer2">
  +			<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"/>
  
  
  

----------------------------------------------------------------------
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


Mime
View raw message