You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by cr...@apache.org on 2003/02/19 08:19:12 UTC

cvs commit: xml-cocoon2/src/webapp/samples/i18n multi.xml menu.xml sitemap.xmap

crossley    2003/02/18 23:19:10

  Modified:    src/documentation/xdocs/userdocs/transformers
                        i18n-transformer.xml
               src/java/org/apache/cocoon/transformation
                        I18nTransformer.java
               src/webapp/samples/i18n menu.xml sitemap.xmap
  Added:       src/webapp/samples/i18n multi.xml
  Log:
  Support for multiple catalogues.
  Can now use "input modules" in catalogue name and catalogue location.
  Fixed bug with getting content for the translation key in certain circumstances.
  Fixed bug with accidental removal of translation text in certain circumstances.
  See also http://marc.theaimsgroup.com/?t=104505932200002&r=1&w=2
  Submitted by: Bruno Dumon <bruno.at.outerthought.org>
  PR: 17127
  
  Revision  Changes    Path
  1.5       +50 -16    xml-cocoon2/src/documentation/xdocs/userdocs/transformers/i18n-transformer.xml
  
  Index: i18n-transformer.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/documentation/xdocs/userdocs/transformers/i18n-transformer.xml,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- i18n-transformer.xml	19 Nov 2002 04:20:07 -0000	1.4
  +++ i18n-transformer.xml	19 Feb 2003 07:19:09 -0000	1.5
  @@ -38,7 +38,7 @@
   					</link>
   				, which uses XML dictionaries for all the multilingual data. The namespace URI of i18n transformer is defined as follows: 
                       </p>
  -                    <source>xmlns:i18n="http://apache.org/cocoon/i18n/2.0</source>
  +                    <source>xmlns:i18n="http://apache.org/cocoon/i18n/2.1</source>
                  <s2 title="Other implementation details">
   			<ul>
   				<li>Default name in sitemap: i18n</li>
  @@ -137,6 +137,15 @@
   		    </p>
   				<source><![CDATA[
   <i18n:text i18n:key="key_text">Default value</i18n:text>]]></source>
  +        <p>
  +          Messages can be taken from multiple dictionaries. The dictionaries are configured
  +          in the sitemap (see further on), and each dictionary is assigned an id. There is
  +          one dictionary that serves as the default one. To translate a key using a
  +          non-default dictionary, mention the id of the dictionary in an i18n:catalogue
  +          attribute:
  +        </p>
  +				<source><![CDATA[
  +<i18n:text i18n:catalogue="menu">key_text</i18n:text>]]></source>
   			</s2>
   			<anchor id="i18n_translate"/><s2 title="Translation with param substitution">
   				<p>
  @@ -211,7 +220,14 @@
   				Some versions of Xerces have a bug in removeAttribute() method implementation and this
   				results in a NullPointerException if attributes translation is used. The solution is to upgrade
   				to a newer version of Xerces.
  -			</note>				
  +			</note>
  +        <p>
  +          Just as with i18n:text, the translations for attributes can come from
  +          multiple dictionaries. To use a specific dictionary, add the id of
  +          the dictionary before the key, separated by a colon:
  +        </p>
  +				<source><![CDATA[
  +<INPUT type="submit" value="form:Submit" i18n:attr="value"/>]]></source>
   			</s2>
   			<s2 title="Date, time and number formatting">
   				<anchor id="i18n_date"/><p>To format dates according to the current locale use <code><![CDATA[<i18n:date src-pattern="dd/MM/yyyy" pattern="dd:MMM:yyyy" value="01/01/2001" />]]></code>. The <code>'src-pattern'</code> attribute will be used to parse the <code>'value'</code>, then the date will be formatted according to the current locale using the format specified by <code>'pattern'</code> attribute.
  @@ -302,35 +318,53 @@
   <map:transformer name="i18n"
        src="org.apache.cocoon.transformation.I18nTransformer">
   
  -     <catalogue-name>messages</catalogue-name>
  -     <catalogue-location>translations</catalogue-location>
  +     <catalogues default="messages">
  +       <catalogue id="messages" name="messages" location="translations"/>
  +       <catalogue id="menu" name="menu" location="{defaults:skin}/translations"/>
  +     </catalogues>
        <untranslated-text>untranslated</untranslated-text>
        <cache-at-startup>true</cache-at-startup>
   </map:transformer>]]></source> 
                       <p>where:</p>
                       <ul>
  -                        <li><strong>catalogue-name</strong>: base name of the message
  -                            catalogue (<em>mandatory</em>).</li>
  -                        <li><strong>catalogue-location</strong>: location of the
  -                            message catalogues (<em>mandatory</em>).</li>
  -                        <li><strong>untranslated-text</strong>: text used for
  -                            untranslated keys (default is to output the key name).</li>
  -                        <li><strong>cache-at-startup</strong>: flag whether to cache
  -                            messages at startup (false by default).</li>
  +                      <li><strong>catalogues</strong>: container element in which the catalogues
  +                        are defined. It must have an attribute 'default' whose value is one
  +                        of the id's of the catalogue elements. (<em>mandatory</em>).</li>
  +                      <li><strong>catalogue</strong>: specifies a catalogue. It takes 3 required
  +                        attributes: id (can be wathever you like), name (base name of the catalogue)
  +                        and location (location of the message catalogue). The name and location attributes
  +                        can contain references to "input modules" (same syntax as in other places in the
  +                        sitemap). They are resolved on each usage of the transformer, so they can
  +                        refer to e.g. request parameters. (<em>at least 1 catalogue
  +                          element required</em>).</li>
  +                      <li><strong>untranslated-text</strong>: text used for
  +                        untranslated keys (default is to output the key name).</li>
  +                      <li><strong>cache-at-startup</strong>: flag whether to cache
  +                        messages at startup (false by default).</li>
                       </ul>
  +                    <note>The configuration syntax was changed to add support for configuring
  +                      mulitple catalogues. The old syntax, using the elements 'catalogue-name'
  +                      and 'catalogue-location', is still supported, but cannot be used concurrently
  +                      with the new syntax.</note>
                       <p>To use the transformer in a pipeline, simply specify it in a particular transform and indicate the needed locale. eg:</p>
    <source><![CDATA[
   <map:match pattern="file">
           <map:generate src="file.xml"/>
            <map:transform type="i18n">
  -             <map:parameter name="locale" value="en_AU"
  +           <map:parameter name="locale" value="en_AU"/>
            </map:transform>
            <map:serialize/>
   </map:match>]]></source>
                       <note>Note, that since Cocoon version 2.0.1 you should specify the needed locale as a parameter at pipeline level. This gives more flexibility in locale selection, e.g. URI parts can be used: <code>/en_AU/file</code>. See LocaleAction documentation for other possibilities.</note>
  -                    <p>Also, <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>
  +                    <p>The default catalogue can be changed at the pipeline level by specifying
  +                      a parameter <strong>default-catalogue-id</strong>. Likewise, a parameter
  +                      <strong>untranslated-text</strong> can be used to override the default
  +                      untranslated text.</p>
  +                    <note>Before multiple catalogues were supported, the catalogue could be defined
  +                      at the pipeline level by adding parameters <strong>catalogue-name</strong> and
  +                      <strong>catalogue-location</strong>. This is still supported, but
  +                      cannot be used concurrently with the <strong>default-catalogue-id</strong>
  +                      parameter.</note>
   			</s2>
   		</s1>
   		<s1 title="Samples">
  
  
  
  1.31      +283 -65   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.30
  retrieving revision 1.31
  diff -u -r1.30 -r1.31
  --- I18nTransformer.java	31 Jan 2003 22:51:56 -0000	1.30
  +++ I18nTransformer.java	19 Feb 2003 07:19:10 -0000	1.31
  @@ -60,6 +60,8 @@
   import org.apache.avalon.framework.configuration.DefaultConfiguration;
   import org.apache.avalon.framework.parameters.Parameters;
   import org.apache.cocoon.ProcessingException;
  +import org.apache.cocoon.sitemap.PatternException;
  +import org.apache.cocoon.components.treeprocessor.variables.PreparedVariableResolver;
   import org.apache.cocoon.caching.CacheableProcessingComponent;
   import org.apache.cocoon.environment.SourceResolver;
   import org.apache.cocoon.i18n.Bundle;
  @@ -140,27 +142,43 @@
    * This allows the developer to write a hierarchy of message catalogues,
    * at each defining messages with increasing depth of variation.
    *
  + * <p>The i18n:text element can optionally take an attribute <strong>i18n:catalogue</strong>
  + * to specify a specific catalogue to use. The value of this attribute should be
  + * the id of the catalogue to use (see sitemap configuration).
  + *
    * <h3>Sitemap configuration</h3>
    * <pre>
    * &lt;map:transformer name="i18n"
    *     src="org.apache.cocoon.transformation.I18nTransformer"&gt;
    *
  - *     &lt;catalogue-name&gt;messages&lt;/catalogue-name&gt;
  - *     &lt;catalogue-location&gt;translations&lt;/catalogue-location&gt;
  + *     &lt;catalogues default="someId"&gt;
  + *       &lt;catalogue id="someId" name="messages" location="translations"&gt;
  + *     &lt;/catalogues&gt;
    *     &lt;untranslated-text&gt;untranslated&lt;/untranslated-text&gt;
    *     &lt;cache-at-startup&gt;true&lt;/cache-at-startup&gt;
    * &lt;/map:transformer&gt;
    * </pre> where:
    * <ul>
  - *  <li><strong>catalogue-name</strong>: base name of the message
  - *      catalogue (<i>mandatory</i>).
  - *  <li><strong>catalogue-location</strong>: location of the
  - *      message catalogues (<i>mandatory</i>).
  + *  <li><strong>catalogues</strong>: container element in which the catalogues
  + *      are defined. It must have an attribute 'default' whose value is one
  + *      of the id's of the catalogue elements. (<i>mandatory</i>).
  + *  <li><strong>catalogue</strong>: specifies a catalogue. It takes 3 required
  + *      attributes: id (can be wathever you like), name (base name of the catalogue)
  + *      and location (location of the message catalogue). The name and location attributes
  + *      can contain references to inputmodules (same syntax as in other places in the
  + *      sitemap). They are resolved on each usage of the transformer, so they can
  + *      refer to e.g. request parameters. (<i>at least 1 catalogue
  + *      element required</i>).
    *  <li><strong>untranslated-text</strong>: text used for
    *      untranslated keys (default is to output the key name).
    *  <li><strong>cache-at-startup</strong>: flag whether to cache
    *      messages at startup (false by default).
    * </ul>
  + * <p>Note: before using multiple catalogues was supported, the catalogue name
  + * and location was specified using elements named <code>catalogue-name</code> and
  + * <code>catalogue-location</code>. This syntax is still supported, but can't be used
  + * concurrently with the new syntax.
  + *
    * <p>To use the transformer in a pipeline, simply specify it in a particular
    * transform. eg:
    * <pre>
  @@ -171,9 +189,17 @@
    * &lt;/map:match&gt;
    * </pre>
    *
  - * <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>If in certain pipeline, you want to use a different catalogue as the
  + * default catalogue, you can do so by specifying a parameter called
  + * <strong>default-catalogue-id</strong>.
  + *
  + * <p>The <strong>untranslated-text</strong> can also be overridden at the
  + * pipeline level by specifying it as a parameter.
  + *
  + * <p>Note: before multiple catalogues were supported, the catalogue to use
  + * could be overridden at the pipeline level by specifying parameters called
  + * <strong>catalogue-name</strong>, <strong>catalogue-location</strong>. This
  + * is still supported, but can't be used together with the new parameter default-catalogue-id.
    *
    * <p>For date, time and number formatting use the following tags:
    * <ul>
  @@ -200,7 +226,6 @@
    *
    * <p>Future work coming:
    * <ul>
  - *  <li>Improved dictionary handling (allow 'bundle' attribute on elements)
    *  <li>Introduce new &lt;get-locale /&gt; element
    *  <li>Move all formatting routines to I18nUrils
    * </ul>
  @@ -599,6 +624,13 @@
        */
       public static final String I18N_TYPE_ATTRIBUTE          = "type";
   
  +    /**
  +     * This attribute can be used on <code>i18n:text</code> to indicate the catalogue
  +     * from which the key should be retrieved. This attribute is optional,
  +     * if it is not mentioned the default catalogue is used.
  +     */
  +    public static final String I18N_CATALOGUE_ATTRIBUTE = "catalogue";
  +
       // Configuration parameters
   
       /**
  @@ -619,6 +651,13 @@
       public static final String I18N_CATALOGUE_LOCATION  = "catalogue-location";
   
       /**
  +     * This configuration parameter specifies the id of the catalogue to be used as
  +     * default catalogue, allowing to redefine the default catalogue on the pipeline
  +     * level.
  +     */
  +    public static final String I18N_DEFAULT_CATALOGUE_ID = "default-catalogue-id";
  +
  +    /**
        * This configuration parameter specifies the message that should be
        * displayed in case of a not translated text (message not found).
        */
  @@ -706,6 +745,10 @@
       // Component manager for this component
       private ComponentManager manager;
   
  +    private SourceResolver sourceResolver;
  +
  +    private Map objectModel;
  +
       // Current state of the transformer. The value is STATE_OUTSIDE by default.
       private int current_state;
   
  @@ -721,6 +764,10 @@
       // used as default value.
       private String current_key;
   
  +    // Contains the id of the current catalogue if it was explicitely mentioned
  +    // on an i18n:text element, otherwise it is null.
  +    private String currentCatalogueId;
  +
       // A flag for copying the node when doing in-place translation
       private boolean translate_copy;
   
  @@ -758,8 +805,11 @@
       // Date and number elements and params formatting attributes with values.
       private HashMap formattingParams;
   
  -    // Dictionary data
  -    private Bundle dictionary;
  +    // Default catalogue
  +    private Bundle defaultCatalogue;
  +
  +    // All catalogues (hashed on catalgue id). The values are instances of CatalogueInfo.
  +    private Map catalogues = new HashMap();
   
       // Dictionary loader factory
       private BundleFactory factory;
  @@ -774,6 +824,9 @@
       // Catalogue location value
       private String catalogueLocation;
   
  +    // id of the default catalogue
  +    private String defaultCatalogueId;
  +
       // Untranslated message value
       private String untranslated;
   
  @@ -833,22 +886,54 @@
           }
           // read in the config options from the transformer definition
   
  +        // there are two possible configuration methods:
  +        //  (1) the one dating from before multiple catalogues were supported:
  +        //      in this case the elements catalogue-name and catalogue-location specify
  +        //      the catalogue to be used
  +        //  (2) the new one supporting mulitple catalogues
  +
           // 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);
   
           // obtain the directory location of message catalogues
           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)
  -            throw new ConfigurationException(
  -                    "I18nTransformer requires the name and location of " +
  -                    "the message catalogues"
  -            );
  +        Configuration cataloguesConf = conf.getChild("catalogues", false);
  +
  +        if ((catalogueName != null || catalogueLocation != null) && cataloguesConf != null) {
  +            // if old and new style configuration are used at the same time...
  +            throw new ConfigurationException("I18nTransformer: old and new configuration style are used at the same time. Use either the 'catalogue-name' and 'catalogue-location' elements or use the 'catalogues' element, but don't use both at the same time.");
  +        } else if (catalogueName != null || catalogueLocation != null) {
  +            if (!(catalogueName != null && catalogueLocation != null))
  +                throw new ConfigurationException("I18nTransformer: catalogue-name and catalogue-location must both be specified");
  +            if (getLogger().isDebugEnabled())
  +                getLogger().debug("using old-style configuration: name = " + catalogueName
  +                        + ", location = " + catalogueLocation);
  +        } else if (cataloguesConf == null) {
  +            // if both old and new style configuration are missing ...
  +            throw new ConfigurationException("Missing configuration for the I18nTransformer: a 'catalogues' element specifying the catalogues is required.");
  +        } else {
  +            // new configuration style
  +            Configuration[] catalogueConfs = cataloguesConf.getChildren("catalogue");
  +            for (int i = 0; i < catalogueConfs.length; i++) {
  +                String id = catalogueConfs[i].getAttribute("id");
  +                String name = catalogueConfs[i].getAttribute("name");
  +                String location = catalogueConfs[i].getAttribute("location");
  +                CatalogueInfo newCatalogueInfo;
  +                try {
  +                    newCatalogueInfo = new CatalogueInfo(name, location);
  +                } catch (PatternException e) {
  +                    throw new ConfigurationException("I18nTransformer: error in name or location attribute on catalogue element with id " + id, e);
  +                }
  +                catalogues.put(id, newCatalogueInfo);
  +            }
  +
  +            defaultCatalogueId = cataloguesConf.getAttribute("default");
  +            if (!catalogues.containsKey(defaultCatalogueId))
  +                throw new ConfigurationException("I18nTransformer: default catalogue id '" + defaultCatalogueId + "' denotes a nonexisting catalogue");
  +        }
   
           // obtain default text to use for untranslated messages
           child = conf.getChild(I18N_UNTRANSLATED);
  @@ -870,14 +955,19 @@
                         Parameters parameters)
               throws ProcessingException, SAXException, IOException {
   
  +        this.sourceResolver = resolver;
  +        this.objectModel = objectModel;
  +
           try {
               // check parameters to see if anything has been locally overloaded
               String localCatLocation = null;
               String localCatName = null;
               String localUntranslated = null;
               String lc = null;
  +            String localDefaultCatalogueId = null;
   
               if (parameters != null) {
  +                // localCatLocation and localCatName are to support old-style configuration
                   localCatLocation =
                           parameters.getParameter(I18N_CATALOGUE_LOCATION, null);
                   localCatName =
  @@ -885,6 +975,7 @@
                   localUntranslated =
                           parameters.getParameter(I18N_UNTRANSLATED, null);
                   lc = parameters.getParameter(I18N_LOCALE, null);
  +                localDefaultCatalogueId = parameters.getParameter(I18N_DEFAULT_CATALOGUE_ID, null);
               }
   
               // if untranslated-text has been overridden, save the original
  @@ -894,27 +985,17 @@
                   untranslated = localUntranslated;
               }
   
  -            // configure the factory
  -            _setup(resolver, localCatLocation == null ? catalogueLocation
  -                             : localCatLocation);
  -
               // Get current locale
               Locale locale = I18nUtils.parseLocale(lc);
  -            debug("using locale " + locale.toString());
  -
  -            // setup everything for the current locale
  -            dictionary = (Bundle)factory.select(
  -                    localCatName == null ? catalogueName : localCatName,
  -                    locale
  -            );
  -            debug( "selected dictionary " + dictionary );
  +            if (getLogger().isDebugEnabled())
  +                debug("using locale " + locale.toString());
   
               // Initialize instance state variables
  -
               this.locale             = locale;
               this.current_state      = STATE_OUTSIDE;
               this.prev_state         = STATE_OUTSIDE;
               this.current_key        = null;
  +            this.currentCatalogueId = null;
               this.translate_copy     = false;
               this.tr_text_recorder   = null;
               this.text_recorder      = new MirrorRecorder();
  @@ -926,6 +1007,34 @@
               this.formattingParams   = null;
               this.strBuffer          = null;
   
  +            // give the defaultCatalogue variable its value -- first look if it's locally overridden
  +            // and otherwise use the component-wide defaults.
  +            if (localCatLocation != null || localCatName != null) {
  +                // first local old configuration style
  +                localCatName = localCatName != null ? localCatName : catalogueName;
  +                localCatLocation = localCatLocation != null ? localCatLocation : catalogueLocation;
  +                if (localCatName == null || localCatLocation == null)
  +                    throw new ProcessingException("I18nTransformer: incorrect usage: either catalogue-name or catalogue-location are not specified.");
  +                defaultCatalogue = getCatalogue(localCatName, localCatLocation);
  +            } else if ((localDefaultCatalogueId != null || localCatLocation != null) && localCatName != null) {
  +                // throw error if old and new configuration style are used at the same time
  +                throw new ProcessingException("I18nTransformer: either specify 'catalogue-name' and 'catalogue-location' or specify 'default-catalogue-id', but don't mix the two configuration styles.");
  +            } else if (localDefaultCatalogueId != null) {
  +                // then if new local configuration style
  +                CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(localDefaultCatalogueId);
  +                if (catalogueInfo == null)
  +                    throw new ProcessingException("I18nTransformer: '" + localDefaultCatalogueId + "' is not an existing catalogue id.");
  +                defaultCatalogue = catalogueInfo.getCatalogue();
  +            } else if (catalogueName != null && catalogueLocation != null) {
  +                // then global old configuration style
  +                defaultCatalogue = getCatalogue(catalogueName, catalogueLocation);
  +            } else {
  +                // then global new configuration style
  +                defaultCatalogue = ((CatalogueInfo)catalogues.get(defaultCatalogueId)).getCatalogue();
  +            }
  +            if (getLogger().isDebugEnabled())
  +                getLogger().debug("using default catalogue " + defaultCatalogue);
  +
               // Create and initialize a formatter
               this.formatter = new MessageFormat("");
               this.formatter.setLocale(locale);
  @@ -936,15 +1045,18 @@
           }
       }
   
  +    private Bundle getCatalogue(String name, String location) throws Exception
  +    {
  +        configureFactory(location);
  +        return (Bundle)factory.select(name, locale);
  +    }
       /**
  -     * Internal setup of XML resource bundle and factory.
  +     * Internal setup of XML resource factory.
        *
        * REVISIT (MC): 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 configureFactory(String location) throws Exception {
           // configure the factory to log correctly and cache catalogues
           DefaultConfiguration configuration =
                   new DefaultConfiguration("name", "location");
  @@ -966,18 +1078,19 @@
           debug("catalog location:" + location);
           Source source = null;
           try {
  -            source = resolver.resolveURI(location);
  +            source = sourceResolver.resolveURI(location);
               String systemId = source.getURI();
               debug("catalog directory:" + systemId);
               dirConf.setValue(systemId);
               configuration.addChild(dirConf);
           } finally {
  -            resolver.release(source);
  +            if (source != null)
  +                sourceResolver.release(source);
           }
   
           // Pass created configuration object to the factory
           ((Configurable)factory).configure(configuration);
  -        debug("configured");
  +        debug("factory configured");
       }
   
       //
  @@ -1088,19 +1201,11 @@
               prev_state = current_state;
               current_state = STATE_INSIDE_TEXT;
               current_key = attr.getValue(I18N_NAMESPACE_URI, I18N_KEY_ATTRIBUTE);
  +            currentCatalogueId = attr.getValue(I18N_NAMESPACE_URI, I18N_CATALOGUE_ATTRIBUTE);
               if (prev_state != STATE_INSIDE_PARAM)
                   tr_text_recorder = null;
   
  -            // Better send an exception to prevent users from this?
  -            if (current_key == null && prev_state == STATE_INSIDE_TRANSLATE) {
  -                debug("WARNING: no key for text in translate! Don't do this!");
  -                /*throw new SAXException(this.getClass().getName()
  -                + ": you must provide a key when using i18n:text"
  -                + " within i18n:translate"
  -                + " Current state: " + current_state);
  -                */
  -            }
  -            else if (current_key != null) {
  +            if (current_key != null) {
                   tr_text_recorder = getMirrorRecorder(current_key, null);
                   //debug("Got translation: " + tr_text_recorder);
               }
  @@ -1423,8 +1528,17 @@
                   int attr_index = temp_attr.getIndex(attr_name);
                   if (attr_index != -1) {
                       String text2translate = temp_attr.getValue(attr_index);
  +                    // check if the text2translate contains a colon, if so the text before
  +                    // the colon denotes a catalogue id
  +                    int colonPos = text2translate.indexOf(":");
  +                    String catalogueId = null;
  +                    if (colonPos != -1)
  +                    {
  +                        catalogueId = text2translate.substring(0, colonPos);
  +                        text2translate = text2translate.substring(colonPos + 1, text2translate.length());
  +                    }
                       String result =
  -                            getString(text2translate, (untranslated == null)
  +                            getString(text2translate, catalogueId, (untranslated == null)
                                                         ? text2translate
                                                         : untranslated);
   
  @@ -1459,7 +1573,8 @@
                           tr_text_recorder = getMirrorRecorder(text_recorder.text(), text_recorder);
                       } else {
                           // We have the key, but couldn't find a transltation
  -                        debug("translation not found for key " + current_key);
  +                        if (getLogger().isDebugEnabled())
  +                            debug("translation not found for key " + current_key);
                           tr_text_recorder = text_recorder;
                       }
                   }
  @@ -1471,12 +1586,15 @@
                   text_recorder.recycle();
                   tr_text_recorder = null;
                   current_key = null;
  +                currentCatalogueId = null;
                   break;
   
               case STATE_INSIDE_TRANSLATE:
                   if (tr_text_recorder == null) {
                       if (!text_recorder.empty()) {
  -                        tr_text_recorder = (MirrorRecorder) text_recorder.clone();
  +                        tr_text_recorder = getMirrorRecorder(text_recorder.text(), text_recorder);
  +                        if (tr_text_recorder == text_recorder) // if the default value was returned
  +                            tr_text_recorder = (MirrorRecorder) text_recorder.clone();
                       }
                   }
   
  @@ -1533,9 +1651,11 @@
       }
   
       private void endTranslateElement() throws SAXException {
  -        if (indexedParams.size() > 0 && tr_text_recorder != null) {
  -            debug("End of translate with params");
  -            debug("Fragment for substitution : " + tr_text_recorder.text());
  +        if (tr_text_recorder != null) {
  +            if (getLogger().isDebugEnabled()) {
  +                debug("End of translate with params");
  +                debug("Fragment for substitution : " + tr_text_recorder.text());
  +            }
               tr_text_recorder.send(super.contentHandler, indexedParams);
               tr_text_recorder = null;
               text_recorder.recycle();
  @@ -1849,12 +1969,31 @@
   
       //-- Dictionary handling routins
   
  -    // Helper method to retrieve a message from the dictionary.
  -    // mattam: now only used for i:attr.
  -    // A default value is returned if message is not found
  -    private String getString(String key, String defaultValue) {
  +    /**
  +     * Helper method to retrieve a message from the dictionary.
  +     * mattam: now only used for i:attr.
  +     * A default value is returned if message is not found
  +     *
  +     * @param catalogueId if not null, this catalogue will be used instead of the default one.
  +     */
  +    private String getString(String key, String catalogueId, String defaultValue) {
           try {
  -            Node res = (Node)dictionary.getObject(
  +            Bundle catalogue = defaultCatalogue;
  +            if (catalogueId != null) {
  +                CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(catalogueId);
  +                if (catalogueInfo == null) {
  +                    if (getLogger().isDebugEnabled())
  +                        debug("Catalogue not found: " + catalogueId + ", could not translate key " + key);
  +                    return defaultValue;
  +                }
  +                try {
  +                    catalogue = catalogueInfo.getCatalogue();
  +                } catch (Exception e) {
  +                    getLogger().error("Error getting catalogue " + catalogueInfo.getName() + " from location " + catalogueInfo.getLocation() + " for locale " + locale + ", will not translate key " + key);
  +                    return defaultValue;
  +                }
  +            }
  +            Node res = (Node)catalogue.getObject(
                       I18N_CATALOGUE_PREFIX + "[@key='" + key + "']");
   
               String value = getTextValue(res);
  @@ -1889,8 +2028,23 @@
       // A default value is returned if message is not found
       private MirrorRecorder getMirrorRecorder(String key, MirrorRecorder defaultValue) {
           try {
  +            Bundle catalogue = defaultCatalogue;
  +            if (currentCatalogueId != null) {
  +                CatalogueInfo catalogueInfo = (CatalogueInfo)catalogues.get(currentCatalogueId);
  +                if (catalogueInfo == null) {
  +                    if (getLogger().isDebugEnabled())
  +                        debug("Catalogue not found: " + currentCatalogueId + ", could not translate key " + key);
  +                    return defaultValue;
  +                }
  +                try {
  +                    catalogue = catalogueInfo.getCatalogue();
  +                } catch (Exception e) {
  +                    getLogger().error("Error getting catalogue " + catalogueInfo.getName() + " from location " + catalogueInfo.getLocation() + " for locale " + locale + ", will not translate key " + key);
  +                    return defaultValue;
  +                }
  +            }
               MirrorRecorder value = new MirrorRecorder (
  -                    (Node)dictionary.getObject(
  +                    (Node)catalogue.getObject(
                               I18N_CATALOGUE_PREFIX + "[@key='" + key + "']"));
   
               if (value == null)
  @@ -1917,8 +2071,21 @@
               untranslated = globalUntranslated;
           }
   
  -        factory.release(dictionary);
  -        dictionary = null;
  +        // clean up default catalogue
  +        factory.release(defaultCatalogue);
  +        defaultCatalogue = null;
  +
  +        // clean up the other catalogues
  +        Iterator catalogueIt = catalogues.values().iterator();
  +        while (catalogueIt.hasNext())
  +        {
  +            CatalogueInfo catalogueInfo = (CatalogueInfo)catalogueIt.next();
  +            catalogueInfo.releaseCatalog();
  +        }
  +
  +        sourceResolver = null;
  +        objectModel = null;
  +
           super.recycle();
       }
   
  @@ -1927,6 +2094,57 @@
               this.manager.release(this.factory);
           }
           factory = null;
  +    }
  +
  +    /**
  +     * Holds information about one catalogue. The location and name of the catalogue
  +     * can contain references to input modules, and are resolved upon each transformer
  +     * usage. It is important that releaseCatalog is called when the transformer is recycled.
  +     */
  +    private final class CatalogueInfo {
  +        PreparedVariableResolver name;
  +        PreparedVariableResolver location;
  +        String resolvedName;
  +        String resolvedLocation;
  +        Bundle catalogue;
  +
  +        public CatalogueInfo(String name, String location) throws PatternException {
  +            this.name = new PreparedVariableResolver(name, manager);
  +            this.location = new PreparedVariableResolver(location, manager);
  +        }
  +
  +        public String getName() {
  +            return resolvedName;
  +        }
  +
  +        public String getLocation() {
  +            return resolvedLocation;
  +        }
  +
  +        private void resolve() throws PatternException {
  +            if (resolvedLocation == null)
  +                resolvedLocation = location.resolve(null, objectModel);
  +            if (resolvedName == null)
  +                resolvedName = name.resolve(null, objectModel);
  +        }
  +
  +        public Bundle getCatalogue() throws Exception {
  +            if (catalogue == null) {
  +                resolve();
  +                configureFactory(resolvedLocation);
  +                catalogue = (Bundle)factory.select(resolvedName, locale);
  +            }
  +            return catalogue;
  +        }
  +
  +        public void releaseCatalog() {
  +            // would it be necessary to first configure the factory here?
  +            if (catalogue != null)
  +                factory.release(catalogue);
  +            catalogue = null;
  +            resolvedName = null;
  +            resolvedLocation = null;
  +        }
       }
   
   /*
  
  
  
  1.6       +1 -0      xml-cocoon2/src/webapp/samples/i18n/menu.xml
  
  Index: menu.xml
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/webapp/samples/i18n/menu.xml,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- menu.xml	31 Jan 2003 23:30:01 -0000	1.5
  +++ menu.xml	19 Feb 2003 07:19:10 -0000	1.6
  @@ -13,6 +13,7 @@
       <menu-item label="Static (XML)" href="simple.xml" i18n:attr="label"/>
       <menu-item label="Dynamic (XSP)" href="simple.xsp" i18n:attr="label"/>
       <menu-item label="Sitemap source" href="sitemap.xmap" i18n:attr="label"/>    
  +    <menu-item label="Multi catalogues" href="multi.xml" i18n:attr="label"/>    
     </menu>  
    
     <menu label="Locales" i18n:attr="label">
  
  
  
  1.7       +6 -6      xml-cocoon2/src/webapp/samples/i18n/sitemap.xmap
  
  Index: sitemap.xmap
  ===================================================================
  RCS file: /home/cvs/xml-cocoon2/src/webapp/samples/i18n/sitemap.xmap,v
  retrieving revision 1.6
  retrieving revision 1.7
  diff -u -r1.6 -r1.7
  --- sitemap.xmap	13 Nov 2002 22:37:00 -0000	1.6
  +++ sitemap.xmap	19 Feb 2003 07:19:10 -0000	1.7
  @@ -8,10 +8,10 @@
               <!-- Configure i18n transformer -->
               <map:transformer name="i18n" logger="sitemap.transformer.i18n" 
                   src="org.apache.cocoon.transformation.I18nTransformer">
  -                <!-- This parameter sets the base name for dictionary files -->                
  -                <catalogue-name>messages</catalogue-name>
  -                <!-- This parameter sets the path where dictionaries are placed-->
  -                <catalogue-location>translations</catalogue-location>                
  +                <catalogues default="messages">
  +                  <catalogue id="messages" name="messages" location="translations"/>
  +                  <catalogue id="menu" name="menu" location="translations"/>
  +                </catalogues>
                   <cache-at-startup>true</cache-at-startup>
               </map:transformer>
           </map:transformers>
  @@ -107,8 +107,8 @@
                   <map:match pattern="menu/*">
                           <map:generate src="menu.xml"/>
                           <map:transform type="i18n">
  -                            <!-- Override default catalog name for this pipeline -->
  -                            <map:parameter name="catalogue-name" value="menu"/>
  +                            <!-- Override default catalogue for this pipeline -->
  +                            <map:parameter name="default-catalogue-id" value="menu"/>
                               <map:parameter name="locale" value="{../locale}"/>
                           </map:transform>
                           <map:transform src="menu2html.xsl">
  
  
  
  1.1                  xml-cocoon2/src/webapp/samples/i18n/multi.xml
  
  Index: multi.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  <root xmlns:i18n="http://apache.org/cocoon/i18n/2.1" i18n:attr="language" language="language">
  	<title>
  		<i18n:text>titletext</i18n:text>
  	</title>
    <content>
      <para>
        This simple example shows how to use mulitple catalogues concurrently.
      </para>
      <para>Here we take the key 'language' from the default (= messages) catalogue: <i18n:text>language</i18n:text>.</para>
      <para>Here we take the key 'Documentation' from the menu catalogue: <i18n:text i18n:catalogue="menu">Documentation</i18n:text>.</para>
    </content>
  </root>
  
  
  

----------------------------------------------------------------------
In case of troubles, e-mail:     webmaster@xml.apache.org
To unsubscribe, e-mail:          cocoon-cvs-unsubscribe@xml.apache.org
For additional commands, e-mail: cocoon-cvs-help@xml.apache.org