Sunday, November 15, 2009

GWT: Client-side vs Server-side locale selection

When you deploy a GWT application which uses I18N, gwt's compiler generates a file for each browser-language pair. So, before the application is loaded, it is necessary to load a minimal amount of javascript code which figures out which is the appropriate file for this user to load.

Right now, the Gwt algorithm used for language detection, only considers the language parameter in the url, the locale meta-tag,  and the javascript variable __gwt_Locale. With these options, the developer has to provide whether links to the user to select the adequate language, or server-side code able to read the Accept-Language header, which has to be called before the gwt application is loaded.  Take a look to this post to know exactly how to do the server-side language detection.

However, it is possible to know the language the user is using asking the browser as is explained in this page.
The language reported by the browser is just the language used for the application (menus, messages, etc), and not the Accept-Language header, but it covers a hight percentage of cases. It works fine when an user selects the language at login time in linux, Mac, other un*x, and with the default user's language in windows.

So, simply adding the code below to your .gwt.xml file, your application will select the correct language for most user cases, and you don't need to add complex stuff to do the selection in server side.

I have sent a patch to gwt guys which I expect could be included soon in the official trunk.

<property-provider name="locale">
    <![CDATA[
      try {
      var locale;
      // Look for the locale as a url argument
      if (locale == null) {
        var args = location.search;
        var startLang = args.indexOf("locale=");
        if (startLang >= 0) {
          var language = args.substring(startLang);
          var begin = language.indexOf("=") + 1;
          var end = language.indexOf("&");
          if (end == -1) {
            end = language.length;
          }
          locale = language.substring(begin, end);
        }
      }
      if (locale == null) {
        // Look for the locale on the web page
        locale = __gwt_getMetaProperty("locale")
      }
      if (locale == null) {
        // Look for an override computed by other means in the selection script
        locale = $wnd['__gwt_Locale'];
      }
      if (locale == null) {
        // Use the browser's locale
        locale = navigator.browserLanguage ? navigator.browserLanguage : navigator.language;
        if (locale != null) {
           locale = locale.replace(/-/g, '_');
        }
      }
      if (locale == null) {
        return "default";
      }
      while (!__gwt_isKnownPropertyValue("locale",  locale)) {
        var lastIndex = locale.lastIndexOf("_");
        if (lastIndex == -1) {
              locale = "default";
          break;
        } else {
          locale = locale.substring(0,lastIndex);
        }
      }
      return locale;
    } catch(e){
      alert("Unexpected exception in locale detection, using default: " + e);
      return "default";
    }
  ]]>
  </property-provider>