11 Replies Latest reply on Sep 15, 2006 4:14 AM by nhpvti

    Step-by-step localization guide

    nhpvti

      You know, most of us in the world are natively speaking not English.
      So I think that it would make sense to pre-configure Seam samples and seam-gen to produce ready-to-localize applications. I spent last Friday by figuring out how to fill this gap.

      Here is my step-by-step localization guide compiled from different sources on net:

      1) add new supported locale
      in .../WEB-INF/faces-config.xml for example:

      <locale-config>
       <default-locale>en</default-locale>
       <supported-locale>en</supported-locale>
       <supported-locale>de</supported-locale>
       <supported-locale>ru</supported-locale>
      </locale-config>
      

      2) Create new messages files, in my example messages_de.properties and messages_ru.properties
      The 1st obstacle that should be considered here: property files should be UTF-8 encoded and transformed into ASCII by escaping UTF-8 characters (mix of ISO char sets is not advisable in a multi-language web application).
      Built-in Eclipse properties editor cannot handle this, so I've installed Resource Bundle editor from
      http://sourceforge.net/projects/eclipse-rbe/ (thank you guys for excellent work!)
      Alternatively you can use utility from JDK, e.g.:
      native2ascii.exe -encoding utf-8 messages_ru.txt messages_ru.properties

      3) Configure your code editors to use UTF-8.
      For example in Eclipse 3.2 check settings under i. Window-Preferences-General-Workspace-Text and ii. Window-Preferences-Web and XML-CSS Files, HTML Files, JSP Files, XML Files

      4) In order to enable UTF-8 in input fields (the 2nd big obstacle) you will need a phase listener (compensating bug in myfaces and(or) JSF components?) that re-sets request char set to UTF-8.
      To do this follow instructions found in
      https://facelets.dev.java.net/servlets/ReadMsg?list=users&msgNo=1403
      (see also discussion here http://www.jboss.com/?module=bb&op=viewtopic&t=73369)

      and activate the listener in the .../WEB-INF/faces-config.xml file, e.g.:
      <lifecycle>
      <phase-listener>org.jboss.seam.jsf.SeamExtendedManagedPersistencePhaseListener</phase-listener>
      <phase-listener>com.xxx.yyy.zzz.util.UTF8PhaseListener</phase-listener>
      </lifecycle>


      5) Set char set in template(s) to UTF-8:
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" pageEncoding="UTF-8" />


      6) The last, MySQL specific, obstacle eaten much time to find out a solution: force JDBC driver to use UTF-8, otherwise only ? characters will be saved in the database. So extend JDBC url in a data source file, e.g.:
      <jndi-name>myDatasource</jndi-name>
      <connection-url>jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&charactetrResultSets=utf8</connection-url>
      <driver-class>com.mysql.jdbc.Driver</driver-class>


      7) Create database with default UTF-8 support, for example for MySQL:
      CREATE DATABASE mydb DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
      

      Note: probably you have to change default tables type to InnoDB for transactions support. You can do this by adding start parameter (or via MySQL configuration file). On SuSE 9.2 this can look so:
      in /etc/init.d/mysql.server:
      $bindir/mysqld_safe --datadir=$datadir --default-table-type=innodb --pid-file=$pid_file >/dev/null 2>&1 &


      Ready, now you have your native language as static text, data input and database output.

      I hope this small contribution will save you a couple of hours.
      And I hope that others in this forum will follow my example and share their experience in more detail, not simply stating: "I've solved the problem XYZ!"

        • 1. Re: Step-by-step localization guide
          mrohad

          that's a great - thanks , I wonder what design pattern do you use with seam/ejb3.0 in order to retrieve text in different languges from the db to the JSF pages

          • 2. Re: Step-by-step localization guide

            In CVS version of Seam is SeamCharacterEncodingFilter class. This should make UTF8PhaseListener obsolete (can someone confirm that?). You must put this in web.xml:

            <filter>
             <filter-name>Character Encoding</filter-name>
             <filter-class>
             org.jboss.seam.servlet.SeamCharacterEncodingFilter
             </filter-class>
             <init-param>
             <param-name>encoding</param-name>
             <param-value>UTF-8</param-value>
             </init-param>
             <init-param>
             <param-name>overrideClient</param-name>
             <param-value>true</param-value>
             </init-param>
             </filter>


            • 3. Re: Step-by-step localization guide
              nhpvti

               

              "mrohad" wrote:
              that's a great - thanks , I wonder what design pattern do you use with seam/ejb3.0 in order to retrieve text in different languges from the db to the JSF pages


              It should be misunderstanding. I'm speaking about user input in native language that should be stored in a database and retrieved from it. Localized texts are in properties files and facelet templates. Not figured out yet the best way to maintain and include localized static text blocks,
              any expierence?

              • 4. Re: Step-by-step localization guide
                perwik

                One option would be to see each static content item as one object and then connect a number of translations to it. Then you show the translation corresponding to the current locale using the same rules as the properties files (i.e showing the default locale when the selected locale is not in the list of supported locales).

                An other (although similar way) would be to see each translation as a single object with no connections to each other. I think this depends a bit on if you want your site to have an exact copy of everything in each language or if the english version should have content that does not exists in the german version and so on.

                • 5. Re: Step-by-step localization guide
                  nhpvti

                   

                  "perwik" wrote:
                  One option would be to see each static content item as one object and then connect a number of translations to it. Then you show the translation corresponding to the current locale using the same rules as the properties files (i.e showing the default locale when the selected locale is not in the list of supported locales).


                  Thank you for the idea, but maintenance question is still open in this case. Or how are you going to populate this object with content: from a data base? Then a custom-made CMS would be necessary to maintain texts in the database...
                  By the way content can require for example html to be included. Properties files don't allow entering html according to my quick test.

                  So I would prefer simple <ui:include> tag:
                  <div class="header"><ui:include src="/WEB-INF/layout/header#{messages['application.fileSuffix']}.xhtml" /></div>


                  Any concerns?

                  • 6. Re: Step-by-step localization guide
                    perwik

                    Oh, whith "object" and "connections" etc. I was thinking of a database.

                    Your solution works well with content that is really static. But I'd prefer some sort of wiki/CMS functionality so that users can edit the content without access to the .xhtml files (which they shouldn't even have to know or care about).
                    There is no cookbook for this but I thought we were talking design patterns here and not ready made solutions.

                    • 7. Re: Step-by-step localization guide
                      nhpvti

                       

                      "perwik" wrote:
                      Oh, whith "object" and "connections" etc. I was thinking of a database.


                      I have concerns about retrieving static information at runtime from a database, even using some kind of caching.

                      Why not to use some standard CMS to produce templates and this way avoid overhead at runtime?

                      A standard CMS product can afford fine-grained access management via user roles, mature GUI, some kind of preview mode, version control Etc.

                      I've tried custom navigation handler for adding language suffix at the end of an action, for example: register_en, register_de

                      It works, but in this case I have to duplicate navigation rules :-(

                      <navigation-rule>
                       <navigation-case>
                       <from-outcome>register_en</from-outcome>
                       <to-view-id>/register_en.xhtml</to-view-id>
                       <redirect />
                       </navigation-case>
                       <navigation-case>
                       <from-outcome>register_de</from-outcome>
                       <to-view-id>/register_de.xhtml</to-view-id>
                       <redirect />
                       </navigation-case>
                      </navigation-rule>


                      I still have to check whether Seam page flow can be used easier for supporting my approach.


                      • 8. Re: Step-by-step localization guide
                        perwik

                        It doesn't work, but something like this would be nicer:

                        <navigation-rule>
                         <navigation-case>
                         <from-outcome>register</from-outcome>
                         <to-view-id>/register_#{locale}.xhtml</to-view-id>
                         <redirect />
                         </navigation-case>
                        </navigation-rule>
                        


                        but why do this at all when you can have register.xhtml include the locale specific parts instead?

                        • 9. Re: Step-by-step localization guide
                          nhpvti

                           

                          "perwik" wrote:

                          but why do this at all when you can have register.xhtml include the locale specific parts instead?


                          In order to reduce number of templates to be generated and supported. Besides CMS contributors should get a "big picture" in preview mode, ideally the whole content part in order to get approximate idea how it could look at runtime.

                          • 10. Re: Step-by-step localization guide
                            lcoetzee

                            For those guys out there using postgresql this is how to create a UTF-8 db:


                            #On linux su to postgres user
                            su -c "su -s /bin/sh postgres"

                            #Create the db user
                            createuser -d -P -E dbUser



                            #create the instance
                            createdb -E UNICODE instance -O dbUser

                            #To see if it worked:
                            psql -l

                            #Results in
                            instance | dbUser | UTF8

                            Regards

                            Louis



                            • 11. Re: Step-by-step localization guide
                              nhpvti

                               

                              "nhpvti" wrote:

                              I've tried custom navigation handler for adding language suffix at the end of an action, for example: register_en, register_de


                              My custom view handler works seamless with JSF navigation and Seam pageflow and forces templates names in the form of name_loc.xhtml, for example register_en.xhtml, register_de.xhtml Etc.

                              Hope that this won't cause any complications in future versions of Faces or Seam.

                              Here the details:

                              1) In faces-config.xml (for example):

                              <application>
                               <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
                               <view-handler>com.mycompany.util.LocaleViewHandler</view-handler>
                               <message-bundle>messages</message-bundle>
                               <locale-config>
                               <default-locale>en</default-locale>
                               <supported-locale>en</supported-locale>
                               <supported-locale>de</supported-locale>
                               <supported-locale>ru</supported-locale>
                               </locale-config>
                              </application>


                              2) Implement LocaleViewHandler class (some additional methods have to be implemented, but they should simply wrap the base class functionality)


                              import javax.faces.application.ViewHandler;
                              ...
                              public class LocaleViewHandler extends ViewHandler
                              {
                               private ViewHandler base;
                               private static final String separator = "_";
                               private static final String actionExtension = ".seam";
                              
                               public LocaleViewHandler(ViewHandler base)
                               {
                               super();
                               this.base = base;
                               }
                              
                               public String getActionURL(FacesContext context, String viewId)
                               {
                               StringBuilder newActionURL = null;
                               String actionURL = this.base.getActionURL(context, viewId);
                               StringBuilder localeSuffix = new StringBuilder(separator);
                               localeSuffix.append(LocaleSelector.instance().getLocaleString());
                               StringBuilder localizedEnd = new StringBuilder(localeSuffix);
                               localizedEnd.append(actionExtension);
                               int pointPosition = actionURL.indexOf(actionExtension);
                               if (pointPosition != -1 && actionURL.indexOf(localizedEnd.toString()) == -1)
                               {
                               newActionURL = new StringBuilder(actionURL.substring(0, pointPosition));
                               newActionURL.append(localizedEnd);
                               return newActionURL.toString();
                               } else
                               {
                               return actionURL;
                               }
                               }
                              ...
                              }