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>
* <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
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: [EMAIL PROTECTED]
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]