sylvain 02/01/30 06:56:13
Modified: src/java/org/apache/cocoon/acting LocaleAction.java
src/java/org/apache/cocoon/transformation
I18nTransformer.java
src/webapp/i18n simple.xml simple.xsp
src/webapp/i18n/translations messages.xml messages_en.xml
Added: src/java/org/apache/cocoon/i18n i18n.diff
Log:
I18n patches from Konstantin Piroumian ([EMAIL PROTECTED])
Revision Changes Path
1.2 +22 -29
xml-cocoon2/src/java/org/apache/cocoon/acting/LocaleAction.java
Index: LocaleAction.java
===================================================================
RCS file:
/home/cvs/xml-cocoon2/src/java/org/apache/cocoon/acting/LocaleAction.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- LocaleAction.java 3 Jan 2002 12:31:07 -0000 1.1
+++ LocaleAction.java 30 Jan 2002 14:56:12 -0000 1.2
@@ -7,18 +7,24 @@
*****************************************************************************/
package org.apache.cocoon.acting;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.thread.ThreadSafe;
-import org.apache.cocoon.Constants;
-import org.apache.cocoon.environment.*;
-import org.apache.regexp.RE;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
+import org.apache.cocoon.Constants;
+import org.apache.cocoon.environment.Cookie;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.Response;
+import org.apache.cocoon.environment.Session;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.i18n.I18nUtils;
/**
* LocaleAction is a class which obtains the request's locale information
@@ -54,7 +60,7 @@
*
* <br>
*
- * The variables <code>lang</code>, <code>country</code>,
+ * The variables <code>lang</code>, <code>country</code>,
* <code>variant</code>, and <code>locale</code> are all available. Note that
* <code>country</code> and <code>variant</code> can be empty, however
* <code>lang</code> and <code>locale</code> will always contain a valid
value.
@@ -229,29 +235,24 @@
) throws Exception {
// obtain locale information (note, Locale.get* do not return null)
- String lc = getLocale(objectModel);
-
- String[] matches = new RE("_").split(lc);
-
- String l = matches.length > 0
- ? matches[0] : Locale.getDefault().getLanguage();
- String c = matches.length > 1 ? matches[1] : "";
- String v = matches.length > 2 ? matches[2] : "";
+ String lc = getLocaleAttribute(objectModel);
+ Locale locale = I18nUtils.parseLocale(lc);
+ String l = locale.getLanguage();
+ String c = locale.getCountry();
+ String v = locale.getVariant();
debug("obtained locale information, locale = " + lc);
debug("language = " + l + ", country = " + c + ", variant = " + v);
- checkParams(par);
+ if (getLogger().isDebugEnabled()) {
+ checkParams(par);
+ }
if (storeInRequest) {
Request request =
(Request) objectModel.get(Constants.REQUEST_OBJECT);
- request.setAttribute(langAttr, l);
- request.setAttribute(countryAttr, c);
- request.setAttribute(variantAttr, v);
request.setAttribute(localeAttr, lc);
-
debug("updated request");
}
@@ -262,11 +263,7 @@
Session session = request.getSession(createSession);
if (session != null) {
- session.setAttribute(langAttr, l);
- session.setAttribute(countryAttr, c);
- session.setAttribute(variantAttr, v);
session.setAttribute(localeAttr, lc);
-
debug("updated session");
}
}
@@ -276,11 +273,7 @@
Response response =
(Response) objectModel.get(Constants.RESPONSE_OBJECT);
- response.addCookie(response.createCookie(langAttr, l));
- response.addCookie(response.createCookie(countryAttr, c));
- response.addCookie(response.createCookie(variantAttr, v));
response.addCookie(response.createCookie(localeAttr, lc));
-
debug("created cookies");
}
@@ -303,7 +296,7 @@
* @return locale value
* @throws Exception should some error occur
*/
- public static String getLocale(Map objectModel) throws Exception {
+ public static String getLocaleAttribute(Map objectModel) throws
Exception {
String ret_val;
1.1 xml-cocoon2/src/java/org/apache/cocoon/i18n/i18n.diff
Index: i18n.diff
===================================================================
? src/java/org/apache/cocoon/i18n/I18nUtils.java
Index: src/java/org/apache/cocoon/acting/LocaleAction.java
===================================================================
RCS file:
/home/cvspublic/xml-cocoon2/src/java/org/apache/cocoon/acting/LocaleAction.java,v
retrieving revision 1.1
diff -u -r1.1 LocaleAction.java
--- src/java/org/apache/cocoon/acting/LocaleAction.java 3 Jan 2002
12:31:07 -0000 1.1
+++ src/java/org/apache/cocoon/acting/LocaleAction.java 29 Jan 2002
12:58:54 -0000
@@ -7,18 +7,24 @@
*****************************************************************************/
package org.apache.cocoon.acting;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
import org.apache.avalon.framework.configuration.Configurable;
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.thread.ThreadSafe;
-import org.apache.cocoon.Constants;
-import org.apache.cocoon.environment.*;
-import org.apache.regexp.RE;
-import java.util.HashMap;
-import java.util.Locale;
-import java.util.Map;
+import org.apache.cocoon.Constants;
+import org.apache.cocoon.environment.Cookie;
+import org.apache.cocoon.environment.Redirector;
+import org.apache.cocoon.environment.Request;
+import org.apache.cocoon.environment.Response;
+import org.apache.cocoon.environment.Session;
+import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.i18n.I18nUtils;
/**
* LocaleAction is a class which obtains the request's locale information
@@ -54,7 +60,7 @@
*
* <br>
*
- * The variables <code>lang</code>, <code>country</code>,
+ * The variables <code>lang</code>, <code>country</code>,
* <code>variant</code>, and <code>locale</code> are all available. Note that
* <code>country</code> and <code>variant</code> can be empty, however
* <code>lang</code> and <code>locale</code> will always contain a valid
value.
@@ -229,29 +235,24 @@
) throws Exception {
// obtain locale information (note, Locale.get* do not return null)
- String lc = getLocale(objectModel);
-
- String[] matches = new RE("_").split(lc);
-
- String l = matches.length > 0
- ? matches[0] : Locale.getDefault().getLanguage();
- String c = matches.length > 1 ? matches[1] : "";
- String v = matches.length > 2 ? matches[2] : "";
+ String lc = getLocaleAttribute(objectModel);
+ Locale locale = I18nUtils.parseLocale(lc);
+ String l = locale.getLanguage();
+ String c = locale.getCountry();
+ String v = locale.getVariant();
debug("obtained locale information, locale = " + lc);
debug("language = " + l + ", country = " + c + ", variant = " + v);
- checkParams(par);
+ if (getLogger().isDebugEnabled()) {
+ checkParams(par);
+ }
if (storeInRequest) {
Request request =
(Request) objectModel.get(Constants.REQUEST_OBJECT);
- request.setAttribute(langAttr, l);
- request.setAttribute(countryAttr, c);
- request.setAttribute(variantAttr, v);
request.setAttribute(localeAttr, lc);
-
debug("updated request");
}
@@ -262,11 +263,7 @@
Session session = request.getSession(createSession);
if (session != null) {
- session.setAttribute(langAttr, l);
- session.setAttribute(countryAttr, c);
- session.setAttribute(variantAttr, v);
session.setAttribute(localeAttr, lc);
-
debug("updated session");
}
}
@@ -276,11 +273,7 @@
Response response =
(Response) objectModel.get(Constants.RESPONSE_OBJECT);
- response.addCookie(response.createCookie(langAttr, l));
- response.addCookie(response.createCookie(countryAttr, c));
- response.addCookie(response.createCookie(variantAttr, v));
response.addCookie(response.createCookie(localeAttr, lc));
-
debug("created cookies");
}
@@ -303,7 +296,7 @@
* @return locale value
* @throws Exception should some error occur
*/
- public static String getLocale(Map objectModel) throws Exception {
+ public static String getLocaleAttribute(Map objectModel) throws
Exception {
String ret_val;
Index: src/java/org/apache/cocoon/transformation/I18nTransformer.java
===================================================================
RCS file:
/home/cvspublic/xml-cocoon2/src/java/org/apache/cocoon/transformation/I18nTransformer.java,v
retrieving revision 1.4
diff -u -r1.4 I18nTransformer.java
--- src/java/org/apache/cocoon/transformation/I18nTransformer.java 25 Jan
2002 03:58:30 -0000 1.4
+++ src/java/org/apache/cocoon/transformation/I18nTransformer.java 29 Jan
2002 12:59:01 -0000
@@ -9,7 +9,30 @@
*/
package org.apache.cocoon.transformation;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
import org.apache.avalon.excalibur.pool.Poolable;
+import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
@@ -18,1075 +41,1330 @@
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.avalon.framework.parameters.Parameters;
-import org.apache.avalon.excalibur.pool.Recyclable;
+
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.acting.LocaleAction;
import org.apache.cocoon.environment.Source;
import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.i18n.I18nUtils;
import org.apache.cocoon.i18n.XMLResourceBundle;
import org.apache.cocoon.i18n.XMLResourceBundleFactory;
-import org.apache.regexp.RE;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-import java.io.IOException;
-import java.text.*;
-import java.util.*;
/**
- * Internationalisation transformer. Used to transform i18n markup into text
+ * Internationalization transformer is used to transform i18n markup into
text
* based on a particular locale.
*
- * <p>
- *
- * The i18n transformer works by obtaining the users locale via
- * <code>getLocale()</code> in the LocaleAction.
- * (@see org.apache.cocoon.acting.LocaleAction). It them attempts to find a
- * message catalogue that satisifies the particular locale, and use it for
- * for text replacement within i18n markup.
- *
- * <p>
- *
- * Catalogues are maintained in separate files, with a naming convention
- * similar to that of ResourceBundle (@see java.util.ResourceBundle). ie.
+ * <h3>i18n transformer</h3>
+ * <p>The <strong>i18n transformer</strong> works by obtaining the users
locale
+ * based on request, session attributes or a cookie data. See
+ * [EMAIL PROTECTED] org.apache.cocoon.acting.LocaleAction#getLocale(Map) }
for details.
+ * It then attempts to find a <strong>message catalogue</strong> that
satisifies
+ * the particular locale, and use it for for text replacement within i18n
markup.
+ * <p>Catalogues are maintained in separate files, with a naming convention
+ * similar to that of ResourceBundle (See java.util.ResourceBundle).
+ * ie.
* <strong>basename</strong>_<strong>locale</strong>, where <i>basename</i>
* can be any name, and <i>locale</i> can be any locale specified using
- * ISO 639/3166 characters (eg. en_AU, de_AT, es).
- *
- * <p>
- *
- * Catalogues are of the following format:
+ * ISO 639/3166 characters (eg. en_AU, de_AT, es).<br/>
+ * <strong>NOTE: </strong>ISO 639 is not a stable standard; some of the
language
+ * codes it defines (specifically iw, ji, and in) have changed
+ * (see java.util.Locale for details).
*
+ * <h3>Catalogues</h3>
+ * <p>Catalogues are of the following format:
* <pre>
* <?xml version="1.0"?>
* <!-- message catalogue file for locale ... -->
- *
* <catalogue xml:lang="locale">
* <message key="key">text</message>
* ....
* </catalogue>
- * </pre>
- *
- * Where <strong>key</strong> specifies a particular message for that
+ * </pre> Where <strong>key</strong> specifies a particular message for that
* language.
*
- * <p>
- *
- * Files to be translated contain the following markup:
- *
+ * <h3>Usage</h3>
+ * <p>Files to be translated contain the following markup:
* <pre>
* <?xml version="1.0"?>
- *
* ... some text, translate <i18n:text>key</i18n:text>
* </pre>
- *
* At runtime, the i18n transformer will find a message catalogue for the
* user's locale, and will appropriately replace the text between the
* <code><i18n:text></code> markup, using the value between the tags as
* the lookup key.
- *
- * <p>
- *
- * If the i18n transformer cannot find an appropriate message catalogue for
+ * <p>If the i18n transformer cannot find an appropriate message catalogue
for
* the user's given locale, it will recursively try to locate a <i>parent</i>
* message catalogue, until a valid catalogue can be found.
- *
- * ie:
- *
+ * ie:
* <ul>
*
<li><strong>catalogue</strong>_<i>language</i>_<i>country</i>_<i>variant</i>.xml
* <li><strong>catalogue</strong>_<i>language</i>_<i>country</i>.xml
* <li><strong>catalogue</strong>_<i>language</i>.xml
* <li><strong>catalogue</strong>.xml
* </ul>
- *
* eg: Assuming a basename of <i>messages</i> and a locale of <i>en_AU</i>
* (no variant), the following search will occur:
- *
* <ul>
* <li><strong>messages</strong>_<i>en</i>_<i>AU</i>.xml
* <li><strong>messages</strong>_<i>en</i>.xml
* <li><strong>messages</strong>.xml
* </ul>
- *
* This allows the developer to write a hierarchy of message catalogues,
* at each defining messages with increasing depth of variation.
*
- * <p>
- *
- * Sitemap configuration:
- *
+ * <h3>Sitemap configuration</h3>
* <pre>
- * <map:transformer name="i18n"
src="org.apache.cocoon.transformation.I18nTransformer">
- * <catalogue-name>messages</catalogue-name>
- * <catalogue-location>translations</catalogue-location>
- * <untranslated-text>untranslated</untranslated-text>
- * <cache-at-startup>true</cache-at-startup>
- * </map:transformer>
- * </pre>
+ * <map:transformer name="i18n"
+ * src="org.apache.cocoon.transformation.I18nTransformer">
*
+ * <catalogue-name>messages</catalogue-name>
+ * <catalogue-location>translations</catalogue-location>
+ * <untranslated-text>untranslated</untranslated-text>
+ * <cache-at-startup>true</cache-at-startup>
+ * </map:transformer>
+ * </pre> where:
* <ul>
- * <li><strong>catalogue-name</strong>: base name of the message
+ * <li><strong>catalogue-name</strong>: base name of the message
* catalogue (<i>mandatory</i>).
- * <li><strong>catalogue-location</strong>: location of the
+ * <li><strong>catalogue-location</strong>: location of the
* message catalogues (<i>mandatory</i>).
- * <li><strong>untranslated-text</strong>: default text used for
+ * <li><strong>untranslated-text</strong>: default text used for
* untranslated keys (default is 'untranslated-text').
- * <li><strong>cache-at-startup</strong>: flag whether to cache
+ * <li><strong>cache-at-startup</strong>: flag whether to cache
* messages at startup (false by default).
* </ul>
- *
- * <p>
- *
- * To use the transformer in a pipeline, simply specify it in a particular
+ * <p>To use the transformer in a pipeline, simply specify it in a particular
* transform. eg:
- *
* <pre>
* <map:match pattern="file">
- * <map:generate src="file.xml"/>
- * <map:transform type="i18n"/>
- * <map:serialize/>
+ * <map:generate src="file.xml"/>
+ * <map:transform type="i18n"/>
+ * <map:serialize/>
* </map:match>
* </pre>
*
- * <p/>
- *
- * Note, <strong>catalogue-name</strong>, <strong>catalogue-location</strong>
+ * <p>Note, <strong>catalogue-name</strong>,
<strong>catalogue-location</strong>
* and <strong>untranslated-text</strong> can all be overridden at the
* pipeline level by specifying them as parameters to the transform
statement.
*
+ * <p>For date, time and number formatting use the following tags:
* <ul>
- * <li><strong><i18n:date/></strong> gives now only the date.</li>
- * <li><strong><i18n:date-time/></strong> gives the date and time.</li>
- * <li><strong><i18n:time/></strong> gives the time.</li>
- * <li>For date, date-time and time the pattern and src-pattern attribute
- * may have also values of: "short", "medium",
- * "long" or "full".</li>
+ * <li><strong><i18n:date/></strong> gives localized date.</li>
+ * <li><strong><i18n:date-time/></strong> gives localized date and
time.</li>
+ * <li><strong><i18n:time/></strong> gives localized time.</li>
* </ul>
- * <p/>
- * <ul>
- * <li>for date, date-time, time and number a different locale and
- * source-locale can be specified:
- * <i18n:date src-pattern="short" src-locale="en_US" locale="de_DE">
+ * For <code>date</code>, <code>date-time</code> and <code>time</code> the
+ * <code>pattern</code> and <code>src-pattern</code> attribute may have also
+ * values of: <code>short</code>, <code>medium</code>, <code>long</code> or
+ * <code>full</code>. (See java.text.DateFormat for more info on this).
+ * <p>For <code>date</code>, <code>date-time</code>, <code>time</code> and
+ * <code>number</code> a different <code>locale</code> and
+ * <code>source-locale</code> can be specified:
+ * <pre>
+ * <i18n:date src-pattern="short" src-locale="en_US" locale="de_DE">
* 12/24/01
- * </i18n:date>
- * will result in 24.12.2001</li>
- * <li>A given real pattern and src-pattern (not short, medium, long, full)
- * overwrites the locale and src-locale</li>
- * </ul>
- * <p/>
- * Future work coming:
+ * </i18n:date>
+ * </pre> will result in 24.12.2001.
+ *
+ * <p>A given real <code>pattern</code> and <code>src-pattern</code> (not
+ * <code>short, medium, long, full</code>) overwrites the
+ * <code>locale</code> and <code>src-locale</code>
*
+ * <p>Future work coming:
* <ul>
- * <li>Many clean ups :-)
+ * <li>Many clean ups ...done on 23-Jan-2002 ;)
+ * <li>Introduce empty translation (XMLResourceBundle)
+ * <li>Remove 'sub-type' attribute (add possible values to 'type')
+ * <li>Introduce new <get-locale /> element
+ * <li>Introduce new 'currency-no-unit' and 'int-currency-no-unit' types
+ * <li>Fix resources disposal
+ * <li>A little clean ups... ;)
* </ul>
*
- * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Konstantin Piroumian</a>
- * @author <a href="mailto:[EMAIL PROTECTED]">Lassi Immonen</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Michael Enke</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Lassi Immonen</a>
+ *
+ * @todo Move all formatting/parsing routines to I18nUtils
*/
public class I18nTransformer extends AbstractTransformer
- implements Composable, Poolable, Configurable, Recyclable,
Disposable {
-
- private static final String FILE = "file:";
-
- protected ComponentManager manager;
+ implements Composable, Poolable, Configurable, Recyclable, Disposable {
/**
- * The namespace for i18n is "http://apache.org/cocoon/i18n/2.0"
+ * The namespace for i18n is "http://apache.org/cocoon/i18n/2.0".
*/
public static final String I18N_NAMESPACE_URI =
- "http://apache.org/cocoon/i18n/2.0";
+ "http://apache.org/cocoon/i18n/2.0";
//
- // Dictionary elements and attributes
+ // i18n elements
//
- public static final String I18N_DICTIONARY_ELEMENT = "dictionary";
- public static final String I18N_ENTRY_ELEMENT = "entry";
- public static final String I18N_KEY_ELEMENT = "key";
- public static final String I18N_TRANSLATION_ELEMENT = "translation";
-
- //
- // Text elements and attributes
- //
- public static final String I18N_LANG = "lang";
- public static final String I18N_KEY_ATTRIBUTE = "key";
- public static final String I18N_ATTR_ATTRIBUTE = "attr";
- public static final String I18N_TEXT_ELEMENT = "text";
- public static final String I18N_TRANSLATE_ELEMENT = "translate";
- public static final String I18N_PARAM_ELEMENT = "param";
- public static final String I18N_DATE_ELEMENT = "date";
- public static final String I18N_DATE_TIME_ELEMENT = "date-time";
- public static final String I18N_TIME_ELEMENT = "time";
- public static final String I18N_NUMBER_ELEMENT = "number";
-
- // number and date formatting attributes
- public static final String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
- public static final String I18N_PATTERN_ATTRIBUTE = "pattern";
- public static final String I18N_VALUE_ATTRIBUTE = "value";
- public static final String I18N_LOCALE_ATTRIBUTE = "locale";
- public static final String I18N_SRC_LOCALE_ATTRIBUTE = "src-locale";
-
- // configuration parameters
- public static final String I18N_CATALOGUE_NAME = "catalogue-name";
- public static final String I18N_CATALOGUE_LOCATION =
"catalogue-location";
- public static final String I18N_CATALOGUE_PREFIX = "/catalogue/message";
- public static final String I18N_UNTRANSLATED = "untranslated-text";
- public static final String I18N_CACHE_STARTUP = "cache-at-startup";
/**
- * <code>sub-type</code> attribute is used with <code>i18:number</code>
to
- * indicate a sub-type: <code>currency</code>, <code>int-currency</code>
- * or <code>percent</code>.
+ * i18n:text element is used to translate simple text, e.g.:<br/>
+ * <pre>
+ * <i18n:text>This is a multilanguage string</i18n:text>
+ * </pre>
+ */
+ public static final String I18N_TEXT_ELEMENT = "text";
+
+ /**
+ * i18n:translate element is used to translate text with parameter
+ * substitution, e.g.:<br/>
+ * <pre>
+ * <i18n:translate>
+ * This is a multilanguage string with {0} param
+ * <i18n:param>1</i18n:param>
+ * </i18n:translate>
+ * </pre>
+ *
+ * @see #I18N_TEXT_ELEMENT
+ * @see #I18N_PARAM_ELEMENT
*/
- public static final String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
+ public static final String I18N_TRANSLATE_ELEMENT = "translate";
/**
- * <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.
+ * i18n:param is used with i18n:translate to provide substitution params.
+ * The param can have i18n:text as its value to provide multilungual
value.
+ * Parameters can have additional attributes to be used for formatting:
+ * <ul>
+ * <li><code>type</code> - can be <code>date, date-time, time or
+ * number</code>. Used to format params before substitution.
+ * </li>
+ * <li><code>sub-type</code> - can be <code>currency, percent</code>
+ * and used with <code>number</code> type to format a given number
+ * value as a currency or percent.
+ * </li>
+ * <li><code>value</code> - the value of the param. If no value is
+ * specified then the text inside of the param element will be used.
+ * </li>
+ * <li><code>locale</code> - used only with <code>number, date,
time,
+ * date-time</code> types and used to override the current locale to
+ * format the given value.
+ * </li>
+ * <li><code>src-locale</code> - used with <code>number, date, time,
+ * date-time</code> types and specify the locale that should be
used to
+ * parse the given value.
+ * </li>
+ * <li><code>pattern</code> - used with <code>number, date, time,
+ * date-time</code> types and specify the pattern that should be
used
+ * to format the given value.
+ * </li>
+ * <li><code>src-pattern</code> - used with <code>number, date,
time,
+ * date-time</code> types and specify the pattern that should be
used
+ * to parse the given value.
+ * </li>
+ * </ul>
+ * @see #I18N_TRANSLATE_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_PARAM_ELEMENT = "param";
+
+ /**
+ * i18n:date is used to provide a localized date string. Allowed
attributes
+ * are: <code>pattern, src-pattern, locale, src-locale</code>
+ * Usage examples:
+ * <pre>
+ * <i18n:date src-pattern="short" src-locale="en_US"
locale="de_DE">
+ * 12/24/01
+ * </i18n:date>
+ *
+ * <i18n:date pattern="dd/MM/yyyy" />
+ * </pre>
+ *
+ * If no value is specified then the current date will be used. E.g.:
+ * <pre>
+ * <i18n:date />
+ * </pre> displays the current date formatted with default pattern for
+ * the current locale.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_DATE_ELEMENT = "date";
+
+ /**
+ * i18n:date-time is used to provide a localized date and time string.
+ * Allowed attributes are: <code>pattern, src-pattern, locale,
+ * src-locale</code>
+ * Usage examples:
+ * <pre>
+ * <i18n:date-time src-pattern="short" src-locale="en_US"
locale="de_DE"
+ * >
+ * 12/24/01 1:00 AM
+ * </i18n:date>
+ *
+ * <i18n:date-time pattern="dd/MM/yyyy hh:mm" />
+ * </pre>
+ *
+ * If no value is specified then the current date and time will be used.
+ * E.g.:
+ * <pre>
+ * <i18n:date-time />
+ * </pre> displays the current date formatted with default pattern for
+ * the current locale.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_DATE_TIME_ELEMENT = "date-time";
+
+ /**
+ * i18n:time is used to provide a localized time string. Allowed
attributes
+ * are: <code>pattern, src-pattern, locale, src-locale</code>
+ * Usage examples:
+ * <pre>
+ * <i18n:time src-pattern="short" src-locale="en_US"
locale="de_DE">
+ * 1:00 AM
+ * </i18n:time>
+ *
+ * <i18n:time pattern="hh:mm:ss" />
+ * </pre>
+ *
+ * If no value is specified then the current time will be used. E.g.:
+ * <pre>
+ * <i18n:time />
+ * </pre> displays the current time formatted with default pattern for
+ * the current locale.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_TIME_ELEMENT = "time";
+
+ /**
+ * i18n:number is used to provide a localized number string. Allowed
+ * attributes are: <code>pattern, src-pattern, locale, src-locale,
sub-type
+ * </code>
+ * Usage examples:
+ * <pre>
+ * <i18n:number src-pattern="short" src-locale="en_US"
locale="de_DE">
+ * 1000.0
+ * </i18n:number>
+ *
+ * <i18n:number sub-type="currency" />
+ * </pre>
+ *
+ * If no value is specifies then 0 will be used.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
*/
- public static final String I18N_TYPE_ATTRIBUTE = "type";
+ public static final String I18N_NUMBER_ELEMENT = "number";
- // States of the transformer
- private static final int STATE_OUTSIDE = 0;
- private static final int STATE_INSIDE_TEXT = 1;
- private static final int STATE_INSIDE_PARAM = 2;
- private static final int STATE_INSIDE_TRANSLATE = 3;
- private static final int STATE_INSIDE_TRANSLATE_TEXT = 4;
- private static final int STATE_TRANSLATE_KEY = 5;
- private static final int STATE_TRANSLATE_TEXT_KEY = 6;
- private static final int STATE_INSIDE_DATE = 7;
- private static final int STATE_INSIDE_DATE_TIME = 8;
- private static final int STATE_INSIDE_TIME = 9;
- private static final int STATE_INSIDE_NUMBER = 10;
+ /** @todo Implement currency element */
+ public static final String I18N_CURRENCY_ELEMENT = "currency";
+
+ /** @todo Implement percent element */
+ public static final String I18N_PERCENT_ELEMENT = "percent";
+
+ /** @todo Implemement integer currency element */
+ public static final String I18N_INT_CURRENCY_ELEMENT
+ = "int-currency";
+
+ /** @todo Implement currency without unit element */
+ public static final String I18N_CURRENCY_NO_UNIT_ELEMENT =
+ "currency-no-unit";
+
+ /** @todo Implement integer currency without unit element */
+ public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT =
+ "int-currency-no-unit";
+
+ // i18n general attributes
/**
- * Current state of the transformer.
- * The value is STATE_OUTSIDE by default.
+ * This attribute is used with i18n:text element to indicate the key of
+ * the according message. The character data of the element will be used
+ * if no message is found by this key. E.g.:
+ * <pre>
+ * <i18n:text i18n:key="a_key">article_text1</i18n:text>
+ * </pre>
*/
- private int current_state = STATE_OUTSIDE;
+ public static final String I18N_KEY_ATTRIBUTE = "key";
/**
- * Previous state.
- * Used to translate text inside params and translate elements.
+ * This attribute is used with <strong>any</strong> element (even not
i18n)
+ * to translate attribute values. Should contain whitespace separated
+ * attribute names that should be translated. E.g.
+ * <pre>
+ * <para title="first" name="article" i18n:attr="title name" />
+ * </pre>
*/
- private int prev_state = STATE_OUTSIDE;
+ public static final String I18N_ATTR_ATTRIBUTE = "attr";
+
+ // i18n number and date formatting attributes
/**
- * 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.
+ * This attribute is used with date and number formatting elements to
+ * indicate the pattern that should be used to parse the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String current_key = null;
+ public static final String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
/**
- * Translated text inside the i18n:text element.
+ * This attribute is used with date and number formatting elements to
+ * indicate the pattern that should be used to format the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String translated_text = null;
+ public static final String I18N_PATTERN_ATTRIBUTE = "pattern";
/**
- * Translated text, ready for param substitution.
+ * This attribute is used with date and number formatting elements to
+ * indicate the locale that should be used to format the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String substitute_text = null;
+ public static final String I18N_LOCALE_ATTRIBUTE = "locale";
+
+ /**
+ * This attribute is used with date and number formatting elements to
+ * indicate the locale that should be used to parse the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_SRC_LOCALE_ATTRIBUTE = "src-locale";
/**
- * Current parameter value (translated or not)
+ * This attribute is used with date and number formatting elements to
+ * indicate the value that should be parsed and formatted. If value
+ * attribute is not used then the character data of the element will be
used.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String param_value = null;
+ public static final String I18N_VALUE_ATTRIBUTE = "value";
/**
- * @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>
+ * This attribute is used with <code>i18:param</code> to
+ * indicate the parameter type: <code>date, time, date-time</code> or
+ * <code>number, currency, percent, int-currency, currency-no-unit,
+ * int-currency-no-unit</code>.
*/
- private HashMap namedParams = null;
+ public static final String I18N_TYPE_ATTRIBUTE = "type";
/**
- * i18n:params are stored for index substitutions.
+ * This attribute is used with <code>i18:number</code> to
+ * indicate a sub-type: <code>currency</code>, <code>int-currency</code>
+ * or <code>percent</code>.
*/
- private ArrayList indexedParams = new ArrayList();
+ public static final String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
+
+ // �onfiguration parameters
/**
- * Message formatter for param substitution.
+ * This configuration parameter specifies the message catalog name.
*/
- private MessageFormat formatter = new MessageFormat( "" );
+ public static final String I18N_CATALOGUE_NAME = "catalogue-name";
/**
- * Current language id.
+ * This configuration parameter specifies the message catalog location
+ * relative to the current sitemap.
*/
- private String lang;
+ public static final String I18N_CATALOGUE_LOCATION =
"catalogue-location";
/**
- * Locale setting.
+ * This configuration parameter specifies the message that should be
+ * displayed in case of a not translated text (message not found).
*/
- private Locale locale, loc, srcLoc;
+ public static final String I18N_UNTRANSLATED =
"untranslated-text";
/**
- * Date element attributes and their values.
+ * This configuration parameter specifies if the message catalog should
be
+ * cached at startup.
*/
- private HashMap formattingParams;
+ public static final String I18N_CACHE_STARTUP = "cache-at-startup";
/**
- * Dictionary data.
+ * This constant specifies the XPath prefix that will be used
+ * to retrieve a value from a message catalogue. The resulting XPath is
+ * looks like this:
+ * <code>/catalogue/[EMAIL PROTECTED]'key_value']</code>
+ *<br/>
+ * FIXME (KP): We need a more generic way of key interpretation: to use
+ * XPath expression as keys, or keys with non-XML dictionaries.
*/
+ public static final String I18N_CATALOGUE_PREFIX =
"/catalogue/message";
+
+ // Used to check that the catalogue location is a directory.
+ // FIXME (KP): Why should it be a file? It can be any resource!
+ private static final String FILE = "file:";
+
+ // States of the transformer
+ private static final int STATE_OUTSIDE = 0;
+ private static final int STATE_INSIDE_TEXT = 1;
+ private static final int STATE_INSIDE_PARAM = 2;
+ private static final int STATE_INSIDE_TRANSLATE = 3;
+ private static final int STATE_INSIDE_TRANSLATE_TEXT = 4;
+ private static final int STATE_TRANSLATE_KEY = 5;
+ private static final int STATE_TRANSLATE_TEXT_KEY = 6;
+ private static final int STATE_INSIDE_DATE = 7;
+ private static final int STATE_INSIDE_DATE_TIME = 8;
+ private static final int STATE_INSIDE_TIME = 9;
+ private static final int STATE_INSIDE_NUMBER = 10;
+
+ // All date-time related parameter types and element names
+ private static final Set dateTypes;
+
+ // All number related parameter types and element names
+ private static final Set numberTypes;
+
+ // Date pattern types map: short, medium, long, full
+ private static final Map datePatterns;
+
+ static {
+ // initialize date types set
+ HashSet set = new HashSet();
+ set.add(I18N_DATE_ELEMENT);
+ set.add(I18N_TIME_ELEMENT);
+ set.add(I18N_DATE_TIME_ELEMENT);
+ dateTypes = Collections.unmodifiableSet(set);
+
+ // initialize number types set
+ set = new HashSet();
+ set.add(I18N_NUMBER_ELEMENT);
+ set.add(I18N_PERCENT_ELEMENT);
+ set.add(I18N_CURRENCY_ELEMENT);
+ set.add(I18N_INT_CURRENCY_ELEMENT);
+ set.add(I18N_CURRENCY_NO_UNIT_ELEMENT);
+ set.add(I18N_INT_CURRENCY_NO_UNIT_ELEMENT);
+ numberTypes = Collections.unmodifiableSet(set);
+
+ // Initialize date patterns map
+ Map map = new HashMap();
+ map.put("SHORT", new Integer(DateFormat.SHORT));
+ map.put("MEDIUM", new Integer(DateFormat.MEDIUM));
+ map.put("LONG", new Integer(DateFormat.LONG));
+ map.put("FULL", new Integer(DateFormat.FULL));
+ datePatterns = Collections.unmodifiableMap(map);
+ }
+
+ // Component manager for this component
+ private ComponentManager manager;
+
+ // Current state of the transformer. The value is STATE_OUTSIDE by
default.
+ private int current_state;
+
+ // Previous state. Used in text translation inside params and translate
+ // elements.
+ private int prev_state;
+
+ // 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;
+
+ // Translated text inside the i18n:text element.
+ private String translated_text;
+
+ // Translated text, ready for param substitution.
+ private String substitute_text;
+
+ // Current parameter value (translated or not)
+ private String param_value;
+
+ // i18n:params are stored for index substitutions.
+ private ArrayList indexedParams;
+
+ // Message formatter for param substitution.
+ private MessageFormat formatter;
+
+ // Current locale
+ private Locale locale;
+
+ // Date and number elements and params formatting attributes with values.
+ private HashMap formattingParams;
+
+ // Dictionary data
private XMLResourceBundle dictionary;
- // FIXME: Shouldn't factory be the global component?
+ // FIXME: Shouldn't factory be a global component?
// Now every I18nTransformer have own instance of factory
// every of which in turn have own file cache.
+ // FIXME (KP): Can we use static factory variable?
private XMLResourceBundleFactory factory = new
XMLResourceBundleFactory();
- /*
- * i18n configuration variables
- */
+ //
+ // i18n configuration variables
+ //
+
+ // Catalogue name value
private String catalogueName;
+
+ // Catalogue location value
private String catalogueLocation;
+
+ // Untranslated message value
private String untranslated;
- private String globalUntranslated;
+
+ // Cache at startup setting value
private boolean cacheAtStartup;
+ // Helper variable used to hold the old untranslated value
+ private String globalUntranslated;
+
/**
- * Configure this transformer.
+ * Returns the current locale setting of this transformer instance.
+ * @return current Locale object
*/
- public void configure( Configuration conf )
- throws ConfigurationException {
- if ( conf != null ) {
+ public Locale getLocale() {
+ return this.locale;
+ }
+ /**
+ * Configure this transformer.
+ */
+ public void configure(Configuration conf) throws ConfigurationException {
+ if (conf != null) {
// read in the config options from the transformer definition
// obtain the base name of the message catalogue
- Configuration child = conf.getChild( I18N_CATALOGUE_NAME );
- catalogueName = child.getValue( null );
- debug( "Default catalogue name is " + catalogueName );
+ Configuration child = conf.getChild(I18N_CATALOGUE_NAME);
+ catalogueName = child.getValue(null);
+ debug("Default catalogue name is " + catalogueName);
// obtain the directory location of message catalogues
- child = conf.getChild( I18N_CATALOGUE_LOCATION );
- catalogueLocation = child.getValue( null );
- debug( "Default catalogue location is " + catalogueLocation );
+ child = conf.getChild(I18N_CATALOGUE_LOCATION);
+ catalogueLocation = child.getValue(null);
+ debug("Default catalogue location is " + catalogueLocation);
// check our mandatory parameters
- if ( catalogueName == null || catalogueLocation == null )
+ if (catalogueName == null || catalogueLocation == null)
throw new ConfigurationException(
- "I18nTransformer requires the name and location of "
+
- "the message catalogues"
+ "I18nTransformer requires the name and location of " +
+ "the message catalogues"
);
// obtain default text to use for untranslated messages
- child = conf.getChild( I18N_UNTRANSLATED );
- untranslated = child.getValue( I18N_UNTRANSLATED );
- debug( "Default untranslated text is '" + untranslated + "'" );
+ child = conf.getChild(I18N_UNTRANSLATED);
+ untranslated = child.getValue(I18N_UNTRANSLATED);
+ debug("Default untranslated text is '" + untranslated + "'");
// obtain config option, whether to cache messages at startup
time
- child = conf.getChild( I18N_CACHE_STARTUP );
- cacheAtStartup = child.getValueAsBoolean( false );
- debug( ( cacheAtStartup ? "will" : "won't" ) +
+ child = conf.getChild(I18N_CACHE_STARTUP);
+ cacheAtStartup = child.getValueAsBoolean(false);
+ debug((cacheAtStartup ? "will" : "won't") +
" cache messages during startup, by default"
);
// activate resource bundle logging
- factory.setLogger( getLogger() );
+ factory.setLogger(getLogger());
}
}
/**
* Uses <code>org.apache.cocoon.acting.LocaleAction.getLocale()</code>
- * to get language user has selected.
+ * to get language user has selected.
+ * FIXME (KP): Why should I18nTransformer depend on an action?
*/
- public void setup( SourceResolver resolver, Map objectModel, String
source,
- Parameters parameters )
- throws ProcessingException, SAXException, IOException {
+ public void setup(SourceResolver resolver, Map objectModel, String
source,
+ Parameters parameters)
+ throws ProcessingException, SAXException, IOException {
try {
-
// check parameters to see if anything has been locally
overloaded
String localCatLocation = null;
String localCatName = null;
String localUntranslated = null;
- if ( parameters != null ) {
+ if (parameters != null) {
localCatLocation =
- parameters.getParameter( I18N_CATALOGUE_LOCATION, null );
+ parameters.getParameter(I18N_CATALOGUE_LOCATION, null);
localCatName =
- parameters.getParameter( I18N_CATALOGUE_NAME, null );
+ parameters.getParameter(I18N_CATALOGUE_NAME, null);
localUntranslated =
- parameters.getParameter( I18N_UNTRANSLATED, null );
+ parameters.getParameter(I18N_UNTRANSLATED, null);
}
- // if untranslated-text has been overridden, save the original
+ // if untranslated-text has been overridden, save the original
// value so it can be restored when this object is recycled.
- if ( localUntranslated != null ) {
+ // FIXME (KP): What about other overriden params?
+ if (localUntranslated != null) {
globalUntranslated = untranslated;
untranslated = localUntranslated;
}
- // Set current language and locale
- String lc = LocaleAction.getLocale( objectModel );
-
// configure the factory
- _setup( resolver,
- localCatLocation == null
- ? catalogueLocation : localCatLocation
- );
+ _setup(resolver, localCatLocation == null ? catalogueLocation
+ : localCatLocation);
+
+ // Get current locale
+ String lc = LocaleAction.getLocaleAttribute(objectModel);
+ Locale locale = I18nUtils.parseLocale(lc);
+ debug("using locale " + locale.toString());
// setup everything for the current locale
- String[] matches = new RE( "_" ).split( lc );
+ dictionary = (XMLResourceBundle) factory.select(
+ localCatName == null ? catalogueName : localCatName,
+ locale
+ );
+ debug( "selected dictionary " + dictionary );
- String l = matches.length > 0
- ? matches[0] : Locale.getDefault().getLanguage();
- String c = matches.length > 1 ? matches[1] : "";
- String v = matches.length > 2 ? matches[2] : "";
- Locale locale = new Locale( l, c, v );
-
- debug( "using locale " + locale.toString() );
-
- dictionary =
- (XMLResourceBundle) factory.select(
- localCatName == null ? catalogueName : localCatName,
- locale
- );
+ // Initialize instance state variables
- debug( "selected dictionary " + dictionary );
+ this.locale = locale;
+ this.current_state = STATE_OUTSIDE;
+ this.prev_state = STATE_OUTSIDE;
+ this.current_key = null;
+ this.translated_text = null;
+ this.substitute_text = null;
+ this.param_value = null;
+ this.indexedParams = new ArrayList();
+ this.formattingParams = null;
- setLocale( locale );
+ // Create and initialize a formatter
+ this.formatter = new MessageFormat("");
+ this.formatter.setLocale(locale);
- } catch ( Exception e ) {
- debug( "exception generated, leaving unconfigured" );
- throw new ProcessingException( e.getMessage(), e );
+ } catch (Exception e) {
+ debug("exception generated, leaving unconfigured");
+ throw new ProcessingException(e.getMessage(), e);
}
}
/**
- * Internal setup.
+ * Internal setup of XML resource bundle and factory.
*
* REVISIT: when we can get the resolver anywhere, we can pass the
* configuration object directly to XMLResourceBundle.
*/
- private void _setup( SourceResolver resolver, String location )
- throws Exception {
+ private void _setup(SourceResolver resolver, String location)
+ throws Exception {
// configure the factory to log correctly and cache catalogues
DefaultConfiguration configuration =
- new DefaultConfiguration( "name", "location" );
+ new DefaultConfiguration("name", "location");
DefaultConfiguration cacheConf =
- new DefaultConfiguration(
-
XMLResourceBundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
- "location"
- );
- cacheConf.setValue( new Boolean( cacheAtStartup ).toString() );
- configuration.addChild( cacheConf );
+ new DefaultConfiguration(
+ XMLResourceBundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
+ "location"
+ );
+ cacheConf.setValue(new Boolean(cacheAtStartup).toString());
+ configuration.addChild(cacheConf);
// set the root location for message catalogues
DefaultConfiguration dirConf =
- new DefaultConfiguration(
-
XMLResourceBundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
- "location"
- );
+ new DefaultConfiguration(
+ XMLResourceBundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
+ "location"
+ );
- debug( "catalog location:" + location );
+ debug("catalog location:" + location);
Source source = resolver.resolve(location);
try {
String systemId = source.getSystemId();
- if ( !systemId.startsWith( FILE ) ) {
- throw new ResourceNotFoundException( systemId + " does not
denote a directory" );
+ if (!systemId.startsWith(FILE)) {
+ throw new ResourceNotFoundException(
+ systemId + " does not denote a directory"
+ );
}
- debug( "catalog directory:" + systemId );
- dirConf.setValue( systemId );
+ debug("catalog directory:" + systemId);
+ dirConf.setValue(systemId);
+ configuration.addChild(dirConf);
} finally {
source.recycle();
}
- configuration.addChild( dirConf );
- factory.configure( configuration );
-
- debug( "configured" );
+ // Pass created configuration object to the factory
+ factory.configure(configuration);
+ debug("configured");
}
- public void compose( ComponentManager manager ) {
+ public void compose(ComponentManager manager) {
this.manager = manager;
- factory.compose( manager );
+ factory.compose(manager);
}
- public void startElement( String uri, String name, String raw,
- Attributes attr ) throws SAXException {
+ //
+ // Standart SAX event handlers
+ //
- if ( I18N_NAMESPACE_URI.equals( uri ) ) {
- debug( "Starting i18n element: " + name );
- startI18NElement( name, attr );
+ public void startElement(String uri, String name, String raw,
+ Attributes attr) throws SAXException {
+
+ if (I18N_NAMESPACE_URI.equals(uri)) {
+ debug("Starting i18n element: " + name);
+ startI18NElement(name, attr);
return;
}
- super.startElement( uri, name, raw, translateAttributes( name, attr
) );
+ super.startElement(uri, name, raw, translateAttributes(name, attr));
}
- public void endElement( String uri, String name, String raw )
- throws SAXException {
+ public void endElement(String uri, String name, String raw)
+ throws SAXException {
- if ( I18N_NAMESPACE_URI.equals( uri ) ) {
- endI18NElement( name );
+ if (I18N_NAMESPACE_URI.equals(uri)) {
+ endI18NElement(name);
return;
}
- super.endElement( uri, name, raw );
+ super.endElement(uri, name, raw);
}
- public void characters( char[] ch, int start, int len ) throws
SAXException {
+ public void characters(char[] ch, int start, int len) throws
SAXException {
- if ( current_state != STATE_OUTSIDE ) {
- i18nCharacters( ch, start, len );
+ if (current_state != STATE_OUTSIDE) {
+ i18nCharacters(ch, start, len);
return;
}
- super.characters( ch, start, len );
+ super.characters(ch, start, len);
}
- // My own content handlers
+ //
+ // i18n specific event handlers
+ //
- private void startI18NElement( String name, Attributes attr )
- throws SAXException {
- 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." );
- }
+ private void startI18NElement(String name, Attributes attr)
+ throws SAXException {
- setFormattingParams( attr );
- current_state = STATE_INSIDE_DATE;
- } else if ( I18N_DATE_TIME_ELEMENT.equals( name ) ) {
- if ( current_state != STATE_OUTSIDE ) {
- throw new SAXException( this.getClass().getName()
- + ": i18n:date-time elements are
not allowed "
- + "inside of other i18n
elements." );
- }
+ debug("Start i18n element: " + name);
- setFormattingParams( attr );
- current_state = STATE_INSIDE_DATE_TIME;
- } else if ( I18N_TIME_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." );
- }
+ 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
+ );
+ }
- setFormattingParams( attr );
- current_state = STATE_INSIDE_TIME;
- } 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." );
- }
+ 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_DATE_TIME_ELEMENT.equals(name)) {
+ if (current_state != STATE_OUTSIDE) {
+ throw new SAXException(
+ this.getClass().getName()
+ + ": i18n:date-time elements are not allowed "
+ + "inside of other i18n elements."
+ );
+ }
- setFormattingParams( attr );
- current_state = STATE_INSIDE_NUMBER;
+ setFormattingParams(attr);
+ current_state = STATE_INSIDE_DATE_TIME;
+ } else if (I18N_TIME_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_TIME;
+ } 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."
+ );
}
- } 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 );
+
+ setFormattingParams(attr);
+ current_state = STATE_INSIDE_NUMBER;
}
}
- /**
- * Get src-pattern, pattern and value attribute values and store in a Map
- */
- private void setFormattingParams( Attributes attr ) throws SAXException {
- formattingParams = new HashMap( 3 );
+ // Get all possible i18n formatting attribute values and store in a Map
+ private void setFormattingParams(Attributes attr) throws SAXException {
+ formattingParams = new HashMap(3); // average number of attributes
is 3
- String attr_value = attr.getValue( I18N_SRC_PATTERN_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_SRC_PATTERN_ATTRIBUTE, attr_value );
+ String attr_value = attr.getValue(I18N_SRC_PATTERN_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_SRC_PATTERN_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_PATTERN_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_PATTERN_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_PATTERN_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_PATTERN_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_VALUE_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_VALUE_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_VALUE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_LOCALE_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_LOCALE_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_LOCALE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_LOCALE_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_SRC_LOCALE_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_SRC_LOCALE_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_SRC_LOCALE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_SRC_LOCALE_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_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 );
+ 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 {
- 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:
- case STATE_INSIDE_DATE_TIME:
- case STATE_INSIDE_TIME:
- {
- endDate_TimeElement();
- 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 );
- }
- }
+ private void endI18NElement(String name) throws SAXException {
+ debug("End i18n element: " + name);
+ switch (current_state) {
+ case STATE_INSIDE_TEXT:
+ endTextElement();
+ break;
+ case STATE_INSIDE_TRANSLATE:
+ endTranslateElement();
+ break;
- /*
- */
- 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;
+ case STATE_INSIDE_PARAM:
+ endParamElement();
+ break;
+
+ case STATE_INSIDE_DATE:
+ case STATE_INSIDE_DATE_TIME:
+ case STATE_INSIDE_TIME:
+ endDate_TimeElement();
+ break;
+
+ case STATE_INSIDE_NUMBER:
+ endNumberElement();
+ break;
}
- String result = ( s + "!" ).trim();
- return result.substring( 0, result.length() - 1 );
}
- private void i18nCharacters( char[] ch, int start, int len )
- throws SAXException {
+ private void i18nCharacters(char[] ch, int start, int len)
+ throws SAXException {
- String key = new String( ch, start, len );
- key = stripWhitespace( key );
- if ( key == null || key.length() == 0 ) {
+ String textValue = new String(ch, start, len).trim();
+ if (textValue == null || textValue.length() == 0) {
return;
}
- debug( "i18n message key = '" + key + "'" );
+ debug( "i18n message text = '" + textValue + "'" );
- switch ( current_state ) {
- case STATE_INSIDE_TEXT:
- {
- if ( current_key != null ) {
- try {
- translated_text = getString( current_key );
- } catch ( MissingResourceException e ) {
- translated_text = untranslated;
- }
- if ( translated_text == null ) {
- translated_text = key;
- }
- current_key = null;
- } else {
- try {
- translated_text = getString( key );
- } catch ( MissingResourceException e ) {
- translated_text = untranslated;
- }
- }
-
- break;
- }
- case STATE_INSIDE_TRANSLATE:
- {
- // Store text for param substitution (do not translate)
- if ( substitute_text == null ) {
- substitute_text = key;
- }
- break;
+ switch (current_state) {
+ case STATE_INSIDE_TEXT:
+ if (current_key != null) { // if i18n:key attribute were used
+ translated_text = getString(current_key, untranslated);
+
+ // If no translation found and untranslated param is null
+ if (translated_text == null) {
+ translated_text = textValue; // use the key
}
- case STATE_INSIDE_PARAM:
- {
- // Store translation for param substitution
- if ( param_value == null ) {
- param_value = key;
- }
- break;
- }
- case STATE_INSIDE_DATE:
- case STATE_INSIDE_DATE_TIME:
- case STATE_INSIDE_TIME:
- case STATE_INSIDE_NUMBER:
- {
- if ( formattingParams != null ) {
- if ( formattingParams.get( I18N_VALUE_ATTRIBUTE ) ==
null ) {
- formattingParams.put( I18N_VALUE_ATTRIBUTE, key
);
- } else {
- // how to use the text inside of date element?
- }
- }
- break;
- }
- default:
- {
- throw new SAXException( this.getClass().getName()
- + "Something's really wrong!!!"
);
- }
+ // reset the key holding variable
+ current_key = null;
+ } else { // use text value as dictionary key
+ translated_text = getString(textValue, untranslated);
+ }
+ break;
+
+ case STATE_INSIDE_TRANSLATE:
+ // Store text for param substitution (do not translate)
+ if (substitute_text == null) {
+ substitute_text = textValue;
+ }
+ break;
+
+ case STATE_INSIDE_PARAM:
+ // Store translation for param substitution
+ if (param_value == null) {
+ param_value = textValue;
+ }
+ break;
+
+ case STATE_INSIDE_DATE:
+ case STATE_INSIDE_DATE_TIME:
+ case STATE_INSIDE_TIME:
+ case STATE_INSIDE_NUMBER:
+ if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null) {
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, textValue);
+ } else {
+ ; // ignore the text inside of date element
+ }
+ break;
+
+ default:
+ throw new IllegalStateException(
+ this.getClass().getName()
+ + " developer's fault");
}
}
- private Attributes translateAttributes( String name, Attributes attr )
- throws SAXException {
- if ( attr == null ) {
+ // Translate all attributes that are listed in i18n:attr attribute
+ private Attributes translateAttributes(String name, Attributes attr)
+ throws SAXException {
+
+ if (attr == null) {
return attr;
}
- AttributesImpl temp_attr = new AttributesImpl( 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 ) {
+ 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
) );
+ 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
+ temp_attr.removeAttribute(i18n_attr_index);
+
+ // iterate through listed attributes and translate them
+ while (st.hasMoreElements()) {
String attr_name = st.nextToken();
- int attr_index = temp_attr.getIndex( attr_name );
+ int attr_index = temp_attr.getIndex(attr_name);
+ if (attr_index != -1) {
+ String text2translate = temp_attr.getValue(attr_index);
+ String result = getString(text2translate, untranslated);
- if ( attr_index != -1 ) {
- String text2translate = temp_attr.getValue( attr_index );
- String result;
-
- try {
- result = getString( text2translate );
- } catch ( MissingResourceException e ) {
- result = untranslated;
- }
// set the translated value
- if ( result != null ) {
- temp_attr.setValue( attr_index, result );
+ if (result != null) {
+ temp_attr.setValue(attr_index, result);
} else {
- getLogger().warn( "translation not found for
attribute "
- + attr_name + " in element: " +
name );
+ getLogger().warn("translation not found for
attribute "
+ + attr_name + " in element: " +
name);
}
} else {
- getLogger().warn( "i18n attribute '" + attr_name
- + "' not found in element: " + name );
+ getLogger().warn("i18n attribute '" + attr_name
+ + "' not found in element: " + name);
}
}
+
return temp_attr;
}
+ // nothing to translate, just return
return attr;
}
private void endTextElement() throws SAXException {
- 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
- debug( "--- Translation not found! ---" );
- }
- break;
- }
- case STATE_INSIDE_TRANSLATE:
- {
- substitute_text = translated_text;
- break;
- }
- case STATE_INSIDE_PARAM:
- {
- param_value = translated_text;
- break;
- }
+ 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
+ debug( "--- Translation not found! ---" );
+ }
+ break;
+
+ case STATE_INSIDE_TRANSLATE:
+ substitute_text = translated_text;
+ break;
+
+ case STATE_INSIDE_PARAM:
+ param_value = translated_text;
+ break;
}
+
translated_text = null;
current_state = prev_state;
prev_state = STATE_OUTSIDE;
}
+ // Process substitution parameter
private void endParamElement() throws SAXException {
- debug( "Substitution param: " + param_value );
- if ( formattingParams != null ) {
- String paramType = (String) formattingParams.get(
I18N_TYPE_ATTRIBUTE );
- if ( paramType != null ) {
- debug( "Param type: " + paramType );
- if ( formattingParams.get( I18N_VALUE_ATTRIBUTE ) == null
- && param_value != null ) {
- debug( "Put param value: " + param_value );
- formattingParams.put( I18N_VALUE_ATTRIBUTE, param_value
);
- }
- if ( "date".equals( paramType ) ||
- "date-time".equals( paramType ) ||
- "time".equals( paramType ) ) {
- debug( "Formatting date_time param: " + formattingParams
);
- param_value = formatDate_Time( formattingParams );
- } else if ( "number".equals( paramType ) ) {
- debug( "Formatting number param: " + formattingParams );
- param_value = formatNumber( formattingParams );
- }
+ debug("Substitution param: " + param_value);
+
+ String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
+ if (paramType != null) {
+ // We have a typed parameter
+
+ debug("Param type: " + paramType);
+ if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null &&
+ param_value != null) {
+
+ debug("Put param value: " + param_value);
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
+ }
+
+ // Check if we have a date or a number parameter
+ if (dateTypes.contains(paramType)) {
+ debug("Formatting date_time param: " + formattingParams);
+ param_value = formatDate_Time(formattingParams);
+ } else if (numberTypes.contains(paramType)) {
+ debug("Formatting number param: " + formattingParams);
+ param_value = formatNumber(formattingParams);
}
}
- debug( "Added substitution param: " + param_value );
- indexedParams.add( param_value );
+
+ 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 ) {
+ if (substitute_text == null) {
return;
}
String result;
- if ( indexedParams.size() > 0 && substitute_text.length() > 0 ) {
- debug( "Text for susbtitution: " + substitute_text );
- result = formatter.format( substitute_text,
indexedParams.toArray() );
- debug( "Result of susbtitution: " + result );
+ if (indexedParams.size() > 0 && substitute_text.length() > 0) {
+ debug("Text for susbtitution: " + substitute_text);
+ result = formatter.format(substitute_text,
indexedParams.toArray());
+ debug("Result of susbtitution: " + result);
} else {
result = substitute_text;
}
- super.contentHandler.characters( result.toCharArray(), 0,
result.length() );
+ super.contentHandler.characters(result.toCharArray(), 0,
result.length());
indexedParams.clear();
substitute_text = null;
current_state = STATE_OUTSIDE;
}
private void endDate_TimeElement() throws SAXException {
- String result = formatDate_Time( formattingParams );
- super.contentHandler.characters( result.toCharArray(), 0,
result.length() );
+ String result = formatDate_Time(formattingParams);
+ super.contentHandler.characters(result.toCharArray(), 0,
result.length());
current_state = STATE_OUTSIDE;
}
- private Locale getLocale( Map params, String attribute ) {
- Locale locale = this.locale;
+ // Helper method: creates Locale object from a string value in a map
+ private Locale getLocale(Map params, String attribute) {
// the specific locale value
- String lc = (String) params.get( attribute );
- if ( lc != null )
- try {
-
- String[] matches = new RE( "_" ).split( lc );
- String l = matches.length > 0
- ? matches[0] : Locale.getDefault().getLanguage();
- String c = matches.length > 1 ? matches[1] : "";
- String v = matches.length > 2 ? matches[2] : "";
- locale = new Locale( l, c, v );
- } catch ( Exception e ) {
- }
- return locale;
- }
-
- private String formatDate_Time( Map params ) throws SAXException {
- SimpleDateFormat to_fmt = null, from_fmt;
- String element = null;
- int srcStyle = DateFormat.SHORT, style = DateFormat.SHORT;
- boolean realPattern = false, realSrcPattern = false;
-
- if ( params == null ) {
- throw new SAXException( this.getClass().getName()
- + ": i18n:" + element + " - error in
element attributes." );
- }
-
- loc = getLocale( params, I18N_LOCALE_ATTRIBUTE );
- srcLoc = getLocale( params, I18N_SRC_LOCALE_ATTRIBUTE );
-
- // 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 );
+ String lc = (String)params.get(attribute);
+ return I18nUtils.parseLocale(lc, this.locale);
+ }
- if ( srcPattern == null ) {
- srcStyle = DateFormat.DEFAULT;
- } else {
- if ( srcPattern.equalsIgnoreCase( "short" ) )
- srcStyle = DateFormat.SHORT;
- else if ( srcPattern.equalsIgnoreCase( "medium" ) )
- srcStyle = DateFormat.MEDIUM;
- else if ( srcPattern.equalsIgnoreCase( "long" ) )
- srcStyle = DateFormat.LONG;
- else if ( srcPattern.equalsIgnoreCase( "full" ) )
- srcStyle = DateFormat.FULL;
- /* really a src-pattern */
- else
+ private String formatDate_Time(Map params) throws SAXException {
+ // Check that we have not null params
+ if (params == null) {
+ throw new IllegalArgumentException("Nothing to format");
+ }
+
+ // Formatters
+ SimpleDateFormat to_fmt = null;
+ SimpleDateFormat from_fmt = null;
+
+ // Date formatting styles
+ int srcStyle = DateFormat.DEFAULT;
+ int style = DateFormat.DEFAULT;
+
+ // Date formatting patterns
+ boolean realPattern = false;
+ boolean realSrcPattern = false;
+
+ // From locale
+ Locale srcLoc = getLocale(params, I18N_SRC_LOCALE_ATTRIBUTE);
+ // To locale
+ Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
+
+ // 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);
+
+ // A src-pattern attribute is present
+ if (srcPattern != null) {
+ // Check if we have a real pattern
+ Integer patternValue =
(Integer)datePatterns.get(srcPattern.toUpperCase());
+ if (patternValue != null) {
+ srcStyle = patternValue.intValue();
+ } else {
realSrcPattern = true;
+ }
}
- if ( pattern == null ) {
- style = DateFormat.DEFAULT;
- } else {
- if ( pattern.equalsIgnoreCase( "short" ) )
- style = DateFormat.SHORT;
- else if ( pattern.equalsIgnoreCase( "medium" ) )
- style = DateFormat.MEDIUM;
- else if ( pattern.equalsIgnoreCase( "long" ) )
- style = DateFormat.LONG;
- else if ( pattern.equalsIgnoreCase( "full" ) )
- style = DateFormat.FULL;
- /* really a pattern */
- else
+
+ // A pattern attribute is present
+ if (pattern != null) {
+ Integer patternValue =
(Integer)datePatterns.get(pattern.toUpperCase());
+ if (patternValue != null) {
+ style = patternValue.intValue();
+ } else {
realPattern = true;
+ }
}
- if ( current_state == STATE_INSIDE_DATE ) {
- element = I18N_DATE_ELEMENT;
- to_fmt = (SimpleDateFormat) DateFormat.
- getDateInstance( style, loc );
- from_fmt = (SimpleDateFormat) DateFormat.
- getDateInstance( srcStyle, srcLoc );
- } else if ( current_state == STATE_INSIDE_DATE_TIME ) {
- element = I18N_DATE_TIME_ELEMENT;
- to_fmt = (SimpleDateFormat) DateFormat.
- getDateTimeInstance( style, style, loc );
- from_fmt = (SimpleDateFormat) DateFormat.
- getDateTimeInstance( srcStyle, srcStyle, srcLoc );
+ // If we are inside of a typed param
+ String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
+
+ // Initializing date formatters
+ if (current_state == STATE_INSIDE_DATE ||
+ I18N_DATE_ELEMENT.equals(paramType)) {
+
+ to_fmt = (SimpleDateFormat)DateFormat.getDateInstance(style,
loc);
+ from_fmt = (SimpleDateFormat)DateFormat.getDateInstance(
+ srcStyle,
+ srcLoc
+ );
+ } else if (current_state == STATE_INSIDE_DATE_TIME ||
+ I18N_DATE_TIME_ELEMENT.equals(paramType)) {
+ to_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
+ style,
+ style,
+ loc
+ );
+ from_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
+ srcStyle,
+ srcStyle,
+ srcLoc
+ );
} else {
- /* STATE_INSIDE_TIME */
- element = I18N_TIME_ELEMENT;
- to_fmt = (SimpleDateFormat) DateFormat.
- getTimeInstance( style, loc );
- from_fmt = (SimpleDateFormat) DateFormat.
- getTimeInstance( srcStyle, srcLoc );
+ // STATE_INSIDE_TIME or param type='time'
+ to_fmt = (SimpleDateFormat)DateFormat.getTimeInstance(style,
loc);
+ from_fmt = (SimpleDateFormat)DateFormat.getTimeInstance(
+ srcStyle,
+ srcLoc
+ );
}
// parsed date object
Date dateValue = null;
// pattern overwrites locale format
- if ( realSrcPattern ) {
- from_fmt.applyPattern( srcPattern );
+ if (realSrcPattern) {
+ from_fmt.applyPattern(srcPattern);
}
- if ( realPattern ) {
- to_fmt.applyPattern( pattern );
+ if (realPattern) {
+ to_fmt.applyPattern(pattern);
}
// get current date and time by default
- if ( value == null ) {
+ 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 );
+ 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 );
- debug( "i18n:" + element + " result: " + result );
- return result;
+ debug("### Formatting date: " + dateValue + " with localized pattern
"
+ + to_fmt.toLocalizedPattern() + " for locale: " + locale);
+ return to_fmt.format(dateValue);
}
private void endNumberElement() throws SAXException {
- String result = formatNumber( formattingParams );
- super.contentHandler.characters( result.toCharArray(), 0,
result.length() );
+ 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." );
+ 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
);
+ String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
// to pattern
- String pattern = (String) params.get( I18N_PATTERN_ATTRIBUTE );
+ String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
// the number value
- String value = (String) params.get( I18N_VALUE_ATTRIBUTE );
+ String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
// sub-type
- String subType = (String) params.get( I18N_SUB_TYPE_ATTRIBUTE );
+ String subType = (String)params.get(I18N_SUB_TYPE_ATTRIBUTE);
// parsed number
Number numberValue = null;
// locale, may be switched locale
- loc = getLocale( params, I18N_LOCALE_ATTRIBUTE );
- srcLoc = getLocale( params, I18N_SRC_LOCALE_ATTRIBUTE );
-
+ Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
+ Locale srcLoc = getLocale(params, I18N_SRC_LOCALE_ATTRIBUTE);
// src format
- DecimalFormat from_fmt = (DecimalFormat) NumberFormat.getInstance(
srcLoc );
+ DecimalFormat from_fmt =
(DecimalFormat)NumberFormat.getInstance(srcLoc);
int int_currency = 0;
// src-pattern overwrites locale format
- if ( srcPattern != null ) {
- from_fmt.applyPattern( srcPattern );
+ if (srcPattern != null) {
+ from_fmt.applyPattern(srcPattern);
}
// to format
@@ -1094,120 +1372,106 @@
char dec = from_fmt.getDecimalFormatSymbols().getDecimalSeparator();
int decAt = 0;
boolean appendDec = false;
- if ( subType == null ) {
- to_fmt = (DecimalFormat) NumberFormat.getInstance( loc );
- to_fmt.setMaximumFractionDigits( 309 );
- for ( int i = value.length() - 1;
- i >= 0 && value.charAt( i ) != dec; i--, decAt++ )
+ if (subType == null) {
+ to_fmt = (DecimalFormat)NumberFormat.getInstance(loc);
+ to_fmt.setMaximumFractionDigits(309);
+ for (int i = value.length() - 1;
+ i >= 0 && value.charAt(i) != dec; i--, decAt++) {
;
- if ( decAt < value.length() ) to_fmt.setMinimumFractionDigits(
decAt );
+ }
+
+ if (decAt <
value.length())to_fmt.setMinimumFractionDigits(decAt);
decAt = 0;
- for ( int i = 0; i < value.length() && value.charAt( i ) != dec;
i++ ) {
- if ( Character.isDigit( value.charAt( i ) ) ) decAt++;
+ for (int i = 0; i < value.length() && value.charAt(i) != dec;
i++) {
+ if (Character.isDigit(value.charAt(i))) {
+ decAt++;
+ }
}
- to_fmt.setMinimumIntegerDigits( decAt );
- if ( value.charAt( value.length() - 1 ) == dec ) appendDec =
true;
- } else if ( subType.equals( "currency" ) ) {
- to_fmt = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
- } else if ( subType.equals( "int-currency" ) ) {
- to_fmt = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
+
+ to_fmt.setMinimumIntegerDigits(decAt);
+ if (value.charAt(value.length() - 1) == dec) {
+ appendDec = true;
+ }
+ } else if (subType.equals("currency")) {
+ to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
+ } else if (subType.equals("int-currency")) {
+ to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
int_currency = 1;
- for ( int i = 0; i < to_fmt.getMaximumFractionDigits(); i++ )
+ for (int i = 0; i < to_fmt.getMaximumFractionDigits(); i++) {
int_currency *= 10;
- } else if ( subType.equals( "percent" ) ) {
- to_fmt = (DecimalFormat) NumberFormat.getPercentInstance( loc );
+ }
+ } else if (subType.equals("percent")) {
+ to_fmt = (DecimalFormat)NumberFormat.getPercentInstance(loc);
}
// pattern overwrites locale format
- if ( pattern != null ) {
- to_fmt.applyPattern( pattern );
+ if (pattern != null) {
+ to_fmt.applyPattern(pattern);
}
- if ( value == null ) {
- numberValue = new Long( 0 );
+ if (value == null) {
+ numberValue = new Long(0);
} else {
try {
- numberValue = from_fmt.parse( value );
- if ( int_currency > 0 )
- numberValue = new Double( numberValue.doubleValue() /
- int_currency );
- else {
-
+ numberValue = from_fmt.parse(value);
+ if (int_currency > 0) {
+ numberValue = new Double(numberValue.doubleValue() /
+ int_currency);
+ } else {
+ // what?
}
- } catch ( ParseException pe ) {
- throw new SAXException( this.getClass().getName()
- + "i18n:number - parsing error.", pe
);
+ } 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 );
- if ( appendDec ) result = result + dec;
- debug( "i18n:number result: " + result );
+ String result = to_fmt.format(numberValue);
+ if (appendDec) result = result + dec;
+ debug("i18n:number result: " + result);
return result;
}
- /**
- * Helper method to retrieve a message from the dictionary
- */
- private String getString( String key ) {
+ // Helper method to retrieve a message from the dictionary
+ // Returnes null if no message is found
+ private String getString(String key) {
+ return getString(key, null);
+ }
+
+ // Helper method to retrieve a message from the dictionary.
+ // A default value is returned if message is not found
+ private String getString(String key, String defaultValue) {
+ try {
+ String value = dictionary.getString(
+ I18N_CATALOGUE_PREFIX + "[EMAIL PROTECTED]'" + key + "']"
+ );
- return dictionary.getString(
- new StringBuffer( I18N_CATALOGUE_PREFIX ).append( "[EMAIL
PROTECTED]'" ).append( key ).append( "']" ).toString()
- );
+ return value != null ? value : defaultValue;
+ } catch (MissingResourceException e) {
+ return defaultValue;
+ }
}
- private void setLocale( Locale locale ) {
+ private void setLocale(Locale locale) {
this.locale = locale;
- lang = locale.getLanguage();
- formatter.setLocale( locale );
}
- /**
- * Helper method to debug messages
- */
- private void debug( String msg ) {
- getLogger().debug( "I18nTransformer: " + msg );
+ // Helper method to debug messages
+ private void debug(String msg) {
+ getLogger().debug("I18nTransformer: " + msg);
}
- /*
- *
- 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);
- }
- }
- */
-
public void recycle() {
-
// restore untranslated-text if necessary
- if ( globalUntranslated != null &&
- !untranslated.equals( globalUntranslated )
- ) {
+ if (globalUntranslated != null &&
+ !untranslated.equals(globalUntranslated)) {
+
untranslated = globalUntranslated;
- debug( "untranslated-text restored to " + untranslated );
}
+
factory.release(dictionary);
dictionary = null;
}
@@ -1216,4 +1480,36 @@
factory.dispose();
factory = 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];n
+ 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);
+ }
+ }
+*/
}
Index: src/webapp/i18n/simple.xml
===================================================================
RCS file: /home/cvspublic/xml-cocoon2/src/webapp/i18n/simple.xml,v
retrieving revision 1.1
diff -u -r1.1 simple.xml
--- src/webapp/i18n/simple.xml 3 Jan 2002 12:31:42 -0000 1.1
+++ src/webapp/i18n/simple.xml 29 Jan 2002 12:59:10 -0000
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:i18n="http://apache.org/cocoon/i18n/2.0">
<title>
- <i18n:text>titletext</i18n:text>
+ <i18n:text>titletext</i18n:text>
</title>
<sub-title>
- <i18n:date pattern="EEE, MMMM dd, yyyy zzz" />
- </sub-title>
+ <i18n:date-time pattern="FULL"/>
+ </sub-title>
<annotation>
- <i18n:text>doclink</i18n:text>
+ <i18n:text>doclink</i18n:text>
<link>
<href>http://xml.apache.org/cocoon/userdocs/transformers/i18n-transformer.html</href>
<title>Cocoon 2 Web Site</title>
</link>
- </annotation>
+ </annotation>
<menu>
<item>
<title><i18n:text>language</i18n:text></title>
@@ -40,7 +40,7 @@
<i18n:text>language3</i18n:text>
</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=<i18n:text>lang_id4</i18n:text></href>
@@ -48,7 +48,7 @@
<i18n:text>language4</i18n:text>
</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=<i18n:text>lang_id5</i18n:text></href>
@@ -56,7 +56,7 @@
<i18n:text>language5</i18n:text>
</title>
</link>
- </item>
+ </item>
</menu>
<menu>
<item>
@@ -70,20 +70,20 @@
<href>simple.xml?locale=en_GB</href>
<title>English (GB)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=ru_RU</href>
<title>Russian (Russia)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=de_AT_EURO</href>
<title>German (Austria, Euro)</title>
</link>
</item>
- </menu>
+ </menu>
<content>
<para title="first" name="article" i18n:attr="title name">
<i18n:text i18n:key="a_key">article_text1</i18n:text>
@@ -112,12 +112,12 @@
<para title="Number formatting (not translated)"
name="article" i18n:attr="name">
<i18n:translate>
Number : {0} | Currency: {1} | Percent:
{2}, processed on: {3}
- <i18n:param type="number"
value="1703.74" />
- <i18n:param type="number"
sub-type="currency">27.24</i18n:param>
- <i18n:param type="number"
sub-type="percent">1.2</i18n:param>
- <i18n:param type="date" />
+ <i18n:param type="number"
src-locale="en" value="1703.74" />
+ <i18n:param type="number"
sub-type="currency" src-locale="en">27.24</i18n:param>
+ <i18n:param type="number"
sub-type="percent" src-locale="en">1.2</i18n:param>
+ <i18n:param type="date-time"
pattern="MEDIUM"/>
</i18n:translate>
- </para>
+ </para>
</content>
<bottom>
<copyright>
Index: src/webapp/i18n/simple.xsp
===================================================================
RCS file: /home/cvspublic/xml-cocoon2/src/webapp/i18n/simple.xsp,v
retrieving revision 1.1
diff -u -r1.1 simple.xsp
--- src/webapp/i18n/simple.xsp 3 Jan 2002 12:31:42 -0000 1.1
+++ src/webapp/i18n/simple.xsp 29 Jan 2002 12:59:11 -0000
@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsp:page language="java" xmlns:xsp="http://apache.org/xsp"
xmlns:xsp-request="http://apache.org/xsp/request/2.0"
xmlns:i18n="http://apache.org/cocoon/i18n/2.0">
- <xsp:structure>
- <xsp:include>java.text.SimpleDateFormat</xsp:include>
- <xsp:include>java.util.Locale</xsp:include>
- </xsp:structure>
- <xsp:logic>
+<xsp:logic>
private static int count = 0;
</xsp:logic>
<root>
@@ -13,11 +9,7 @@
synchronized (this) {
count++;
}
-
- Locale loc = request.getLocale();
-
- SimpleDateFormat df = new SimpleDateFormat("EEEE, MMMM dd, yyyy
H:mm:ss", loc);
- </xsp:logic>
+ </xsp:logic>
<title>
<i18n:text>titletext</i18n:text>
</title>
@@ -31,11 +23,15 @@
<sub-title>
<i18n:translate>
<i18n:text>count_title</i18n:text>
- <i18n:param>
+ <i18n:param type="number" pattern="000000">
<xsp:expr>count</xsp:expr>
</i18n:param>
- <i18n:param>
- <xsp:expr>df.format(new
Date())</xsp:expr>
+ <i18n:param type="date-time"
+ src-pattern="EEE MMM dd hh:mm:ss zzz yyyy"
+ src-locale="en"
+ pattern="MEDIUM"
+ >
+ <xsp:expr>new Date()</xsp:expr>
</i18n:param>
</i18n:translate>
</sub-title>
@@ -98,20 +94,20 @@
<href>simple.xsp?locale=en_GB</href>
<title>English (GB)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xsp?locale=ru_RU</href>
<title>Russian (Russia)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xsp?locale=de_AT_EURO</href>
<title>German (Austria, Euro)</title>
</link>
- </item>
- </menu>
+ </item>
+ </menu>
<content>
<para title="first" name="article" i18n:attr="title
name">
<i18n:text
i18n:key="a_key">article_text1</i18n:text>
@@ -123,9 +119,7 @@
<i18n:translate>
<i18n:text>Hello, {0}! Glad to see
you!</i18n:text>
<i18n:param name="username">
- <i18n:text>
<xsp-request:get-parameter name="user" default="none" as="string"/>
- </i18n:text>
</i18n:param>
</i18n:translate>
</para>
@@ -148,12 +142,16 @@
<para title="Number formatting (not translated)"
name="article" i18n:attr="name">
<i18n:translate>
Number : {0} | Currency: {1} | Percent:
{2}, processed on: {3}
- <i18n:param type="number"
value="1703.74" />
- <i18n:param type="number"
sub-type="currency">27.24</i18n:param>
- <i18n:param type="number"
sub-type="percent">1.2</i18n:param>
- <i18n:param type="date" />
+ <i18n:param type="number"
src-locale="en">
+ <xsp:expr>17 + 3 + 1974</xsp:expr>
+ </i18n:param>
+ <i18n:param type="number"
sub-type="currency" src-locale="en">
+ <xsp:expr>2002 / 28</xsp:expr>
+ </i18n:param>
+ <i18n:param type="number"
sub-type="percent" src-locale="en">1.2</i18n:param>
+ <i18n:param type="date-time"
pattern="MEDIUM"/>
</i18n:translate>
- </para>
+ </para>
</content>
<bottom>
<copyright>
Index: src/webapp/i18n/translations/messages.xml
===================================================================
RCS file:
/home/cvspublic/xml-cocoon2/src/webapp/i18n/translations/messages.xml,v
retrieving revision 1.1
diff -u -r1.1 messages.xml
--- src/webapp/i18n/translations/messages.xml 3 Jan 2002 12:31:42 -0000
1.1
+++ src/webapp/i18n/translations/messages.xml 29 Jan 2002 12:59:11 -0000
@@ -27,7 +27,7 @@
<message key="article_text1">This is a i18n paragraph.</message>
<message key="article_text2">This is another i18n paragraph and is also
a cool one.</message>
<message key="copyright">Copyright © 2001 Konstantin Piroumian. No
rights are reserved.</message>
- <message key="Hello">Hello, {0}! Glad to see you!</message>
+ <message key="Hello, {0}! Glad to see you!">Hello, {0}! Glad to see
you!</message>
<message key="Kot">Tomcat</message>
<message key="none">None</message>
<message key="one">one</message>
Index: src/webapp/i18n/translations/messages_en.xml
===================================================================
RCS file:
/home/cvspublic/xml-cocoon2/src/webapp/i18n/translations/messages_en.xml,v
retrieving revision 1.1
diff -u -r1.1 messages_en.xml
--- src/webapp/i18n/translations/messages_en.xml 3 Jan 2002 12:31:42
-0000 1.1
+++ src/webapp/i18n/translations/messages_en.xml 29 Jan 2002 12:59:11
-0000
@@ -27,7 +27,7 @@
<message key="article_text1">This is a i18n paragraph.</message>
<message key="article_text2">This is another i18n paragraph and is also
a cool one.</message>
<message key="copyright">Copyright © 2001 Konstantin Piroumian. No
rights are reserved.</message>
- <message key="Hello">Hello, {0}! Glad to see you!</message>
+ <message key="Hello, {0}! Glad to see you!">Hello, {0}! Glad to see
you!</message>
<message key="Kot">Tomcat</message>
<message key="none">None</message>
<message key="one">one</message>
1.5 +1063 -767
xml-cocoon2/src/java/org/apache/cocoon/transformation/I18nTransformer.java
Index: I18nTransformer.java
===================================================================
RCS file:
/home/cvs/xml-cocoon2/src/java/org/apache/cocoon/transformation/I18nTransformer.java,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- I18nTransformer.java 25 Jan 2002 03:58:30 -0000 1.4
+++ I18nTransformer.java 30 Jan 2002 14:56:12 -0000 1.5
@@ -9,7 +9,30 @@
*/
package org.apache.cocoon.transformation;
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.DecimalFormat;
+import java.text.MessageFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Set;
+import java.util.StringTokenizer;
+
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
import org.apache.avalon.excalibur.pool.Poolable;
+import org.apache.avalon.excalibur.pool.Recyclable;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.component.ComponentManager;
import org.apache.avalon.framework.component.Composable;
@@ -18,1075 +41,1330 @@
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.configuration.DefaultConfiguration;
import org.apache.avalon.framework.parameters.Parameters;
-import org.apache.avalon.excalibur.pool.Recyclable;
+
import org.apache.cocoon.ProcessingException;
import org.apache.cocoon.ResourceNotFoundException;
import org.apache.cocoon.acting.LocaleAction;
import org.apache.cocoon.environment.Source;
import org.apache.cocoon.environment.SourceResolver;
+import org.apache.cocoon.i18n.I18nUtils;
import org.apache.cocoon.i18n.XMLResourceBundle;
import org.apache.cocoon.i18n.XMLResourceBundleFactory;
-import org.apache.regexp.RE;
-import org.xml.sax.Attributes;
-import org.xml.sax.SAXException;
-import org.xml.sax.helpers.AttributesImpl;
-
-import java.io.IOException;
-import java.text.*;
-import java.util.*;
/**
- * Internationalisation transformer. Used to transform i18n markup into text
+ * Internationalization transformer is used to transform i18n markup into
text
* based on a particular locale.
*
- * <p>
- *
- * The i18n transformer works by obtaining the users locale via
- * <code>getLocale()</code> in the LocaleAction.
- * (@see org.apache.cocoon.acting.LocaleAction). It them attempts to find a
- * message catalogue that satisifies the particular locale, and use it for
- * for text replacement within i18n markup.
- *
- * <p>
- *
- * Catalogues are maintained in separate files, with a naming convention
- * similar to that of ResourceBundle (@see java.util.ResourceBundle). ie.
+ * <h3>i18n transformer</h3>
+ * <p>The <strong>i18n transformer</strong> works by obtaining the users
locale
+ * based on request, session attributes or a cookie data. See
+ * [EMAIL PROTECTED] org.apache.cocoon.acting.LocaleAction#getLocale(Map) }
for details.
+ * It then attempts to find a <strong>message catalogue</strong> that
satisifies
+ * the particular locale, and use it for for text replacement within i18n
markup.
+ * <p>Catalogues are maintained in separate files, with a naming convention
+ * similar to that of ResourceBundle (See java.util.ResourceBundle).
+ * ie.
* <strong>basename</strong>_<strong>locale</strong>, where <i>basename</i>
* can be any name, and <i>locale</i> can be any locale specified using
- * ISO 639/3166 characters (eg. en_AU, de_AT, es).
- *
- * <p>
- *
- * Catalogues are of the following format:
+ * ISO 639/3166 characters (eg. en_AU, de_AT, es).<br/>
+ * <strong>NOTE: </strong>ISO 639 is not a stable standard; some of the
language
+ * codes it defines (specifically iw, ji, and in) have changed
+ * (see java.util.Locale for details).
*
+ * <h3>Catalogues</h3>
+ * <p>Catalogues are of the following format:
* <pre>
* <?xml version="1.0"?>
* <!-- message catalogue file for locale ... -->
- *
* <catalogue xml:lang="locale">
* <message key="key">text</message>
* ....
* </catalogue>
- * </pre>
- *
- * Where <strong>key</strong> specifies a particular message for that
+ * </pre> Where <strong>key</strong> specifies a particular message for that
* language.
*
- * <p>
- *
- * Files to be translated contain the following markup:
- *
+ * <h3>Usage</h3>
+ * <p>Files to be translated contain the following markup:
* <pre>
* <?xml version="1.0"?>
- *
* ... some text, translate <i18n:text>key</i18n:text>
* </pre>
- *
* At runtime, the i18n transformer will find a message catalogue for the
* user's locale, and will appropriately replace the text between the
* <code><i18n:text></code> markup, using the value between the tags as
* the lookup key.
- *
- * <p>
- *
- * If the i18n transformer cannot find an appropriate message catalogue for
+ * <p>If the i18n transformer cannot find an appropriate message catalogue
for
* the user's given locale, it will recursively try to locate a <i>parent</i>
* message catalogue, until a valid catalogue can be found.
- *
- * ie:
- *
+ * ie:
* <ul>
*
<li><strong>catalogue</strong>_<i>language</i>_<i>country</i>_<i>variant</i>.xml
* <li><strong>catalogue</strong>_<i>language</i>_<i>country</i>.xml
* <li><strong>catalogue</strong>_<i>language</i>.xml
* <li><strong>catalogue</strong>.xml
* </ul>
- *
* eg: Assuming a basename of <i>messages</i> and a locale of <i>en_AU</i>
* (no variant), the following search will occur:
- *
* <ul>
* <li><strong>messages</strong>_<i>en</i>_<i>AU</i>.xml
* <li><strong>messages</strong>_<i>en</i>.xml
* <li><strong>messages</strong>.xml
* </ul>
- *
* This allows the developer to write a hierarchy of message catalogues,
* at each defining messages with increasing depth of variation.
*
- * <p>
- *
- * Sitemap configuration:
- *
+ * <h3>Sitemap configuration</h3>
* <pre>
- * <map:transformer name="i18n"
src="org.apache.cocoon.transformation.I18nTransformer">
- * <catalogue-name>messages</catalogue-name>
- * <catalogue-location>translations</catalogue-location>
- * <untranslated-text>untranslated</untranslated-text>
- * <cache-at-startup>true</cache-at-startup>
- * </map:transformer>
- * </pre>
+ * <map:transformer name="i18n"
+ * src="org.apache.cocoon.transformation.I18nTransformer">
*
+ * <catalogue-name>messages</catalogue-name>
+ * <catalogue-location>translations</catalogue-location>
+ * <untranslated-text>untranslated</untranslated-text>
+ * <cache-at-startup>true</cache-at-startup>
+ * </map:transformer>
+ * </pre> where:
* <ul>
- * <li><strong>catalogue-name</strong>: base name of the message
+ * <li><strong>catalogue-name</strong>: base name of the message
* catalogue (<i>mandatory</i>).
- * <li><strong>catalogue-location</strong>: location of the
+ * <li><strong>catalogue-location</strong>: location of the
* message catalogues (<i>mandatory</i>).
- * <li><strong>untranslated-text</strong>: default text used for
+ * <li><strong>untranslated-text</strong>: default text used for
* untranslated keys (default is 'untranslated-text').
- * <li><strong>cache-at-startup</strong>: flag whether to cache
+ * <li><strong>cache-at-startup</strong>: flag whether to cache
* messages at startup (false by default).
* </ul>
- *
- * <p>
- *
- * To use the transformer in a pipeline, simply specify it in a particular
+ * <p>To use the transformer in a pipeline, simply specify it in a particular
* transform. eg:
- *
* <pre>
* <map:match pattern="file">
- * <map:generate src="file.xml"/>
- * <map:transform type="i18n"/>
- * <map:serialize/>
+ * <map:generate src="file.xml"/>
+ * <map:transform type="i18n"/>
+ * <map:serialize/>
* </map:match>
* </pre>
*
- * <p/>
- *
- * Note, <strong>catalogue-name</strong>, <strong>catalogue-location</strong>
+ * <p>Note, <strong>catalogue-name</strong>,
<strong>catalogue-location</strong>
* and <strong>untranslated-text</strong> can all be overridden at the
* pipeline level by specifying them as parameters to the transform
statement.
*
+ * <p>For date, time and number formatting use the following tags:
* <ul>
- * <li><strong><i18n:date/></strong> gives now only the date.</li>
- * <li><strong><i18n:date-time/></strong> gives the date and time.</li>
- * <li><strong><i18n:time/></strong> gives the time.</li>
- * <li>For date, date-time and time the pattern and src-pattern attribute
- * may have also values of: "short", "medium",
- * "long" or "full".</li>
+ * <li><strong><i18n:date/></strong> gives localized date.</li>
+ * <li><strong><i18n:date-time/></strong> gives localized date and
time.</li>
+ * <li><strong><i18n:time/></strong> gives localized time.</li>
* </ul>
- * <p/>
- * <ul>
- * <li>for date, date-time, time and number a different locale and
- * source-locale can be specified:
- * <i18n:date src-pattern="short" src-locale="en_US" locale="de_DE">
+ * For <code>date</code>, <code>date-time</code> and <code>time</code> the
+ * <code>pattern</code> and <code>src-pattern</code> attribute may have also
+ * values of: <code>short</code>, <code>medium</code>, <code>long</code> or
+ * <code>full</code>. (See java.text.DateFormat for more info on this).
+ * <p>For <code>date</code>, <code>date-time</code>, <code>time</code> and
+ * <code>number</code> a different <code>locale</code> and
+ * <code>source-locale</code> can be specified:
+ * <pre>
+ * <i18n:date src-pattern="short" src-locale="en_US" locale="de_DE">
* 12/24/01
- * </i18n:date>
- * will result in 24.12.2001</li>
- * <li>A given real pattern and src-pattern (not short, medium, long, full)
- * overwrites the locale and src-locale</li>
- * </ul>
- * <p/>
- * Future work coming:
+ * </i18n:date>
+ * </pre> will result in 24.12.2001.
+ *
+ * <p>A given real <code>pattern</code> and <code>src-pattern</code> (not
+ * <code>short, medium, long, full</code>) overwrites the
+ * <code>locale</code> and <code>src-locale</code>
*
+ * <p>Future work coming:
* <ul>
- * <li>Many clean ups :-)
+ * <li>Many clean ups ...done on 23-Jan-2002 ;)
+ * <li>Introduce empty translation (XMLResourceBundle)
+ * <li>Remove 'sub-type' attribute (add possible values to 'type')
+ * <li>Introduce new <get-locale /> element
+ * <li>Introduce new 'currency-no-unit' and 'int-currency-no-unit' types
+ * <li>Fix resources disposal
+ * <li>A little clean ups... ;)
* </ul>
*
- * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Konstantin Piroumian</a>
- * @author <a href="mailto:[EMAIL PROTECTED]">Lassi Immonen</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Marcus Crafter</a>
* @author <a href="mailto:[EMAIL PROTECTED]">Michael Enke</a>
+ * @author <a href="mailto:[EMAIL PROTECTED]">Lassi Immonen</a>
+ *
+ * @todo Move all formatting/parsing routines to I18nUtils
*/
public class I18nTransformer extends AbstractTransformer
- implements Composable, Poolable, Configurable, Recyclable,
Disposable {
-
- private static final String FILE = "file:";
-
- protected ComponentManager manager;
+ implements Composable, Poolable, Configurable, Recyclable, Disposable {
/**
- * The namespace for i18n is "http://apache.org/cocoon/i18n/2.0"
+ * The namespace for i18n is "http://apache.org/cocoon/i18n/2.0".
*/
public static final String I18N_NAMESPACE_URI =
- "http://apache.org/cocoon/i18n/2.0";
+ "http://apache.org/cocoon/i18n/2.0";
//
- // Dictionary elements and attributes
+ // i18n elements
//
- public static final String I18N_DICTIONARY_ELEMENT = "dictionary";
- public static final String I18N_ENTRY_ELEMENT = "entry";
- public static final String I18N_KEY_ELEMENT = "key";
- public static final String I18N_TRANSLATION_ELEMENT = "translation";
-
- //
- // Text elements and attributes
- //
- public static final String I18N_LANG = "lang";
- public static final String I18N_KEY_ATTRIBUTE = "key";
- public static final String I18N_ATTR_ATTRIBUTE = "attr";
- public static final String I18N_TEXT_ELEMENT = "text";
- public static final String I18N_TRANSLATE_ELEMENT = "translate";
- public static final String I18N_PARAM_ELEMENT = "param";
- public static final String I18N_DATE_ELEMENT = "date";
- public static final String I18N_DATE_TIME_ELEMENT = "date-time";
- public static final String I18N_TIME_ELEMENT = "time";
- public static final String I18N_NUMBER_ELEMENT = "number";
-
- // number and date formatting attributes
- public static final String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
- public static final String I18N_PATTERN_ATTRIBUTE = "pattern";
- public static final String I18N_VALUE_ATTRIBUTE = "value";
- public static final String I18N_LOCALE_ATTRIBUTE = "locale";
- public static final String I18N_SRC_LOCALE_ATTRIBUTE = "src-locale";
-
- // configuration parameters
- public static final String I18N_CATALOGUE_NAME = "catalogue-name";
- public static final String I18N_CATALOGUE_LOCATION =
"catalogue-location";
- public static final String I18N_CATALOGUE_PREFIX = "/catalogue/message";
- public static final String I18N_UNTRANSLATED = "untranslated-text";
- public static final String I18N_CACHE_STARTUP = "cache-at-startup";
/**
- * <code>sub-type</code> attribute is used with <code>i18:number</code>
to
- * indicate a sub-type: <code>currency</code>, <code>int-currency</code>
- * or <code>percent</code>.
+ * i18n:text element is used to translate simple text, e.g.:<br/>
+ * <pre>
+ * <i18n:text>This is a multilanguage string</i18n:text>
+ * </pre>
+ */
+ public static final String I18N_TEXT_ELEMENT = "text";
+
+ /**
+ * i18n:translate element is used to translate text with parameter
+ * substitution, e.g.:<br/>
+ * <pre>
+ * <i18n:translate>
+ * This is a multilanguage string with {0} param
+ * <i18n:param>1</i18n:param>
+ * </i18n:translate>
+ * </pre>
+ *
+ * @see #I18N_TEXT_ELEMENT
+ * @see #I18N_PARAM_ELEMENT
*/
- public static final String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
+ public static final String I18N_TRANSLATE_ELEMENT = "translate";
/**
- * <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.
+ * i18n:param is used with i18n:translate to provide substitution params.
+ * The param can have i18n:text as its value to provide multilungual
value.
+ * Parameters can have additional attributes to be used for formatting:
+ * <ul>
+ * <li><code>type</code> - can be <code>date, date-time, time or
+ * number</code>. Used to format params before substitution.
+ * </li>
+ * <li><code>sub-type</code> - can be <code>currency, percent</code>
+ * and used with <code>number</code> type to format a given number
+ * value as a currency or percent.
+ * </li>
+ * <li><code>value</code> - the value of the param. If no value is
+ * specified then the text inside of the param element will be used.
+ * </li>
+ * <li><code>locale</code> - used only with <code>number, date,
time,
+ * date-time</code> types and used to override the current locale to
+ * format the given value.
+ * </li>
+ * <li><code>src-locale</code> - used with <code>number, date, time,
+ * date-time</code> types and specify the locale that should be
used to
+ * parse the given value.
+ * </li>
+ * <li><code>pattern</code> - used with <code>number, date, time,
+ * date-time</code> types and specify the pattern that should be
used
+ * to format the given value.
+ * </li>
+ * <li><code>src-pattern</code> - used with <code>number, date,
time,
+ * date-time</code> types and specify the pattern that should be
used
+ * to parse the given value.
+ * </li>
+ * </ul>
+ * @see #I18N_TRANSLATE_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_PARAM_ELEMENT = "param";
+
+ /**
+ * i18n:date is used to provide a localized date string. Allowed
attributes
+ * are: <code>pattern, src-pattern, locale, src-locale</code>
+ * Usage examples:
+ * <pre>
+ * <i18n:date src-pattern="short" src-locale="en_US"
locale="de_DE">
+ * 12/24/01
+ * </i18n:date>
+ *
+ * <i18n:date pattern="dd/MM/yyyy" />
+ * </pre>
+ *
+ * If no value is specified then the current date will be used. E.g.:
+ * <pre>
+ * <i18n:date />
+ * </pre> displays the current date formatted with default pattern for
+ * the current locale.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_DATE_ELEMENT = "date";
+
+ /**
+ * i18n:date-time is used to provide a localized date and time string.
+ * Allowed attributes are: <code>pattern, src-pattern, locale,
+ * src-locale</code>
+ * Usage examples:
+ * <pre>
+ * <i18n:date-time src-pattern="short" src-locale="en_US"
locale="de_DE"
+ * >
+ * 12/24/01 1:00 AM
+ * </i18n:date>
+ *
+ * <i18n:date-time pattern="dd/MM/yyyy hh:mm" />
+ * </pre>
+ *
+ * If no value is specified then the current date and time will be used.
+ * E.g.:
+ * <pre>
+ * <i18n:date-time />
+ * </pre> displays the current date formatted with default pattern for
+ * the current locale.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_DATE_TIME_ELEMENT = "date-time";
+
+ /**
+ * i18n:time is used to provide a localized time string. Allowed
attributes
+ * are: <code>pattern, src-pattern, locale, src-locale</code>
+ * Usage examples:
+ * <pre>
+ * <i18n:time src-pattern="short" src-locale="en_US"
locale="de_DE">
+ * 1:00 AM
+ * </i18n:time>
+ *
+ * <i18n:time pattern="hh:mm:ss" />
+ * </pre>
+ *
+ * If no value is specified then the current time will be used. E.g.:
+ * <pre>
+ * <i18n:time />
+ * </pre> displays the current time formatted with default pattern for
+ * the current locale.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_TIME_ELEMENT = "time";
+
+ /**
+ * i18n:number is used to provide a localized number string. Allowed
+ * attributes are: <code>pattern, src-pattern, locale, src-locale,
sub-type
+ * </code>
+ * Usage examples:
+ * <pre>
+ * <i18n:number src-pattern="short" src-locale="en_US"
locale="de_DE">
+ * 1000.0
+ * </i18n:number>
+ *
+ * <i18n:number sub-type="currency" />
+ * </pre>
+ *
+ * If no value is specifies then 0 will be used.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
*/
- public static final String I18N_TYPE_ATTRIBUTE = "type";
+ public static final String I18N_NUMBER_ELEMENT = "number";
- // States of the transformer
- private static final int STATE_OUTSIDE = 0;
- private static final int STATE_INSIDE_TEXT = 1;
- private static final int STATE_INSIDE_PARAM = 2;
- private static final int STATE_INSIDE_TRANSLATE = 3;
- private static final int STATE_INSIDE_TRANSLATE_TEXT = 4;
- private static final int STATE_TRANSLATE_KEY = 5;
- private static final int STATE_TRANSLATE_TEXT_KEY = 6;
- private static final int STATE_INSIDE_DATE = 7;
- private static final int STATE_INSIDE_DATE_TIME = 8;
- private static final int STATE_INSIDE_TIME = 9;
- private static final int STATE_INSIDE_NUMBER = 10;
+ /** @todo Implement currency element */
+ public static final String I18N_CURRENCY_ELEMENT = "currency";
+
+ /** @todo Implement percent element */
+ public static final String I18N_PERCENT_ELEMENT = "percent";
+
+ /** @todo Implemement integer currency element */
+ public static final String I18N_INT_CURRENCY_ELEMENT
+ = "int-currency";
+
+ /** @todo Implement currency without unit element */
+ public static final String I18N_CURRENCY_NO_UNIT_ELEMENT =
+ "currency-no-unit";
+
+ /** @todo Implement integer currency without unit element */
+ public static final String I18N_INT_CURRENCY_NO_UNIT_ELEMENT =
+ "int-currency-no-unit";
+
+ // i18n general attributes
/**
- * Current state of the transformer.
- * The value is STATE_OUTSIDE by default.
+ * This attribute is used with i18n:text element to indicate the key of
+ * the according message. The character data of the element will be used
+ * if no message is found by this key. E.g.:
+ * <pre>
+ * <i18n:text i18n:key="a_key">article_text1</i18n:text>
+ * </pre>
*/
- private int current_state = STATE_OUTSIDE;
+ public static final String I18N_KEY_ATTRIBUTE = "key";
/**
- * Previous state.
- * Used to translate text inside params and translate elements.
+ * This attribute is used with <strong>any</strong> element (even not
i18n)
+ * to translate attribute values. Should contain whitespace separated
+ * attribute names that should be translated. E.g.
+ * <pre>
+ * <para title="first" name="article" i18n:attr="title name" />
+ * </pre>
*/
- private int prev_state = STATE_OUTSIDE;
+ public static final String I18N_ATTR_ATTRIBUTE = "attr";
+
+ // i18n number and date formatting attributes
/**
- * 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.
+ * This attribute is used with date and number formatting elements to
+ * indicate the pattern that should be used to parse the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String current_key = null;
+ public static final String I18N_SRC_PATTERN_ATTRIBUTE = "src-pattern";
/**
- * Translated text inside the i18n:text element.
+ * This attribute is used with date and number formatting elements to
+ * indicate the pattern that should be used to format the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String translated_text = null;
+ public static final String I18N_PATTERN_ATTRIBUTE = "pattern";
/**
- * Translated text, ready for param substitution.
+ * This attribute is used with date and number formatting elements to
+ * indicate the locale that should be used to format the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String substitute_text = null;
+ public static final String I18N_LOCALE_ATTRIBUTE = "locale";
+
+ /**
+ * This attribute is used with date and number formatting elements to
+ * indicate the locale that should be used to parse the element value.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
+ */
+ public static final String I18N_SRC_LOCALE_ATTRIBUTE = "src-locale";
/**
- * Current parameter value (translated or not)
+ * This attribute is used with date and number formatting elements to
+ * indicate the value that should be parsed and formatted. If value
+ * attribute is not used then the character data of the element will be
used.
+ *
+ * @see #I18N_PARAM_ELEMENT
+ * @see #I18N_DATE_TIME_ELEMENT
+ * @see #I18N_DATE_ELEMENT
+ * @see #I18N_TIME_ELEMENT
+ * @see #I18N_NUMBER_ELEMENT
*/
- private String param_value = null;
+ public static final String I18N_VALUE_ATTRIBUTE = "value";
/**
- * @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>
+ * This attribute is used with <code>i18:param</code> to
+ * indicate the parameter type: <code>date, time, date-time</code> or
+ * <code>number, currency, percent, int-currency, currency-no-unit,
+ * int-currency-no-unit</code>.
*/
- private HashMap namedParams = null;
+ public static final String I18N_TYPE_ATTRIBUTE = "type";
/**
- * i18n:params are stored for index substitutions.
+ * This attribute is used with <code>i18:number</code> to
+ * indicate a sub-type: <code>currency</code>, <code>int-currency</code>
+ * or <code>percent</code>.
*/
- private ArrayList indexedParams = new ArrayList();
+ public static final String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
+
+ // �onfiguration parameters
/**
- * Message formatter for param substitution.
+ * This configuration parameter specifies the message catalog name.
*/
- private MessageFormat formatter = new MessageFormat( "" );
+ public static final String I18N_CATALOGUE_NAME = "catalogue-name";
/**
- * Current language id.
+ * This configuration parameter specifies the message catalog location
+ * relative to the current sitemap.
*/
- private String lang;
+ public static final String I18N_CATALOGUE_LOCATION =
"catalogue-location";
/**
- * Locale setting.
+ * This configuration parameter specifies the message that should be
+ * displayed in case of a not translated text (message not found).
*/
- private Locale locale, loc, srcLoc;
+ public static final String I18N_UNTRANSLATED =
"untranslated-text";
/**
- * Date element attributes and their values.
+ * This configuration parameter specifies if the message catalog should
be
+ * cached at startup.
*/
- private HashMap formattingParams;
+ public static final String I18N_CACHE_STARTUP = "cache-at-startup";
/**
- * Dictionary data.
+ * This constant specifies the XPath prefix that will be used
+ * to retrieve a value from a message catalogue. The resulting XPath is
+ * looks like this:
+ * <code>/catalogue/[EMAIL PROTECTED]'key_value']</code>
+ *<br/>
+ * FIXME (KP): We need a more generic way of key interpretation: to use
+ * XPath expression as keys, or keys with non-XML dictionaries.
*/
+ public static final String I18N_CATALOGUE_PREFIX =
"/catalogue/message";
+
+ // Used to check that the catalogue location is a directory.
+ // FIXME (KP): Why should it be a file? It can be any resource!
+ private static final String FILE = "file:";
+
+ // States of the transformer
+ private static final int STATE_OUTSIDE = 0;
+ private static final int STATE_INSIDE_TEXT = 1;
+ private static final int STATE_INSIDE_PARAM = 2;
+ private static final int STATE_INSIDE_TRANSLATE = 3;
+ private static final int STATE_INSIDE_TRANSLATE_TEXT = 4;
+ private static final int STATE_TRANSLATE_KEY = 5;
+ private static final int STATE_TRANSLATE_TEXT_KEY = 6;
+ private static final int STATE_INSIDE_DATE = 7;
+ private static final int STATE_INSIDE_DATE_TIME = 8;
+ private static final int STATE_INSIDE_TIME = 9;
+ private static final int STATE_INSIDE_NUMBER = 10;
+
+ // All date-time related parameter types and element names
+ private static final Set dateTypes;
+
+ // All number related parameter types and element names
+ private static final Set numberTypes;
+
+ // Date pattern types map: short, medium, long, full
+ private static final Map datePatterns;
+
+ static {
+ // initialize date types set
+ HashSet set = new HashSet();
+ set.add(I18N_DATE_ELEMENT);
+ set.add(I18N_TIME_ELEMENT);
+ set.add(I18N_DATE_TIME_ELEMENT);
+ dateTypes = Collections.unmodifiableSet(set);
+
+ // initialize number types set
+ set = new HashSet();
+ set.add(I18N_NUMBER_ELEMENT);
+ set.add(I18N_PERCENT_ELEMENT);
+ set.add(I18N_CURRENCY_ELEMENT);
+ set.add(I18N_INT_CURRENCY_ELEMENT);
+ set.add(I18N_CURRENCY_NO_UNIT_ELEMENT);
+ set.add(I18N_INT_CURRENCY_NO_UNIT_ELEMENT);
+ numberTypes = Collections.unmodifiableSet(set);
+
+ // Initialize date patterns map
+ Map map = new HashMap();
+ map.put("SHORT", new Integer(DateFormat.SHORT));
+ map.put("MEDIUM", new Integer(DateFormat.MEDIUM));
+ map.put("LONG", new Integer(DateFormat.LONG));
+ map.put("FULL", new Integer(DateFormat.FULL));
+ datePatterns = Collections.unmodifiableMap(map);
+ }
+
+ // Component manager for this component
+ private ComponentManager manager;
+
+ // Current state of the transformer. The value is STATE_OUTSIDE by
default.
+ private int current_state;
+
+ // Previous state. Used in text translation inside params and translate
+ // elements.
+ private int prev_state;
+
+ // 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;
+
+ // Translated text inside the i18n:text element.
+ private String translated_text;
+
+ // Translated text, ready for param substitution.
+ private String substitute_text;
+
+ // Current parameter value (translated or not)
+ private String param_value;
+
+ // i18n:params are stored for index substitutions.
+ private ArrayList indexedParams;
+
+ // Message formatter for param substitution.
+ private MessageFormat formatter;
+
+ // Current locale
+ private Locale locale;
+
+ // Date and number elements and params formatting attributes with values.
+ private HashMap formattingParams;
+
+ // Dictionary data
private XMLResourceBundle dictionary;
- // FIXME: Shouldn't factory be the global component?
+ // FIXME: Shouldn't factory be a global component?
// Now every I18nTransformer have own instance of factory
// every of which in turn have own file cache.
+ // FIXME (KP): Can we use static factory variable?
private XMLResourceBundleFactory factory = new
XMLResourceBundleFactory();
- /*
- * i18n configuration variables
- */
+ //
+ // i18n configuration variables
+ //
+
+ // Catalogue name value
private String catalogueName;
+
+ // Catalogue location value
private String catalogueLocation;
+
+ // Untranslated message value
private String untranslated;
- private String globalUntranslated;
+
+ // Cache at startup setting value
private boolean cacheAtStartup;
+ // Helper variable used to hold the old untranslated value
+ private String globalUntranslated;
+
/**
- * Configure this transformer.
+ * Returns the current locale setting of this transformer instance.
+ * @return current Locale object
*/
- public void configure( Configuration conf )
- throws ConfigurationException {
- if ( conf != null ) {
+ public Locale getLocale() {
+ return this.locale;
+ }
+ /**
+ * Configure this transformer.
+ */
+ public void configure(Configuration conf) throws ConfigurationException {
+ if (conf != null) {
// read in the config options from the transformer definition
// obtain the base name of the message catalogue
- Configuration child = conf.getChild( I18N_CATALOGUE_NAME );
- catalogueName = child.getValue( null );
- debug( "Default catalogue name is " + catalogueName );
+ Configuration child = conf.getChild(I18N_CATALOGUE_NAME);
+ catalogueName = child.getValue(null);
+ debug("Default catalogue name is " + catalogueName);
// obtain the directory location of message catalogues
- child = conf.getChild( I18N_CATALOGUE_LOCATION );
- catalogueLocation = child.getValue( null );
- debug( "Default catalogue location is " + catalogueLocation );
+ child = conf.getChild(I18N_CATALOGUE_LOCATION);
+ catalogueLocation = child.getValue(null);
+ debug("Default catalogue location is " + catalogueLocation);
// check our mandatory parameters
- if ( catalogueName == null || catalogueLocation == null )
+ if (catalogueName == null || catalogueLocation == null)
throw new ConfigurationException(
- "I18nTransformer requires the name and location of "
+
- "the message catalogues"
+ "I18nTransformer requires the name and location of " +
+ "the message catalogues"
);
// obtain default text to use for untranslated messages
- child = conf.getChild( I18N_UNTRANSLATED );
- untranslated = child.getValue( I18N_UNTRANSLATED );
- debug( "Default untranslated text is '" + untranslated + "'" );
+ child = conf.getChild(I18N_UNTRANSLATED);
+ untranslated = child.getValue(I18N_UNTRANSLATED);
+ debug("Default untranslated text is '" + untranslated + "'");
// obtain config option, whether to cache messages at startup
time
- child = conf.getChild( I18N_CACHE_STARTUP );
- cacheAtStartup = child.getValueAsBoolean( false );
- debug( ( cacheAtStartup ? "will" : "won't" ) +
+ child = conf.getChild(I18N_CACHE_STARTUP);
+ cacheAtStartup = child.getValueAsBoolean(false);
+ debug((cacheAtStartup ? "will" : "won't") +
" cache messages during startup, by default"
);
// activate resource bundle logging
- factory.setLogger( getLogger() );
+ factory.setLogger(getLogger());
}
}
/**
* Uses <code>org.apache.cocoon.acting.LocaleAction.getLocale()</code>
- * to get language user has selected.
+ * to get language user has selected.
+ * FIXME (KP): Why should I18nTransformer depend on an action?
*/
- public void setup( SourceResolver resolver, Map objectModel, String
source,
- Parameters parameters )
- throws ProcessingException, SAXException, IOException {
+ public void setup(SourceResolver resolver, Map objectModel, String
source,
+ Parameters parameters)
+ throws ProcessingException, SAXException, IOException {
try {
-
// check parameters to see if anything has been locally
overloaded
String localCatLocation = null;
String localCatName = null;
String localUntranslated = null;
- if ( parameters != null ) {
+ if (parameters != null) {
localCatLocation =
- parameters.getParameter( I18N_CATALOGUE_LOCATION, null );
+ parameters.getParameter(I18N_CATALOGUE_LOCATION, null);
localCatName =
- parameters.getParameter( I18N_CATALOGUE_NAME, null );
+ parameters.getParameter(I18N_CATALOGUE_NAME, null);
localUntranslated =
- parameters.getParameter( I18N_UNTRANSLATED, null );
+ parameters.getParameter(I18N_UNTRANSLATED, null);
}
- // if untranslated-text has been overridden, save the original
+ // if untranslated-text has been overridden, save the original
// value so it can be restored when this object is recycled.
- if ( localUntranslated != null ) {
+ // FIXME (KP): What about other overriden params?
+ if (localUntranslated != null) {
globalUntranslated = untranslated;
untranslated = localUntranslated;
}
- // Set current language and locale
- String lc = LocaleAction.getLocale( objectModel );
-
// configure the factory
- _setup( resolver,
- localCatLocation == null
- ? catalogueLocation : localCatLocation
- );
+ _setup(resolver, localCatLocation == null ? catalogueLocation
+ : localCatLocation);
+
+ // Get current locale
+ String lc = LocaleAction.getLocaleAttribute(objectModel);
+ Locale locale = I18nUtils.parseLocale(lc);
+ debug("using locale " + locale.toString());
// setup everything for the current locale
- String[] matches = new RE( "_" ).split( lc );
+ dictionary = (XMLResourceBundle) factory.select(
+ localCatName == null ? catalogueName : localCatName,
+ locale
+ );
+ debug( "selected dictionary " + dictionary );
- String l = matches.length > 0
- ? matches[0] : Locale.getDefault().getLanguage();
- String c = matches.length > 1 ? matches[1] : "";
- String v = matches.length > 2 ? matches[2] : "";
- Locale locale = new Locale( l, c, v );
-
- debug( "using locale " + locale.toString() );
-
- dictionary =
- (XMLResourceBundle) factory.select(
- localCatName == null ? catalogueName : localCatName,
- locale
- );
+ // Initialize instance state variables
- debug( "selected dictionary " + dictionary );
+ this.locale = locale;
+ this.current_state = STATE_OUTSIDE;
+ this.prev_state = STATE_OUTSIDE;
+ this.current_key = null;
+ this.translated_text = null;
+ this.substitute_text = null;
+ this.param_value = null;
+ this.indexedParams = new ArrayList();
+ this.formattingParams = null;
- setLocale( locale );
+ // Create and initialize a formatter
+ this.formatter = new MessageFormat("");
+ this.formatter.setLocale(locale);
- } catch ( Exception e ) {
- debug( "exception generated, leaving unconfigured" );
- throw new ProcessingException( e.getMessage(), e );
+ } catch (Exception e) {
+ debug("exception generated, leaving unconfigured");
+ throw new ProcessingException(e.getMessage(), e);
}
}
/**
- * Internal setup.
+ * Internal setup of XML resource bundle and factory.
*
* REVISIT: when we can get the resolver anywhere, we can pass the
* configuration object directly to XMLResourceBundle.
*/
- private void _setup( SourceResolver resolver, String location )
- throws Exception {
+ private void _setup(SourceResolver resolver, String location)
+ throws Exception {
// configure the factory to log correctly and cache catalogues
DefaultConfiguration configuration =
- new DefaultConfiguration( "name", "location" );
+ new DefaultConfiguration("name", "location");
DefaultConfiguration cacheConf =
- new DefaultConfiguration(
-
XMLResourceBundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
- "location"
- );
- cacheConf.setValue( new Boolean( cacheAtStartup ).toString() );
- configuration.addChild( cacheConf );
+ new DefaultConfiguration(
+ XMLResourceBundleFactory.ConfigurationKeys.CACHE_AT_STARTUP,
+ "location"
+ );
+ cacheConf.setValue(new Boolean(cacheAtStartup).toString());
+ configuration.addChild(cacheConf);
// set the root location for message catalogues
DefaultConfiguration dirConf =
- new DefaultConfiguration(
-
XMLResourceBundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
- "location"
- );
+ new DefaultConfiguration(
+ XMLResourceBundleFactory.ConfigurationKeys.ROOT_DIRECTORY,
+ "location"
+ );
- debug( "catalog location:" + location );
+ debug("catalog location:" + location);
Source source = resolver.resolve(location);
try {
String systemId = source.getSystemId();
- if ( !systemId.startsWith( FILE ) ) {
- throw new ResourceNotFoundException( systemId + " does not
denote a directory" );
+ if (!systemId.startsWith(FILE)) {
+ throw new ResourceNotFoundException(
+ systemId + " does not denote a directory"
+ );
}
- debug( "catalog directory:" + systemId );
- dirConf.setValue( systemId );
+ debug("catalog directory:" + systemId);
+ dirConf.setValue(systemId);
+ configuration.addChild(dirConf);
} finally {
source.recycle();
}
- configuration.addChild( dirConf );
- factory.configure( configuration );
-
- debug( "configured" );
+ // Pass created configuration object to the factory
+ factory.configure(configuration);
+ debug("configured");
}
- public void compose( ComponentManager manager ) {
+ public void compose(ComponentManager manager) {
this.manager = manager;
- factory.compose( manager );
+ factory.compose(manager);
}
- public void startElement( String uri, String name, String raw,
- Attributes attr ) throws SAXException {
+ //
+ // Standart SAX event handlers
+ //
- if ( I18N_NAMESPACE_URI.equals( uri ) ) {
- debug( "Starting i18n element: " + name );
- startI18NElement( name, attr );
+ public void startElement(String uri, String name, String raw,
+ Attributes attr) throws SAXException {
+
+ if (I18N_NAMESPACE_URI.equals(uri)) {
+ debug("Starting i18n element: " + name);
+ startI18NElement(name, attr);
return;
}
- super.startElement( uri, name, raw, translateAttributes( name, attr
) );
+ super.startElement(uri, name, raw, translateAttributes(name, attr));
}
- public void endElement( String uri, String name, String raw )
- throws SAXException {
+ public void endElement(String uri, String name, String raw)
+ throws SAXException {
- if ( I18N_NAMESPACE_URI.equals( uri ) ) {
- endI18NElement( name );
+ if (I18N_NAMESPACE_URI.equals(uri)) {
+ endI18NElement(name);
return;
}
- super.endElement( uri, name, raw );
+ super.endElement(uri, name, raw);
}
- public void characters( char[] ch, int start, int len ) throws
SAXException {
+ public void characters(char[] ch, int start, int len) throws
SAXException {
- if ( current_state != STATE_OUTSIDE ) {
- i18nCharacters( ch, start, len );
+ if (current_state != STATE_OUTSIDE) {
+ i18nCharacters(ch, start, len);
return;
}
- super.characters( ch, start, len );
+ super.characters(ch, start, len);
}
- // My own content handlers
+ //
+ // i18n specific event handlers
+ //
- private void startI18NElement( String name, Attributes attr )
- throws SAXException {
- 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." );
- }
+ private void startI18NElement(String name, Attributes attr)
+ throws SAXException {
- setFormattingParams( attr );
- current_state = STATE_INSIDE_DATE;
- } else if ( I18N_DATE_TIME_ELEMENT.equals( name ) ) {
- if ( current_state != STATE_OUTSIDE ) {
- throw new SAXException( this.getClass().getName()
- + ": i18n:date-time elements are
not allowed "
- + "inside of other i18n
elements." );
- }
+ debug("Start i18n element: " + name);
- setFormattingParams( attr );
- current_state = STATE_INSIDE_DATE_TIME;
- } else if ( I18N_TIME_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." );
- }
+ 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
+ );
+ }
- setFormattingParams( attr );
- current_state = STATE_INSIDE_TIME;
- } 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." );
- }
+ 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_DATE_TIME_ELEMENT.equals(name)) {
+ if (current_state != STATE_OUTSIDE) {
+ throw new SAXException(
+ this.getClass().getName()
+ + ": i18n:date-time elements are not allowed "
+ + "inside of other i18n elements."
+ );
+ }
- setFormattingParams( attr );
- current_state = STATE_INSIDE_NUMBER;
+ setFormattingParams(attr);
+ current_state = STATE_INSIDE_DATE_TIME;
+ } else if (I18N_TIME_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_TIME;
+ } 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."
+ );
}
- } 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 );
+
+ setFormattingParams(attr);
+ current_state = STATE_INSIDE_NUMBER;
}
}
- /**
- * Get src-pattern, pattern and value attribute values and store in a Map
- */
- private void setFormattingParams( Attributes attr ) throws SAXException {
- formattingParams = new HashMap( 3 );
+ // Get all possible i18n formatting attribute values and store in a Map
+ private void setFormattingParams(Attributes attr) throws SAXException {
+ formattingParams = new HashMap(3); // average number of attributes
is 3
- String attr_value = attr.getValue( I18N_SRC_PATTERN_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_SRC_PATTERN_ATTRIBUTE, attr_value );
+ String attr_value = attr.getValue(I18N_SRC_PATTERN_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_SRC_PATTERN_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_PATTERN_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_PATTERN_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_PATTERN_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_PATTERN_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_VALUE_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_VALUE_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_VALUE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_LOCALE_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_LOCALE_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_LOCALE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_LOCALE_ATTRIBUTE, attr_value);
}
- attr_value = attr.getValue( I18N_SRC_LOCALE_ATTRIBUTE );
- if ( attr_value != null ) {
- formattingParams.put( I18N_SRC_LOCALE_ATTRIBUTE, attr_value );
+ attr_value = attr.getValue(I18N_SRC_LOCALE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_SRC_LOCALE_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_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 );
+ 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 {
- 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:
- case STATE_INSIDE_DATE_TIME:
- case STATE_INSIDE_TIME:
- {
- endDate_TimeElement();
- 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 );
- }
- }
+ private void endI18NElement(String name) throws SAXException {
+ debug("End i18n element: " + name);
+ switch (current_state) {
+ case STATE_INSIDE_TEXT:
+ endTextElement();
+ break;
+ case STATE_INSIDE_TRANSLATE:
+ endTranslateElement();
+ break;
- /*
- */
- 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;
+ case STATE_INSIDE_PARAM:
+ endParamElement();
+ break;
+
+ case STATE_INSIDE_DATE:
+ case STATE_INSIDE_DATE_TIME:
+ case STATE_INSIDE_TIME:
+ endDate_TimeElement();
+ break;
+
+ case STATE_INSIDE_NUMBER:
+ endNumberElement();
+ break;
}
- String result = ( s + "!" ).trim();
- return result.substring( 0, result.length() - 1 );
}
- private void i18nCharacters( char[] ch, int start, int len )
- throws SAXException {
+ private void i18nCharacters(char[] ch, int start, int len)
+ throws SAXException {
- String key = new String( ch, start, len );
- key = stripWhitespace( key );
- if ( key == null || key.length() == 0 ) {
+ String textValue = new String(ch, start, len).trim();
+ if (textValue == null || textValue.length() == 0) {
return;
}
- debug( "i18n message key = '" + key + "'" );
+ debug( "i18n message text = '" + textValue + "'" );
- switch ( current_state ) {
- case STATE_INSIDE_TEXT:
- {
- if ( current_key != null ) {
- try {
- translated_text = getString( current_key );
- } catch ( MissingResourceException e ) {
- translated_text = untranslated;
- }
- if ( translated_text == null ) {
- translated_text = key;
- }
- current_key = null;
- } else {
- try {
- translated_text = getString( key );
- } catch ( MissingResourceException e ) {
- translated_text = untranslated;
- }
- }
-
- break;
- }
- case STATE_INSIDE_TRANSLATE:
- {
- // Store text for param substitution (do not translate)
- if ( substitute_text == null ) {
- substitute_text = key;
- }
- break;
+ switch (current_state) {
+ case STATE_INSIDE_TEXT:
+ if (current_key != null) { // if i18n:key attribute were used
+ translated_text = getString(current_key, untranslated);
+
+ // If no translation found and untranslated param is null
+ if (translated_text == null) {
+ translated_text = textValue; // use the key
}
- case STATE_INSIDE_PARAM:
- {
- // Store translation for param substitution
- if ( param_value == null ) {
- param_value = key;
- }
- break;
- }
- case STATE_INSIDE_DATE:
- case STATE_INSIDE_DATE_TIME:
- case STATE_INSIDE_TIME:
- case STATE_INSIDE_NUMBER:
- {
- if ( formattingParams != null ) {
- if ( formattingParams.get( I18N_VALUE_ATTRIBUTE ) ==
null ) {
- formattingParams.put( I18N_VALUE_ATTRIBUTE, key
);
- } else {
- // how to use the text inside of date element?
- }
- }
- break;
- }
- default:
- {
- throw new SAXException( this.getClass().getName()
- + "Something's really wrong!!!"
);
- }
+ // reset the key holding variable
+ current_key = null;
+ } else { // use text value as dictionary key
+ translated_text = getString(textValue, untranslated);
+ }
+ break;
+
+ case STATE_INSIDE_TRANSLATE:
+ // Store text for param substitution (do not translate)
+ if (substitute_text == null) {
+ substitute_text = textValue;
+ }
+ break;
+
+ case STATE_INSIDE_PARAM:
+ // Store translation for param substitution
+ if (param_value == null) {
+ param_value = textValue;
+ }
+ break;
+
+ case STATE_INSIDE_DATE:
+ case STATE_INSIDE_DATE_TIME:
+ case STATE_INSIDE_TIME:
+ case STATE_INSIDE_NUMBER:
+ if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null) {
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, textValue);
+ } else {
+ ; // ignore the text inside of date element
+ }
+ break;
+
+ default:
+ throw new IllegalStateException(
+ this.getClass().getName()
+ + " developer's fault");
}
}
- private Attributes translateAttributes( String name, Attributes attr )
- throws SAXException {
- if ( attr == null ) {
+ // Translate all attributes that are listed in i18n:attr attribute
+ private Attributes translateAttributes(String name, Attributes attr)
+ throws SAXException {
+
+ if (attr == null) {
return attr;
}
- AttributesImpl temp_attr = new AttributesImpl( 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 ) {
+ 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
) );
+ 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
+ temp_attr.removeAttribute(i18n_attr_index);
+
+ // iterate through listed attributes and translate them
+ while (st.hasMoreElements()) {
String attr_name = st.nextToken();
- int attr_index = temp_attr.getIndex( attr_name );
+ int attr_index = temp_attr.getIndex(attr_name);
+ if (attr_index != -1) {
+ String text2translate = temp_attr.getValue(attr_index);
+ String result = getString(text2translate, untranslated);
- if ( attr_index != -1 ) {
- String text2translate = temp_attr.getValue( attr_index );
- String result;
-
- try {
- result = getString( text2translate );
- } catch ( MissingResourceException e ) {
- result = untranslated;
- }
// set the translated value
- if ( result != null ) {
- temp_attr.setValue( attr_index, result );
+ if (result != null) {
+ temp_attr.setValue(attr_index, result);
} else {
- getLogger().warn( "translation not found for
attribute "
- + attr_name + " in element: " +
name );
+ getLogger().warn("translation not found for
attribute "
+ + attr_name + " in element: " +
name);
}
} else {
- getLogger().warn( "i18n attribute '" + attr_name
- + "' not found in element: " + name );
+ getLogger().warn("i18n attribute '" + attr_name
+ + "' not found in element: " + name);
}
}
+
return temp_attr;
}
+ // nothing to translate, just return
return attr;
}
private void endTextElement() throws SAXException {
- 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
- debug( "--- Translation not found! ---" );
- }
- break;
- }
- case STATE_INSIDE_TRANSLATE:
- {
- substitute_text = translated_text;
- break;
- }
- case STATE_INSIDE_PARAM:
- {
- param_value = translated_text;
- break;
- }
+ 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
+ debug( "--- Translation not found! ---" );
+ }
+ break;
+
+ case STATE_INSIDE_TRANSLATE:
+ substitute_text = translated_text;
+ break;
+
+ case STATE_INSIDE_PARAM:
+ param_value = translated_text;
+ break;
}
+
translated_text = null;
current_state = prev_state;
prev_state = STATE_OUTSIDE;
}
+ // Process substitution parameter
private void endParamElement() throws SAXException {
- debug( "Substitution param: " + param_value );
- if ( formattingParams != null ) {
- String paramType = (String) formattingParams.get(
I18N_TYPE_ATTRIBUTE );
- if ( paramType != null ) {
- debug( "Param type: " + paramType );
- if ( formattingParams.get( I18N_VALUE_ATTRIBUTE ) == null
- && param_value != null ) {
- debug( "Put param value: " + param_value );
- formattingParams.put( I18N_VALUE_ATTRIBUTE, param_value
);
- }
- if ( "date".equals( paramType ) ||
- "date-time".equals( paramType ) ||
- "time".equals( paramType ) ) {
- debug( "Formatting date_time param: " + formattingParams
);
- param_value = formatDate_Time( formattingParams );
- } else if ( "number".equals( paramType ) ) {
- debug( "Formatting number param: " + formattingParams );
- param_value = formatNumber( formattingParams );
- }
+ debug("Substitution param: " + param_value);
+
+ String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
+ if (paramType != null) {
+ // We have a typed parameter
+
+ debug("Param type: " + paramType);
+ if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null &&
+ param_value != null) {
+
+ debug("Put param value: " + param_value);
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
+ }
+
+ // Check if we have a date or a number parameter
+ if (dateTypes.contains(paramType)) {
+ debug("Formatting date_time param: " + formattingParams);
+ param_value = formatDate_Time(formattingParams);
+ } else if (numberTypes.contains(paramType)) {
+ debug("Formatting number param: " + formattingParams);
+ param_value = formatNumber(formattingParams);
}
}
- debug( "Added substitution param: " + param_value );
- indexedParams.add( param_value );
+
+ 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 ) {
+ if (substitute_text == null) {
return;
}
String result;
- if ( indexedParams.size() > 0 && substitute_text.length() > 0 ) {
- debug( "Text for susbtitution: " + substitute_text );
- result = formatter.format( substitute_text,
indexedParams.toArray() );
- debug( "Result of susbtitution: " + result );
+ if (indexedParams.size() > 0 && substitute_text.length() > 0) {
+ debug("Text for susbtitution: " + substitute_text);
+ result = formatter.format(substitute_text,
indexedParams.toArray());
+ debug("Result of susbtitution: " + result);
} else {
result = substitute_text;
}
- super.contentHandler.characters( result.toCharArray(), 0,
result.length() );
+ super.contentHandler.characters(result.toCharArray(), 0,
result.length());
indexedParams.clear();
substitute_text = null;
current_state = STATE_OUTSIDE;
}
private void endDate_TimeElement() throws SAXException {
- String result = formatDate_Time( formattingParams );
- super.contentHandler.characters( result.toCharArray(), 0,
result.length() );
+ String result = formatDate_Time(formattingParams);
+ super.contentHandler.characters(result.toCharArray(), 0,
result.length());
current_state = STATE_OUTSIDE;
}
- private Locale getLocale( Map params, String attribute ) {
- Locale locale = this.locale;
+ // Helper method: creates Locale object from a string value in a map
+ private Locale getLocale(Map params, String attribute) {
// the specific locale value
- String lc = (String) params.get( attribute );
- if ( lc != null )
- try {
-
- String[] matches = new RE( "_" ).split( lc );
- String l = matches.length > 0
- ? matches[0] : Locale.getDefault().getLanguage();
- String c = matches.length > 1 ? matches[1] : "";
- String v = matches.length > 2 ? matches[2] : "";
- locale = new Locale( l, c, v );
- } catch ( Exception e ) {
- }
- return locale;
- }
-
- private String formatDate_Time( Map params ) throws SAXException {
- SimpleDateFormat to_fmt = null, from_fmt;
- String element = null;
- int srcStyle = DateFormat.SHORT, style = DateFormat.SHORT;
- boolean realPattern = false, realSrcPattern = false;
-
- if ( params == null ) {
- throw new SAXException( this.getClass().getName()
- + ": i18n:" + element + " - error in
element attributes." );
- }
-
- loc = getLocale( params, I18N_LOCALE_ATTRIBUTE );
- srcLoc = getLocale( params, I18N_SRC_LOCALE_ATTRIBUTE );
-
- // 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 );
+ String lc = (String)params.get(attribute);
+ return I18nUtils.parseLocale(lc, this.locale);
+ }
- if ( srcPattern == null ) {
- srcStyle = DateFormat.DEFAULT;
- } else {
- if ( srcPattern.equalsIgnoreCase( "short" ) )
- srcStyle = DateFormat.SHORT;
- else if ( srcPattern.equalsIgnoreCase( "medium" ) )
- srcStyle = DateFormat.MEDIUM;
- else if ( srcPattern.equalsIgnoreCase( "long" ) )
- srcStyle = DateFormat.LONG;
- else if ( srcPattern.equalsIgnoreCase( "full" ) )
- srcStyle = DateFormat.FULL;
- /* really a src-pattern */
- else
+ private String formatDate_Time(Map params) throws SAXException {
+ // Check that we have not null params
+ if (params == null) {
+ throw new IllegalArgumentException("Nothing to format");
+ }
+
+ // Formatters
+ SimpleDateFormat to_fmt = null;
+ SimpleDateFormat from_fmt = null;
+
+ // Date formatting styles
+ int srcStyle = DateFormat.DEFAULT;
+ int style = DateFormat.DEFAULT;
+
+ // Date formatting patterns
+ boolean realPattern = false;
+ boolean realSrcPattern = false;
+
+ // From locale
+ Locale srcLoc = getLocale(params, I18N_SRC_LOCALE_ATTRIBUTE);
+ // To locale
+ Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
+
+ // 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);
+
+ // A src-pattern attribute is present
+ if (srcPattern != null) {
+ // Check if we have a real pattern
+ Integer patternValue =
(Integer)datePatterns.get(srcPattern.toUpperCase());
+ if (patternValue != null) {
+ srcStyle = patternValue.intValue();
+ } else {
realSrcPattern = true;
+ }
}
- if ( pattern == null ) {
- style = DateFormat.DEFAULT;
- } else {
- if ( pattern.equalsIgnoreCase( "short" ) )
- style = DateFormat.SHORT;
- else if ( pattern.equalsIgnoreCase( "medium" ) )
- style = DateFormat.MEDIUM;
- else if ( pattern.equalsIgnoreCase( "long" ) )
- style = DateFormat.LONG;
- else if ( pattern.equalsIgnoreCase( "full" ) )
- style = DateFormat.FULL;
- /* really a pattern */
- else
+
+ // A pattern attribute is present
+ if (pattern != null) {
+ Integer patternValue =
(Integer)datePatterns.get(pattern.toUpperCase());
+ if (patternValue != null) {
+ style = patternValue.intValue();
+ } else {
realPattern = true;
+ }
}
- if ( current_state == STATE_INSIDE_DATE ) {
- element = I18N_DATE_ELEMENT;
- to_fmt = (SimpleDateFormat) DateFormat.
- getDateInstance( style, loc );
- from_fmt = (SimpleDateFormat) DateFormat.
- getDateInstance( srcStyle, srcLoc );
- } else if ( current_state == STATE_INSIDE_DATE_TIME ) {
- element = I18N_DATE_TIME_ELEMENT;
- to_fmt = (SimpleDateFormat) DateFormat.
- getDateTimeInstance( style, style, loc );
- from_fmt = (SimpleDateFormat) DateFormat.
- getDateTimeInstance( srcStyle, srcStyle, srcLoc );
+ // If we are inside of a typed param
+ String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
+
+ // Initializing date formatters
+ if (current_state == STATE_INSIDE_DATE ||
+ I18N_DATE_ELEMENT.equals(paramType)) {
+
+ to_fmt = (SimpleDateFormat)DateFormat.getDateInstance(style,
loc);
+ from_fmt = (SimpleDateFormat)DateFormat.getDateInstance(
+ srcStyle,
+ srcLoc
+ );
+ } else if (current_state == STATE_INSIDE_DATE_TIME ||
+ I18N_DATE_TIME_ELEMENT.equals(paramType)) {
+ to_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
+ style,
+ style,
+ loc
+ );
+ from_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
+ srcStyle,
+ srcStyle,
+ srcLoc
+ );
} else {
- /* STATE_INSIDE_TIME */
- element = I18N_TIME_ELEMENT;
- to_fmt = (SimpleDateFormat) DateFormat.
- getTimeInstance( style, loc );
- from_fmt = (SimpleDateFormat) DateFormat.
- getTimeInstance( srcStyle, srcLoc );
+ // STATE_INSIDE_TIME or param type='time'
+ to_fmt = (SimpleDateFormat)DateFormat.getTimeInstance(style,
loc);
+ from_fmt = (SimpleDateFormat)DateFormat.getTimeInstance(
+ srcStyle,
+ srcLoc
+ );
}
// parsed date object
Date dateValue = null;
// pattern overwrites locale format
- if ( realSrcPattern ) {
- from_fmt.applyPattern( srcPattern );
+ if (realSrcPattern) {
+ from_fmt.applyPattern(srcPattern);
}
- if ( realPattern ) {
- to_fmt.applyPattern( pattern );
+ if (realPattern) {
+ to_fmt.applyPattern(pattern);
}
// get current date and time by default
- if ( value == null ) {
+ 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 );
+ 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 );
- debug( "i18n:" + element + " result: " + result );
- return result;
+ debug("### Formatting date: " + dateValue + " with localized pattern
"
+ + to_fmt.toLocalizedPattern() + " for locale: " + locale);
+ return to_fmt.format(dateValue);
}
private void endNumberElement() throws SAXException {
- String result = formatNumber( formattingParams );
- super.contentHandler.characters( result.toCharArray(), 0,
result.length() );
+ 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." );
+ 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
);
+ String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
// to pattern
- String pattern = (String) params.get( I18N_PATTERN_ATTRIBUTE );
+ String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
// the number value
- String value = (String) params.get( I18N_VALUE_ATTRIBUTE );
+ String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
// sub-type
- String subType = (String) params.get( I18N_SUB_TYPE_ATTRIBUTE );
+ String subType = (String)params.get(I18N_SUB_TYPE_ATTRIBUTE);
// parsed number
Number numberValue = null;
// locale, may be switched locale
- loc = getLocale( params, I18N_LOCALE_ATTRIBUTE );
- srcLoc = getLocale( params, I18N_SRC_LOCALE_ATTRIBUTE );
-
+ Locale loc = getLocale(params, I18N_LOCALE_ATTRIBUTE);
+ Locale srcLoc = getLocale(params, I18N_SRC_LOCALE_ATTRIBUTE);
// src format
- DecimalFormat from_fmt = (DecimalFormat) NumberFormat.getInstance(
srcLoc );
+ DecimalFormat from_fmt =
(DecimalFormat)NumberFormat.getInstance(srcLoc);
int int_currency = 0;
// src-pattern overwrites locale format
- if ( srcPattern != null ) {
- from_fmt.applyPattern( srcPattern );
+ if (srcPattern != null) {
+ from_fmt.applyPattern(srcPattern);
}
// to format
@@ -1094,120 +1372,106 @@
char dec = from_fmt.getDecimalFormatSymbols().getDecimalSeparator();
int decAt = 0;
boolean appendDec = false;
- if ( subType == null ) {
- to_fmt = (DecimalFormat) NumberFormat.getInstance( loc );
- to_fmt.setMaximumFractionDigits( 309 );
- for ( int i = value.length() - 1;
- i >= 0 && value.charAt( i ) != dec; i--, decAt++ )
+ if (subType == null) {
+ to_fmt = (DecimalFormat)NumberFormat.getInstance(loc);
+ to_fmt.setMaximumFractionDigits(309);
+ for (int i = value.length() - 1;
+ i >= 0 && value.charAt(i) != dec; i--, decAt++) {
;
- if ( decAt < value.length() ) to_fmt.setMinimumFractionDigits(
decAt );
+ }
+
+ if (decAt <
value.length())to_fmt.setMinimumFractionDigits(decAt);
decAt = 0;
- for ( int i = 0; i < value.length() && value.charAt( i ) != dec;
i++ ) {
- if ( Character.isDigit( value.charAt( i ) ) ) decAt++;
+ for (int i = 0; i < value.length() && value.charAt(i) != dec;
i++) {
+ if (Character.isDigit(value.charAt(i))) {
+ decAt++;
+ }
}
- to_fmt.setMinimumIntegerDigits( decAt );
- if ( value.charAt( value.length() - 1 ) == dec ) appendDec =
true;
- } else if ( subType.equals( "currency" ) ) {
- to_fmt = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
- } else if ( subType.equals( "int-currency" ) ) {
- to_fmt = (DecimalFormat) NumberFormat.getCurrencyInstance( loc );
+
+ to_fmt.setMinimumIntegerDigits(decAt);
+ if (value.charAt(value.length() - 1) == dec) {
+ appendDec = true;
+ }
+ } else if (subType.equals("currency")) {
+ to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
+ } else if (subType.equals("int-currency")) {
+ to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(loc);
int_currency = 1;
- for ( int i = 0; i < to_fmt.getMaximumFractionDigits(); i++ )
+ for (int i = 0; i < to_fmt.getMaximumFractionDigits(); i++) {
int_currency *= 10;
- } else if ( subType.equals( "percent" ) ) {
- to_fmt = (DecimalFormat) NumberFormat.getPercentInstance( loc );
+ }
+ } else if (subType.equals("percent")) {
+ to_fmt = (DecimalFormat)NumberFormat.getPercentInstance(loc);
}
// pattern overwrites locale format
- if ( pattern != null ) {
- to_fmt.applyPattern( pattern );
+ if (pattern != null) {
+ to_fmt.applyPattern(pattern);
}
- if ( value == null ) {
- numberValue = new Long( 0 );
+ if (value == null) {
+ numberValue = new Long(0);
} else {
try {
- numberValue = from_fmt.parse( value );
- if ( int_currency > 0 )
- numberValue = new Double( numberValue.doubleValue() /
- int_currency );
- else {
-
+ numberValue = from_fmt.parse(value);
+ if (int_currency > 0) {
+ numberValue = new Double(numberValue.doubleValue() /
+ int_currency);
+ } else {
+ // what?
}
- } catch ( ParseException pe ) {
- throw new SAXException( this.getClass().getName()
- + "i18n:number - parsing error.", pe
);
+ } 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 );
- if ( appendDec ) result = result + dec;
- debug( "i18n:number result: " + result );
+ String result = to_fmt.format(numberValue);
+ if (appendDec) result = result + dec;
+ debug("i18n:number result: " + result);
return result;
}
- /**
- * Helper method to retrieve a message from the dictionary
- */
- private String getString( String key ) {
+ // Helper method to retrieve a message from the dictionary
+ // Returnes null if no message is found
+ private String getString(String key) {
+ return getString(key, null);
+ }
+
+ // Helper method to retrieve a message from the dictionary.
+ // A default value is returned if message is not found
+ private String getString(String key, String defaultValue) {
+ try {
+ String value = dictionary.getString(
+ I18N_CATALOGUE_PREFIX + "[EMAIL PROTECTED]'" + key + "']"
+ );
- return dictionary.getString(
- new StringBuffer( I18N_CATALOGUE_PREFIX ).append( "[EMAIL
PROTECTED]'" ).append( key ).append( "']" ).toString()
- );
+ return value != null ? value : defaultValue;
+ } catch (MissingResourceException e) {
+ return defaultValue;
+ }
}
- private void setLocale( Locale locale ) {
+ private void setLocale(Locale locale) {
this.locale = locale;
- lang = locale.getLanguage();
- formatter.setLocale( locale );
}
- /**
- * Helper method to debug messages
- */
- private void debug( String msg ) {
- getLogger().debug( "I18nTransformer: " + msg );
+ // Helper method to debug messages
+ private void debug(String msg) {
+ getLogger().debug("I18nTransformer: " + msg);
}
- /*
- *
- 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);
- }
- }
- */
-
public void recycle() {
-
// restore untranslated-text if necessary
- if ( globalUntranslated != null &&
- !untranslated.equals( globalUntranslated )
- ) {
+ if (globalUntranslated != null &&
+ !untranslated.equals(globalUntranslated)) {
+
untranslated = globalUntranslated;
- debug( "untranslated-text restored to " + untranslated );
}
+
factory.release(dictionary);
dictionary = null;
}
@@ -1216,4 +1480,36 @@
factory.dispose();
factory = 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];n
+ 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);
+ }
+ }
+*/
}
1.2 +16 -16 xml-cocoon2/src/webapp/i18n/simple.xml
Index: simple.xml
===================================================================
RCS file: /home/cvs/xml-cocoon2/src/webapp/i18n/simple.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- simple.xml 3 Jan 2002 12:31:42 -0000 1.1
+++ simple.xml 30 Jan 2002 14:56:12 -0000 1.2
@@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns:i18n="http://apache.org/cocoon/i18n/2.0">
<title>
- <i18n:text>titletext</i18n:text>
+ <i18n:text>titletext</i18n:text>
</title>
<sub-title>
- <i18n:date pattern="EEE, MMMM dd, yyyy zzz" />
- </sub-title>
+ <i18n:date-time pattern="FULL"/>
+ </sub-title>
<annotation>
- <i18n:text>doclink</i18n:text>
+ <i18n:text>doclink</i18n:text>
<link>
<href>http://xml.apache.org/cocoon/userdocs/transformers/i18n-transformer.html</href>
<title>Cocoon 2 Web Site</title>
</link>
- </annotation>
+ </annotation>
<menu>
<item>
<title><i18n:text>language</i18n:text></title>
@@ -40,7 +40,7 @@
<i18n:text>language3</i18n:text>
</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=<i18n:text>lang_id4</i18n:text></href>
@@ -48,7 +48,7 @@
<i18n:text>language4</i18n:text>
</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=<i18n:text>lang_id5</i18n:text></href>
@@ -56,7 +56,7 @@
<i18n:text>language5</i18n:text>
</title>
</link>
- </item>
+ </item>
</menu>
<menu>
<item>
@@ -70,20 +70,20 @@
<href>simple.xml?locale=en_GB</href>
<title>English (GB)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=ru_RU</href>
<title>Russian (Russia)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xml?locale=de_AT_EURO</href>
<title>German (Austria, Euro)</title>
</link>
</item>
- </menu>
+ </menu>
<content>
<para title="first" name="article" i18n:attr="title name">
<i18n:text i18n:key="a_key">article_text1</i18n:text>
@@ -112,12 +112,12 @@
<para title="Number formatting (not translated)"
name="article" i18n:attr="name">
<i18n:translate>
Number : {0} | Currency: {1} | Percent:
{2}, processed on: {3}
- <i18n:param type="number"
value="1703.74" />
- <i18n:param type="number"
sub-type="currency">27.24</i18n:param>
- <i18n:param type="number"
sub-type="percent">1.2</i18n:param>
- <i18n:param type="date" />
+ <i18n:param type="number"
src-locale="en" value="1703.74" />
+ <i18n:param type="number"
sub-type="currency" src-locale="en">27.24</i18n:param>
+ <i18n:param type="number"
sub-type="percent" src-locale="en">1.2</i18n:param>
+ <i18n:param type="date-time"
pattern="MEDIUM"/>
</i18n:translate>
- </para>
+ </para>
</content>
<bottom>
<copyright>
1.2 +22 -24 xml-cocoon2/src/webapp/i18n/simple.xsp
Index: simple.xsp
===================================================================
RCS file: /home/cvs/xml-cocoon2/src/webapp/i18n/simple.xsp,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- simple.xsp 3 Jan 2002 12:31:42 -0000 1.1
+++ simple.xsp 30 Jan 2002 14:56:12 -0000 1.2
@@ -1,10 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsp:page language="java" xmlns:xsp="http://apache.org/xsp"
xmlns:xsp-request="http://apache.org/xsp/request/2.0"
xmlns:i18n="http://apache.org/cocoon/i18n/2.0">
- <xsp:structure>
- <xsp:include>java.text.SimpleDateFormat</xsp:include>
- <xsp:include>java.util.Locale</xsp:include>
- </xsp:structure>
- <xsp:logic>
+<xsp:logic>
private static int count = 0;
</xsp:logic>
<root>
@@ -13,11 +9,7 @@
synchronized (this) {
count++;
}
-
- Locale loc = request.getLocale();
-
- SimpleDateFormat df = new SimpleDateFormat("EEEE, MMMM dd, yyyy
H:mm:ss", loc);
- </xsp:logic>
+ </xsp:logic>
<title>
<i18n:text>titletext</i18n:text>
</title>
@@ -31,11 +23,15 @@
<sub-title>
<i18n:translate>
<i18n:text>count_title</i18n:text>
- <i18n:param>
+ <i18n:param type="number" pattern="000000">
<xsp:expr>count</xsp:expr>
</i18n:param>
- <i18n:param>
- <xsp:expr>df.format(new
Date())</xsp:expr>
+ <i18n:param type="date-time"
+ src-pattern="EEE MMM dd hh:mm:ss zzz yyyy"
+ src-locale="en"
+ pattern="MEDIUM"
+ >
+ <xsp:expr>new Date()</xsp:expr>
</i18n:param>
</i18n:translate>
</sub-title>
@@ -98,20 +94,20 @@
<href>simple.xsp?locale=en_GB</href>
<title>English (GB)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xsp?locale=ru_RU</href>
<title>Russian (Russia)</title>
</link>
- </item>
+ </item>
<item>
<link>
<href>simple.xsp?locale=de_AT_EURO</href>
<title>German (Austria, Euro)</title>
</link>
- </item>
- </menu>
+ </item>
+ </menu>
<content>
<para title="first" name="article" i18n:attr="title
name">
<i18n:text
i18n:key="a_key">article_text1</i18n:text>
@@ -123,9 +119,7 @@
<i18n:translate>
<i18n:text>Hello, {0}! Glad to see
you!</i18n:text>
<i18n:param name="username">
- <i18n:text>
<xsp-request:get-parameter name="user" default="none" as="string"/>
- </i18n:text>
</i18n:param>
</i18n:translate>
</para>
@@ -148,12 +142,16 @@
<para title="Number formatting (not translated)"
name="article" i18n:attr="name">
<i18n:translate>
Number : {0} | Currency: {1} | Percent:
{2}, processed on: {3}
- <i18n:param type="number"
value="1703.74" />
- <i18n:param type="number"
sub-type="currency">27.24</i18n:param>
- <i18n:param type="number"
sub-type="percent">1.2</i18n:param>
- <i18n:param type="date" />
+ <i18n:param type="number"
src-locale="en">
+ <xsp:expr>17 + 3 + 1974</xsp:expr>
+ </i18n:param>
+ <i18n:param type="number"
sub-type="currency" src-locale="en">
+ <xsp:expr>2002 / 28</xsp:expr>
+ </i18n:param>
+ <i18n:param type="number"
sub-type="percent" src-locale="en">1.2</i18n:param>
+ <i18n:param type="date-time"
pattern="MEDIUM"/>
</i18n:translate>
- </para>
+ </para>
</content>
<bottom>
<copyright>
1.2 +2 -2 xml-cocoon2/src/webapp/i18n/translations/messages.xml
Index: messages.xml
===================================================================
RCS file: /home/cvs/xml-cocoon2/src/webapp/i18n/translations/messages.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- messages.xml 3 Jan 2002 12:31:42 -0000 1.1
+++ messages.xml 30 Jan 2002 14:56:12 -0000 1.2
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- CVS $Id: messages.xml,v 1.1 2002/01/03 12:31:42 giacomo Exp $ -->
+<!-- CVS $Id: messages.xml,v 1.2 2002/01/30 14:56:12 sylvain Exp $ -->
<!-- Default English message catalogue file for cocoon2 sample webapp -->
@@ -27,7 +27,7 @@
<message key="article_text1">This is a i18n paragraph.</message>
<message key="article_text2">This is another i18n paragraph and is also
a cool one.</message>
<message key="copyright">Copyright © 2001 Konstantin Piroumian. No
rights are reserved.</message>
- <message key="Hello">Hello, {0}! Glad to see you!</message>
+ <message key="Hello, {0}! Glad to see you!">Hello, {0}! Glad to see
you!</message>
<message key="Kot">Tomcat</message>
<message key="none">None</message>
<message key="one">one</message>
1.2 +2 -2 xml-cocoon2/src/webapp/i18n/translations/messages_en.xml
Index: messages_en.xml
===================================================================
RCS file: /home/cvs/xml-cocoon2/src/webapp/i18n/translations/messages_en.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- messages_en.xml 3 Jan 2002 12:31:42 -0000 1.1
+++ messages_en.xml 30 Jan 2002 14:56:12 -0000 1.2
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
-<!-- CVS $Id: messages_en.xml,v 1.1 2002/01/03 12:31:42 giacomo Exp $ -->
+<!-- CVS $Id: messages_en.xml,v 1.2 2002/01/30 14:56:12 sylvain Exp $ -->
<!-- English message catalogue file for cocoon2 sample webapp -->
@@ -27,7 +27,7 @@
<message key="article_text1">This is a i18n paragraph.</message>
<message key="article_text2">This is another i18n paragraph and is also
a cool one.</message>
<message key="copyright">Copyright © 2001 Konstantin Piroumian. No
rights are reserved.</message>
- <message key="Hello">Hello, {0}! Glad to see you!</message>
+ <message key="Hello, {0}! Glad to see you!">Hello, {0}! Glad to see
you!</message>
<message key="Kot">Tomcat</message>
<message key="none">None</message>
<message key="one">one</message>
----------------------------------------------------------------------
In case of troubles, e-mail: [EMAIL PROTECTED]
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]