dims 01/06/15 11:00:49 Modified: . Tag: cocoon_20_branch build.xml src/org/apache/cocoon/transformation Tag: cocoon_20_branch I18nTransformer.java webapp Tag: cocoon_20_branch sitemap.xmap webapp/docs/samples/soap Tag: cocoon_20_branch soap.xml webapp/i18n Tag: cocoon_20_branch simple.xsp sitemap.xmap Removed: src/org/apache/cocoon/transformation Tag: cocoon_20_branch I18nTransformer2.java Log: - i18nTransformer2 is now i18nTransformer - Fixed build.xml to not filter i18n directory. Revision Changes Path No revision No revision 1.8.2.7 +7 -0 xml-cocoon2/build.xml Index: build.xml =================================================================== RCS file: /home/cvs/xml-cocoon2/build.xml,v retrieving revision 1.8.2.6 retrieving revision 1.8.2.7 diff -u -r1.8.2.6 -r1.8.2.7 --- build.xml 2001/06/14 16:11:39 1.8.2.6 +++ build.xml 2001/06/15 18:00:37 1.8.2.7 @@ -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> <!-- =================================================================== --> No revision No revision 1.8.2.1 +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.8.2.1 diff -u -r1.8 -r1.8.2.1 --- I18nTransformer.java 2001/05/31 17:38:59 1.8 +++ I18nTransformer.java 2001/06/15 18:00:41 1.8.2.1 @@ -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> * <map:transformer<br> - * name="translate"<br> - * src="org.apache.cocoon.transformation.I18nTransformer"/><br> + * name="translate"<br> + * src="org.apache.cocoon.transformation.I18nTransformer"/><br> * </p> * <p> * <map:match pattern="file"><br> - * <map:generate src="file.xml"/><br> - * <map:transform type="translate"<br> - * src"translations/file_trans.xml"><br> - * <parameter name="default_lang" value="fi"/><br> - * <parameter name="available_lang_1" value="fi"/><br> - * <parameter name="available_lang_2" value="en"/><br> - * <parameter name="available_lang_3" value="sv"/><br> - * </map:transform><br> - * ...<br> + * <map:generate src="file.xml"/><br> + * <map:transform type="translate"><br> + * <parameter name="default_lang" value="fi"/><br> + * <parameter name="available_lang_1" value="fi"/><br> + * <parameter name="available_lang_2" value="en"/><br> + * <parameter name="available_lang_3" value="sv"/><br> + * <parameter name="src"<br> + * value="translations/file_trans.xml"/><br> + * </map:transform><br> * </p> * <p> * When user requests .../file?lang=fi<br> - * transformer substitutes text surrounded <i:tr> or <some-elem i:tr="y"> with - * translations from file_trans.xml. + * transformer substitutes text surrounded <i18n:text> with + * translations from file_trans.xml.<br> + * Attributes listed in <i18n:attr> attribute are also translated * </p> * <p> * file.xml:<br> - * <root xmlns:i="http://apache.org/cocoon/i18n/2.0"><br> - * <elem i:tr="y">Translate me</elem><br> - * <elem><i:tr>Translate me</i:tr></elem><br> + * <root xmlns:i18n="http://apache.org/cocoon/i18n/2.0"><br> + * <elem i18n:attr="title" title="translate_me">Text</elem><br> + * <elem><i18n:text>Translate me</i18n:text></elem><br> * </root> * </p> * <p> * file_trans.xml:<br> * <translations><br> - * <entry><key>Translate me</key><br> - * <translation lang="sv">Översätta mej</translation><br> - * <translation lang="fi">Käännä minut</translation><br> - * </entry><br> + * <entry><key>Translate me</key><br> + * <translation lang="sv">Översätta mej</translation><br> + * <translation lang="fi">Käännä minut</translation><br> + * </entry><br> * </translations><br> * </p> * <p> - *It also provides path substitution to images that has to be translated: - * </p> - * <p> - *<elem><i:image>image.jpg</i:image></elem> - * </p> - * <p> - * is substituted to be according to language - * </p> - * <p> - *<elem>en/image.jpg</elem>,<elem>fi/image.jpg</elem>,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:[EMAIL PROTECTED]">Konstantin Piroumian</a> * @author <a href="mailto:[EMAIL PROTECTED]">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 No revision No revision 1.11.2.4 +1 -1 xml-cocoon2/webapp/sitemap.xmap Index: sitemap.xmap =================================================================== RCS file: /home/cvs/xml-cocoon2/webapp/sitemap.xmap,v retrieving revision 1.11.2.3 retrieving revision 1.11.2.4 diff -u -r1.11.2.3 -r1.11.2.4 --- sitemap.xmap 2001/06/15 16:23:43 1.11.2.3 +++ sitemap.xmap 2001/06/15 18:00:44 1.11.2.4 @@ -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"> No revision No revision 1.1.2.2 +1 -1 xml-cocoon2/webapp/docs/samples/soap/soap.xml Index: soap.xml =================================================================== RCS file: /home/cvs/xml-cocoon2/webapp/docs/samples/soap/soap.xml,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.2 diff -u -r1.1.2.1 -r1.1.2.2 --- soap.xml 2001/06/15 16:23:50 1.1.2.1 +++ soap.xml 2001/06/15 18:00:46 1.1.2.2 @@ -6,7 +6,7 @@ > <page> <call source="xmethods"> - <soap:request endpoint="http://services.xmethods.net:80/soap/servlet/rpcrouter"> + <soap:request endpoint="http://services.xmethods.net:8080/soap/servlet/rpcrouter"> <SOAP-ENV:Envelope xsi="http://www.w3.org/1999/XMLSchema-instance" xsd="http://www.w3.org/1999/XMLSchema"> <SOAP-ENV:Body > <ns1:getTemp xmlns:ns1="urn:xmethods-Temperature" xmlns_ns1="urn:xmethods-Temperature" soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"> No revision No revision 1.4.2.2 +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.4.2.1 retrieving revision 1.4.2.2 diff -u -r1.4.2.1 -r1.4.2.2 --- simple.xsp 2001/06/15 15:00:14 1.4.2.1 +++ simple.xsp 2001/06/15 18:00:47 1.4.2.2 @@ -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.5.2.1 +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.5.2.1 diff -u -r1.5 -r1.5.2.1 --- sitemap.xmap 2001/06/01 16:14:59 1.5 +++ sitemap.xmap 2001/06/15 18:00:48 1.5.2.1 @@ -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: [EMAIL PROTECTED] To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]