Just realized that we need to also localize the Javascript. What would be a good strategy for that? I have four ideas:

  1. Make Javascript files regular JSP files, so we can use normal Java i18n techniques.
    • Problem: Needs careful consideration of caching; otherwise page loads may slow down considerably
  2. Use an AJAX technique to load i18n'd strings into the Javascript
    • Problem: may be too complicated to implement, and also slow
  3. Develop localized versions of each JS file
    • Problem: difficult to keep updated; can't use the Java JAR's to deploy new versions
  4. Remove any user-visible strings from the Javascript
    • Problem: perhaps impossible to do without sacrificing big pieces?

I would like to keep this such that it's possible to deploy a new localization by simply dropping a JAR file in the correct directory. That would mean option #1...


JSPWiki2.6Feature
I would suggest to go for option 1, as part of the commonheader.jsp. Option 2 would be feasible as well, but I see no advantage.

Here is how BrushedTemplate implements some i18n hooks. The localize() function can be called on any string. It tries to match and replace the string with a localized copy. We need a way to grab the Localization javascript object from a .propeties file in the JSP.

/** i18n **/
var Localization = 
{
  "Download Attachment"  : null,
  "No attachment preview": null,
  "( start of page )"    : null,
  "No match found!"      : null,
  /* etc... */
  "More"                 : "More..."
}
String.extend({
    localize: function(){
        var s;
        if(Localization && (s=Localization[this]) ) return s;
        return this;
    }
});

/* somewhere in the js functions */
var x = "More".localize();

--DF


So you would suggest a "localizedstrings.jsp", which is actually a Javascript file, and generated with every new request (could be cached, too; if the user changes the language, they can press shift-refresh)?

BTW, wouldn't it be better to have a constant key, just like in Java, instead of relying on the English string? Otherwise you will need to change many files if your original English changes... We can guarantee an English translation always exists.

--JanneJalkanen, 07-Apr-2007


More simple: retrieve a json object from the DefaultResource.properties file, containing all strings to be localized in the javascript. This could be done in the commonheader.jsp. (or move it to a separate resource file)
<fmt:setBundle basename="templates.DefaultResources"/>

...
<script>
  var Localization = <fmt:message key="common.javascript"/> ;
</script>

In the DefaultResource.properties:

common.javascript={ \
  "Download Attachment"  : "Download Attachment",  \
  "No attachment preview": "Not attachment preview",  \
  "( start of page )"    : "(start of page)",  \
  "No match found!"      : "No match found",  \
  /* etc... */  \
  "More"                 : "More..."  \
}

Of course, we could go for symbolic keys, instead of strings, like you suggested. And even make it more structured. ( this format is not proper JSON, but it correct is javascript ) This format is less robust as previous: when the localised strings object can not be retrieved, there is no way to easily fall back to a default english string.

common.javascript={ \
  attachment: {  \
    download : "Download Attachment",  \
    noPreview : "Not attachment preview"  \
  } , \
  "( start of page )"    : "(start of page)",  \
  "No match found!"      : "No match found",  \
  /* etc... */  \
  "More"                 : "More..."  \
}

It should not be difficult to built in support for parameters, as in "Back to {0}, or {1}".

--DF


Hmm... That'll get awfully complicated to localize (because you must edit it with a text editor, and make sure you don't break the structure). But it's pretty trivial to put in a JSP scriptlet which will just loop through all the keys in a specific bundle, and output them in a format suitable for Javascript.

-- JanneJalkanen


The following scriptlet in commonheader.jsp should do the job. It'll read the Javascript strings from templates/DefaultResources.properties, in exactly the same format as the rest of the scripts. Any key which start with "javascript" is included in the file.

<%-- Get the i18n scripts from the server --%>

<script type="text/javascript">
  var Localization = { <%
     ResourceBundle rb = context.getBundle("templates.DefaultResources");
     for( Enumeration en = rb.getKeys(); en.hasMoreElements(); )
     {
         String key = (String)en.nextElement();
         
         if( key.startsWith("javascript") )
         {
             out.println( "\""+key+"\" : \""+rb.getString(key)+"\",");
         }
     }
  %> }
</script>

Questions: Is commonheader.jsp the right place for this? Would it be better to put in a separate JSP, so that we could include the proper bundle based on the template? (Commonheader.jsp is something which is better kept constant even across different templates).

Other possibility would be to use the script include mechanism we have, and keep this a Java component. It would make the JSP a bit less cluttered.

--JanneJalkanen, 08-Apr-2007


Excellent proposal ! Editing the properties would definitely be mush easier.

So far, I have not seen huge volumes of localisation strings needed in the javascript. So, including all of them it in the commonheader.jsp, and thus the same for ALL JSP pages is ok, IMHO. However, when breaking it up in smaller pieces (say a dedicated set of localisation strings for upload, view or edit) you need to be also more specific on the subset of resources you include. ( key.startsWith("javascript") should become key.startsWith("javascript.view") etc. ). I think this is not worth the effort.

The concept of turning some resources into a JSON object is pretty generic. May be it could be a good candidate for a TLD tag ?

--DF


I did a few quick tests and it seems to work. Adding the following function to jspwiki-common.js gives a simple, Java-like way of getting the string:

getString = function( key )
{
  var s = Localization["javascript."+key];
  if( !s ) return "§§§"+key+"§§§";
  return s;
}

Then, a script looks like this:

  s += "<br /><div onclick='SearchBox.clearRecentSearches()'>"+getString("SearchBox.clearrecent")+"</div>";

and the relevant portion of DefaultResources.properties:

javascript.SearchBox.clearrecent=Clear Recent Searches

Now, how do we tackle arguments ({0}, {1}, etc?)

--JanneJalkanen, 08-Apr-2007


Cool.

For the javascript I would suggest:

String.prototype.localize = function(){
      var s = Localization["javascript."+this];
      return ( s ) ? s : "§§§"+this+"§§§";
    }
});
So now you can use in a script:
  s += "<br /><div onclick='SearchBox.clearRecentSearches()'>"+"SearchBox.clearrecent".localize()+"</div>";

I will digg into the arguments ({0}, {1}, etc?) stuff later. Lunch is ready ;-) --DF


OK, bon appetit :-).

--JanneJalkanen, 08-Apr-2007


An useful link:

--JanneJalkanen, 08-Apr-2007


Yup, the following works:

getString = function( key )
{
  var s = Localization["javascript."+key];
  if( !s ) return "§§§"+key+"§§§";
  
  for (var i = 1; i < arguments.length; i++)
  {
    s = s.replace("{" + (i-1) + "}", arguments[i]);
  }
  
  return s;
}
...
 s += "<br /><div onclick='SearchBox.clearRecentSearches()'>"+getString("SearchBox.clearrecent",1)+"</div>";
...
javascript.SearchBox.clearrecent=Clear {0} Recent Searches

I don't quite know how that would then be transformed to use your proposal of the localize() -method, and which one would be better.

--JanneJalkanen, 08-Apr-2007


Try following js snippet:
String.prototype.localize = function(){
      var s = Localization["javascript."+this];
      if( !s ) return(  "§§§" + this + "§§§"  );
      for (var i = 0; i < arguments.length; i++)
      {
        s = s.replace("{" + i + "}", arguments[i]);
      }  
      return s;
    }
});
...
 s += "<br /><div onclick='SearchBox.clearRecentSearches()'>"+ "SearchBox.clearrecent".localize(1) + "</div>";
...
javascript.SearchBox.clearrecent=Clear {0} Recent Searches
Which one to choose is a matter of taste.

--DF


Okeydokey, all this goodness is now in 2.5.41. I used the string prototype; it has some geeky beauty that I like :-)

--JanneJalkanen, 10-Apr-2007


Hi guys,

Thanks for the tips on Javascript localization, it was a big help. I also looked at JSGettext, but this seems more straightforward and in keeping with the Java way of doing things (i.e., simple property files).

I did make some minor modifications to what you outlined here, mostly to ensure I was using the browser's locale setting. Here's my modified version of the Localization variable:

var localizedStrings = { <%
   Locale locale = request.getLocale();
   ResourceBundle rb = ResourceBundle.getBundle("MyRb", locale);
   for( Enumeration<String> en = rb.getKeys(); en.hasMoreElements(); )
   {
      String key = (String)en.nextElement();				       
      out.println( "\"" + key + "\" : \"" + rb.getString(key) + "\"," );
   }
%> }

Cheers,

Rich 道道道的的得到的得到的的得到的得到的得到的的得到的

--Rich, 01-Sep-2009 19:43


Hi there !

I'm in progress with i18n providing among JS files and found this nice knowledge sharing. As I see the code:

getString = function( key )
{
  var s = Localization["javascript."+key];
  if( !s ) return "§§§"+key+"§§§";
  
  for (var i = 1; i < arguments.length; i++)
  {
    s = s.replace("{" + (i-1) + "}", arguments[i]);
  }
  
  return s;
}
...
 s += "<br /><div onclick='SearchBox.clearRecentSearches()'>"+getString("SearchBox.clearrecent",1)+"</div>";
...
javascript.SearchBox.clearrecent=Clear {0} Recent Searches

Should "provide" i18n support among JS codeline. But, maybe my question is silly: How to append resource bundle, I mean property file within this code ? Could somebody put an example of fully localized js (without JSP usage)with property file append

--AnonymousCoward, 20-Jun-2012 11:38

Add new attachment

Only authorized users are allowed to upload new attachments.
« This page (revision-22) was last changed on 20-Jun-2012 11:38 by AnonymousCoward