Return-Path:
sub-type
attribute is used with i18:number
to indicate
+ * a sub-type: currency
or percent
.
+ */
+ public final static String I18N_SUB_TYPE_ATTRIBUTE = "sub-type";
+ /**
+ * type
attribute is used with i18:param
to indicate
+ * the parameter type: date
or number
.
+ * If type
is number
then a sub-type
+ * can be used.
+ */
+ public final static String I18N_TYPE_ATTRIBUTE = "type";
+
// States of the transformer
private final static int STATE_OUTSIDE = 0;
private final static int STATE_INSIDE_TEXT = 1;
private final static int STATE_INSIDE_PARAM = 2;
private final static int STATE_INSIDE_TRANSLATE = 3;
-// private final static int STATE_INSIDE_PARAM_TEXT = 4;
- private final static int STATE_INSIDE_TRANSLATE_TEXT = 5;
- private final static int STATE_TRANSLATE_KEY = 6;
- private final static int STATE_TRANSLATE_TEXT_KEY = 7;
+ private final static int STATE_INSIDE_TRANSLATE_TEXT = 4;
+ private final static int STATE_TRANSLATE_KEY = 5;
+ private final static int STATE_TRANSLATE_TEXT_KEY = 6;
+ private final static int STATE_INSIDE_DATE = 7;
+ private final static int STATE_INSIDE_NUMBER = 8;
/**
* Current state of the transformer.
+ * The value is STATE_OUTSIDE by default.
*/
private int current_state = STATE_OUTSIDE;
/**
- * Previous state. Used to translate text inside params and translate elements.
+ * Previous state.
+ * Used to translate text inside params and translate elements.
*/
private int prev_state = STATE_OUTSIDE;
@@ -205,13 +232,33 @@
* Also, different encodings can be specified: ru_RU_koi8
*/
private Locale locale;
+
+ /**
+ * Date element attributes and their values.
+ */
+ private HashMap formattingParams;
+
+ public static Locale parseLocale(String locale) {
+ StringTokenizer st = new StringTokenizer(locale, "_");
+ String lang = null;
+ String country = null;
+ String variant = null;
+ if (!st.hasMoreTokens()) {
+ return Locale.ENGLISH;
+ }
+ else {
+ lang = st.nextToken();
+ }
- public void setLang(String lang) {
- this.lang = lang;
+ country = st.hasMoreTokens() ? st.nextToken() : "";
+ variant = st.hasMoreTokens() ? st.nextToken() : "";
+
+ return new Locale(lang, country, variant);
}
public void setLocale(Locale locale) {
this.locale = locale;
+ this.lang = locale.getLanguage();
}
/**
@@ -228,26 +275,8 @@
if (lang == null) {
lang = LangSelect.getLang(objectModel, parameters);
}
- setLang(lang);
- Locale locale = null;
- int ind = lang.indexOf("_");
- if (ind != -1) {
- int lind = lang.lastIndexOf("_");
- if (ind == lind) {
- locale = new Locale(lang.substring(0, ind - 1),
- lang.substring(ind + 1));
- }
- else {
- locale = new Locale(lang.substring(0, ind - 1),
- lang.substring(ind + 1, lind - 1),
- lang.substring(lind + 1));
- }
- }
- else {
- locale = new Locale(lang, "");
- }
- setLocale(locale);
+ setLocale(parseLocale(lang));
formatter.setLocale(locale);
// FIXME (KP)
@@ -307,51 +336,126 @@
private void startI18NElement(String name, Attributes attr)
throws SAXException {
this.getLogger().debug("Start i18n element: " + name);
- 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);
+ 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);
}
- 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);
+ 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;
}
- 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);
+ 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;
}
- current_state = STATE_INSIDE_PARAM;
+ else if (I18N_DATE_ELEMENT.equals(name)) {
+ if (current_state != STATE_OUTSIDE) {
+ throw new SAXException(this.getClass().getName()
+ + ": i18n:date elements are not allowed "
+ + "inside of other i18n elements.");
+ }
+
+ setFormattingParams(attr);
+ current_state = STATE_INSIDE_DATE;
+ }
+ else if (I18N_NUMBER_ELEMENT.equals(name)) {
+ if (current_state != STATE_OUTSIDE) {
+ throw new SAXException(this.getClass().getName()
+ + ": i18n:number elements are not allowed "
+ + "inside of other i18n elements.");
+ }
+
+ setFormattingParams(attr);
+ current_state = STATE_INSIDE_NUMBER;
+ }
+ } catch (Exception e) {
+ // we need it to avoid further errors if an exception occurs
+ current_state = STATE_OUTSIDE;
+ throw new SAXException(this.getClass().getName()
+ + ": error in format", e);
}
}
+ /**
+ * Get src-pattern, pattern and value attribute values and store in a Map
+ */
+ private void setFormattingParams(Attributes attr) throws SAXException {
+ formattingParams = new HashMap(3);
+
+ String attr_value = attr.getValue(I18N_SRC_PATTERN_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_SRC_PATTERN_ATTRIBUTE, attr_value);
+ }
+
+ 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_TYPE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_TYPE_ATTRIBUTE, attr_value);
+ }
+
+ attr_value = attr.getValue(I18N_SUB_TYPE_ATTRIBUTE);
+ if (attr_value != null) {
+ formattingParams.put(I18N_SUB_TYPE_ATTRIBUTE, attr_value);
+ }
+ }
+
private void endI18NElement(String name) throws SAXException {
this.getLogger().debug("End i18n element: " + name);
- switch (current_state) {
- case STATE_INSIDE_TEXT: {
- endTextElement();
- break;
- }
- case STATE_INSIDE_TRANSLATE: {
- endTranslateElement();
- break;
- }
- case STATE_INSIDE_PARAM: {
- endParamElement();
- break;
- }
+ try {
+ switch (current_state) {
+ case STATE_INSIDE_TEXT: {
+ endTextElement();
+ break;
+ }
+ case STATE_INSIDE_TRANSLATE: {
+ endTranslateElement();
+ break;
+ }
+ case STATE_INSIDE_PARAM: {
+ endParamElement();
+ break;
+ }
+ case STATE_INSIDE_DATE: {
+ endDateElement();
+ break;
+ }
+ case STATE_INSIDE_NUMBER: {
+ endNumberElement();
+ break;
+ }
+ }
+ } catch (Exception e) {
+ // we need it to avoid further errors if an exception occurs
+ current_state = STATE_OUTSIDE;
+ throw new SAXException(this.getClass().getName()
+ + ": error in format", e);
}
}
@@ -362,9 +466,9 @@
// FIXME (KP) Must be a better way to determine whitespace-only nodes.
// trim() function does not remove spaces if string does not contain
// anything else.
-// if (s == null) {
-// return null;
-// }
+ if (s == null) {
+ return null;
+ }
String result = (s + "!").trim();
return result.substring(0, result.length() - 1);
}
@@ -375,7 +479,6 @@
String text2translate = new String(ch, start, len);
text2translate = stripWhitespace(text2translate);
if (text2translate == null || text2translate.length() == 0) {
-// this.getLogger().warn(this.getClass().getName() + ": null i18n text found");
return;
}
@@ -390,7 +493,7 @@
}
current_key = null;
}
- else if (len > 0) {
+ else {
translated_text = (String)(dictionary.get(text2translate));
}
@@ -398,19 +501,44 @@
}
case STATE_INSIDE_TRANSLATE: {
// Store text for param substitution (do not translate)
- if (len > 0 && substitute_text == null) {
+ if (substitute_text == null) {
substitute_text = text2translate;
}
break;
}
case STATE_INSIDE_PARAM: {
// Store translation for param substitution
- if (len > 0 && param_value == null) {
+ if (param_value == null) {
param_value = text2translate;
}
break;
}
-
+ case STATE_INSIDE_DATE: {
+ if (formattingParams != null) {
+ if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null) {
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, text2translate);
+ }
+ else {
+ // how to use the text inside of date element?
+ }
+ }
+ break;
+ }
+ case STATE_INSIDE_NUMBER: {
+ if (formattingParams != null) {
+ if (formattingParams.get(I18N_PATTERN_ATTRIBUTE) == null) {
+ formattingParams.put(I18N_PATTERN_ATTRIBUTE, text2translate);
+ }
+ else {
+ // how to use the text inside of number element?
+ }
+ }
+ break;
+ }
+ default: {
+ throw new SAXException(this.getClass().getName()
+ + "Something's really wrong!!!");
+ }
}
}
@@ -471,9 +599,8 @@
0, translated_text.length());
}
else {
- // Translation not found.
- super.contentHandler.characters("".toCharArray(),
- 0, 0);
+ // else - translation not found
+ this.getLogger().debug("--- Translation not found! ---");
}
break;
}
@@ -491,8 +618,28 @@
prev_state = STATE_OUTSIDE;
}
- private void endParamElement() {
+ private void endParamElement() throws SAXException {
this.getLogger().debug("Substitution param: " + param_value);
+ if (formattingParams != null) {
+ String paramType = (String)formattingParams.get(I18N_TYPE_ATTRIBUTE);
+ if (paramType != null) {
+ this.getLogger().debug("Param type: " + paramType);
+ if (formattingParams.get(I18N_VALUE_ATTRIBUTE) == null
+ && param_value != null) {
+ this.getLogger().debug("Put param value: " + param_value);
+ formattingParams.put(I18N_VALUE_ATTRIBUTE, param_value);
+ }
+ if ("date".equals(paramType)) {
+ this.getLogger().debug("Formatting date param: " + formattingParams);
+ param_value = formatDate(formattingParams);
+ }
+ else if ("number".equals(paramType)) {
+ this.getLogger().debug("Formatting number param: " + formattingParams);
+ param_value = formatNumber(formattingParams);
+ }
+ }
+ }
+ this.getLogger().debug("Added substitution param: " + param_value);
indexedParams.add(param_value);
param_value = null;
current_state = STATE_INSIDE_TRANSLATE;
@@ -520,8 +667,124 @@
current_state = STATE_OUTSIDE;
}
+ private void endDateElement() throws SAXException {
+ String result = formatDate(formattingParams);
+ super.contentHandler.characters(result.toCharArray(), 0, result.length());
+ current_state = STATE_OUTSIDE;
+ }
+
+ private String formatDate(Map params) throws SAXException {
+ if (params == null) {
+ throw new SAXException(this.getClass().getName()
+ + ": i18n:date - error in element attributes.");
+ }
+ // from pattern
+ String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
+ // to pattern
+ String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
+ // the date value
+ String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
+
+ // parsed date object
+ Date dateValue = null;
+
+ // src format
+ SimpleDateFormat from_fmt = (SimpleDateFormat)DateFormat.getInstance();
+ if (srcPattern != null) {
+ from_fmt.applyPattern(srcPattern);
+ }
+
+ // result pattern is localized
+ SimpleDateFormat to_fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
+ DateFormat.DEFAULT, DateFormat.DEFAULT, locale);
+ if (pattern != null) {
+ to_fmt.applyPattern(pattern);
+ }
+
+ // get current date and time by default
+ if (value == null) {
+ dateValue = new Date();
+ }
+ else {
+ try {
+ dateValue = from_fmt.parse(value);
+ } catch (ParseException pe) {
+ throw new SAXException(this.getClass().getName()
+ + "i18n:date - parsing error.", pe);
+ }
+ }
+
+ // we have all necessary data here: do formatting.
+ String result = to_fmt.format(dateValue);
+ this.getLogger().debug("i18n:date result: " + result);
+ return result;
+ }
+
+ private void endNumberElement() throws SAXException {
+ String result = formatNumber(formattingParams);
+ super.contentHandler.characters(result.toCharArray(), 0, result.length());
+ current_state = STATE_OUTSIDE;
+ }
+
+ private String formatNumber(Map params) throws SAXException {
+ if (params == null) {
+ throw new SAXException(this.getClass().getName()
+ + ": i18n:number - error in element attributes.");
+ }
+ // from pattern
+ String srcPattern = (String)params.get(I18N_SRC_PATTERN_ATTRIBUTE);
+ // to pattern
+ String pattern = (String)params.get(I18N_PATTERN_ATTRIBUTE);
+ // the number value
+ String value = (String)params.get(I18N_VALUE_ATTRIBUTE);
+ // sub-type
+ String subType = (String)params.get(I18N_SUB_TYPE_ATTRIBUTE);
+
+ // parsed number
+ Number numberValue = null;
+
+ // src format
+ DecimalFormat from_fmt = (DecimalFormat)NumberFormat.getInstance();
+ if (srcPattern != null) {
+ from_fmt.applyPattern(srcPattern);
+ }
+
+ // result pattern is localized
+ DecimalFormat to_fmt = null;
+ if (subType == null) {
+ to_fmt = (DecimalFormat)NumberFormat.getInstance(locale);
+ }
+ else if (subType.equals("currency")) {
+ to_fmt = (DecimalFormat)NumberFormat.getCurrencyInstance(locale);
+ }
+ else if (subType.equals("percent")) {
+ to_fmt = (DecimalFormat)NumberFormat.getPercentInstance(locale);
+ }
+ if (pattern != null) {
+ to_fmt.applyPattern(pattern);
+ }
+
+ // get current date and time by default
+ if (value == null) {
+ numberValue = new Long(0);
+ }
+ else {
+ try {
+ numberValue = from_fmt.parse(value);
+ } catch (ParseException pe) {
+ throw new SAXException(this.getClass().getName()
+ + "i18n:number - parsing error.", pe);
+ }
+ }
+
+ // we have all necessary data here: do formatting.
+ String result = to_fmt.format(numberValue);
+ this.getLogger().debug("i18n:number result: " + result);
+ return result;
+ }
+
/**
- *Gets translations from xml file to dictionary.
+ * Gets translations from xml file to dictionary.
*/
class I18nContentHandler extends DefaultHandler {
boolean in_entry = false;
@@ -622,4 +885,41 @@
if(parser != null) this.manager.release((Component) parser);
}
}
+
+ /**
+ *
+ */
+ static public void main(String[] args) {
+
+ Locale locale = null;
+
+ Locale[] locales = Locale.getAvailableLocales();
+ for (int i = 0; i < locales.length; i++) {
+ locale = locales[i];
+ SimpleDateFormat fmt = (SimpleDateFormat)DateFormat.getDateTimeInstance(
+ DateFormat.DEFAULT, DateFormat.DEFAULT, locale
+ );
+
+ String localized = fmt.format(new Date());
+
+ NumberFormat n_fmt = NumberFormat.getCurrencyInstance(locale);
+ String money = n_fmt.format(1210.5);
+
+ System.out.println("Locale ["
+ + locale.getLanguage() + ", "
+ + locale.getCountry() + ", "
+ + locale.getVariant() + "] : "
+ + locale.getDisplayName()
+ + " \t Date: " + localized
+ + " \t Money: " + money);
+ }
+ }
+
}
+
+
+
+
+
+
+
No revision
No revision
1.3.2.1 +43 -5 xml-cocoon2/webapp/i18n/simple.xml
Index: simple.xml
===================================================================
RCS file: /home/cvs/xml-cocoon2/webapp/i18n/simple.xml,v
retrieving revision 1.3
retrieving revision 1.3.2.1
diff -u -r1.3 -r1.3.2.1
--- simple.xml 2001/05/23 12:32:20 1.3
+++ simple.xml 2001/06/15 15:00:11 1.3.2.1
@@ -1,15 +1,18 @@
+
+
-
-
+
+
+
+
+
+
+
+
+ xmlns:i18n="http://apache.org/cocoon/i18n/2.0"
- First implementation was developed by Lassi Immonen. In this implementation the syntax was changed according to the Infozone Group's i18n proposal (with a little difference) and new features were implemented. + First implementation was developed by Lassi Immonen. In this implementation syntax was changed according to the Infozone Group's i18n proposal (with a little difference) and some new features were implemented.
A simple example of i18n: @@ -52,12 +56,11 @@ ]]>
Text inside the ]]>
will be used as a key to find the
- translation in the dictionary. All attributes that are listed in the ]]>
attribute also will
- be translated and their values will be used as dicionary keys.
+ translation in the dictionary. All attributes that are listed in the ]]>
attribute also will be translated and their values will be used as dicionary keys.
@@ -106,21 +109,21 @@
text
here
]]>
-
+
Now we want to translate this into German.
First, the processor will look into the dictionary, we specified, for
the string:
-
- Some {0} was inserted {1}.
-
-
+
+ Some {0} was inserted {1}.
+
+
It finds the string and translates it to German:
-
-
+
+
Etwas {0} wurde {1} eingesetzt.
-
-
+
+
Now the processor will replace the parameters. {0} will be replaced
with "text" and {1} with "here". This results in:
@@ -159,6 +162,33 @@
Parameter replacement is not available for attributes at this time.
+
+ To format dates and time according to the current locale use ]]>
. The 'src-pattern'
attribute will be used to parse the 'value'
, then the date will be formatted according to the current locale using the format specified by 'pattern'
attribute.
+
+
+ If no pattern was specified then the date will be formatted with the DateFormat.DEFAULT
format (both date and time). If no value for the date is specified then the current date will be used. E.g.:
will result in the current date and time, formatted with default localized pattern.
+
+ To format numbers in locale sensitive manner use ]]>
. This will be useful for Arabic, Indian, etc. number formatting. Additionally, currencies and percent formatting can be used. E.g.:
+
+
+ ]]>
will result in localized presentation of the value
- $1,703.74 for US locale.
+ ]]>
will result in localized percent value
- %120 for most of the locales.
+
+
+ Also, date and number formatting can be used with substitution params. Additional type
attribute must be used with params to indicate the param type (date or number). Default type is string
.
+
+
+
+ Result will be like this: You have to pay $102.5 for 2.5 pounds or 10% of your profit. Valid from 13-Jun-01
+
+
Dictionaries contain the translations for the text to be translated.
@@ -263,26 +293,12 @@
- Some more features must be added for more flexibility and convenience: -
+
- ]]>
- - 'src-pattern'
will be used to parse the 'value'
, then the date will be formatted according to the current lang (Locale) using the 'pattern'
format.
-
- ]]>
- - this will be useful for Arabic, Indian, etc. number formatting.
-