Internationalization of portlets

I want to know how Vaadin faces the internacionalization task for portlets.

Portlets have a special requirement: the locale can change at any moment in the middle of the portlet lifecycle.

Application should be unaware of such changes. But if you set a label text as

thisLabel.setCaption(ResourceBundle.getBundle("....").getString())

The text is correctly set at that moment, but if the locale changes later, the caption does not.

It would be a nightmare to set again all the strings upon locale change.

I think the right way is strings be a key to retrieve the localized strings from a bundle. I’d like to know how Vaadin helps to do this.

Thanks

I think you have face your nightmare here :slight_smile: Once the caption, title, label content is set there is no automatic mechanism to translate it all again.

In some systems I’ve made my own extensions for this. However, the approach of automatically re-translating stuff always has it it’s limitations in stateful applications (for example a label may have been changed later and automatic re-translating misses this). Therefore, I mostly used the following approach to balance between flexibility and developer easiness (i.e. my laziness):

  • Externalize your initialization of UI to separate function that uses
    Application.getLocale()
    to retrieve current language. Furthermore for larger UIs it is probably in separate classes also.
  • Override the setLocale in your application class to save the current state (the data+view user had) and to to call these UI init functions and re-init the full user interface. This loses the users state.
  • Navigate back to saved state. Mainly, show the view and set data. I would not go too much details how “transparent” the change is for the user as Locale changes may have (valid) side-effects on layout, data, etc also.
  • Wire the setLocale to your locale change mechanism - portals or browsers locale for example.

Maybe you could try this too?

I think this could be done without great difficulty, for the developer and for the Vaadin people, by adapting what many web applications do: teh developer writes keys and the text is extracted from a bundle using that key.

There are two i18n levels

  1. basic texts: label captions, tooltips, table column captions, etc
  2. advanced texts: listbox, combo and table column values

I propose someting like this: The Vaadin translator/renderer of Java components could analyze the texts to be rendered:

if text begins with a ‘$’ and is not a single ‘$’, assume a pattern like this

$<classpath.to.resource.bundle>:

(to escape not i18n texts beginning with ‘$’, escape it to ‘$$’)

Each time a page has to be rerendered, i18n’ed strings should be taken from the bundles. Current locale will force to retrieve the right translation.

I18n support could be activated with a parameter. That could add some extra work, as internationalized applications require, but it frees the developer to handle it.

Hi,

I haven’t used portlets myself, and am unlikely to; however, I can’t imagine that “live” locale switching is much more than an thing one does for demonstrations (“Look, here it is in English; press this button, ooh, Finnish!”). Not discounting that as a valid reason, mind you.

The only other realistic use-case I can imagine is when a user signs in : web page starts off in the company’s locale, user signs in, change user’s locale. Actually, scratch that, I can think of another use-case - a translator wanting to translate a web application into a different language, and dynamically switching locales.

(Sorry, I was think aloud).

Anyway, I’m not sure that it’s something that Vaadin should support directly out of the box : which Resource Bundle? How is it found? How is it associated? What about multiple resource bundles, hierarchical searching. That kind of thing is precisely the kind of thing that varies from project to project depending on complexity : one solution will never work for all.

Having said that, we are considering doing something like this to ease translation of our app to different locales. I’ll try and outline what our approach is going to be - we haven’t tried it yet, but I can’t see a problem with it; maybe someone else will point out a problem, and maybe the idea will help you.

We are going to store the resource bundle key in the DebugId of the component when we set the (translated) caption on the component. When we want to change the locale, we’ll search the component tree from application, looking at the debug ids - if they are present, we’ll switch the caption. No doubt one would need to do slightly different things for different components - (date picker, for example, you’d need to change the formatter as well as the caption. Table Headers will be interesting, and you’ll also have to cope with labels on tabs), but the principle would hold true.

As an added bonus, a significant proportion of your visible component will have a meaningful DebugId, which will help with automated testing tools!

I attach some some pseudo code to this message - let me reiterate, we’ve not tried this yet - I am thinking aloud in code. Also, our find-the-literal-from-the-resource-bundle needs to be more sophisticated - none-the-less, I’d imagine something like this.

Hope this helps someone - thinking it through helped me!

Cheers,

Charles [code]

// Creating the initial component
TextField projectName = new TextField();
setInitialCaption(projectName, "projectName", theCurrentLocale);

[...]

// This application listener doesn't exist. Easy enough to add.
application.addLocaleChangeListener(new LocaleChangeListener(){
  public void onLocaleChange(LocaleChangeEvent event){
    localizeComponents(event.getApplication());
   }
});
[...]

/* Set the initial caption on a component. */
public void setInitialCaption(Component component, String resourceBundleKey, Locale locale) {
component.setDebugId(resourceBundleKey);
populateCaption(component, locale);
}

/* The actual lets-set-the-caption on the component method */
public void populateCaption(Component component, Locale locale) {
ResourceBundle rb = ResourceBundle.getBundle(“a-resource-bundle”, locale);
String key = component.getDebugId();
if (key != null) { // May need a smarter check, if debug id is set on a non-captioned component : maybe is key non-null and start with a given prefix?
String caption = rb.getString(key);
component.setCaption(caption == null?key : caption);
}
}

/** A Component walker for an application **/
private void localizeComponents(Application application) {
Collection windows = getWindows();
for (Window window : windows) {
localizeComponent(window, application.getLocale());
}
}

public void localizeComponent(Window window, Locale locale) {
populateCaption(window, locale);
/* TODO : Grey Area here - a window is a component container, and contains a component container (content);
beed to be careful not to process the tree twice. This is probably slightly wrong. */
localizeComponent((ComponentContainer) window, locale);

Set<Window> childWindows = window.getChildWindows();
for (Window childWindow : childWindows) {
  localizeComponent(childWindow, locale);
}

}

public void localizeComponent(ComponentContainer componentContainer, Locale locale) {
// Update the caption on the container itself
populateCaption(componentContainer, locale);

// Update all the contained components
Iterator childIterator = componentContainer.getComponentIterator();
while(childIterator.hasNext()){
  Component component = (Component) childIterator.next();
  localizeComponent(component, locale);
}

}

public void localizeComponent(DateField component, Locale locale) {
/* THis override method might be unecessary for DateField if
DateField#getLocale ultimately goes to Application#getLocale */
populateCaption(component, locale);
component.setLocale(locale);
}

/* This is the basic update-the-caption-on-a-component */
public void localizeComponent(Component component, Locale locale) {
populateCaption(component, locale);
}

[/code]

When I think in portlet internationalization, I’m thinking in Liferay and it’s language portlet. Have you tried it? Go to www.liferay.com and switch to any language. Every Liferay bundled portlet is internationalized. Even, user content can be internationalized easily. Liferay has included internationalization support from the very beginning. I’m not writing about demos but real world applications.

By the way, I’ve read Vaadin MailPortlet will replace Liferay’s. I’d like to know how it will handle internationalization. I hope it be easy and simple. I suggest to whoever is internationalizing that portlet to write a blog entry or a wiki page with recommendations.

Regards
Aniceto

I agree that it is nice to maintain full state, but it probably has no added benefit for the end-user. Typically, change language at login is what users vote for in the end (even I have implemented a huge amount of code to allow dynamic language change :wink: ).

I think the question for me here is that (usability-wise) if you change the language from A to B because you did not understand language A, it makes no sense to maintain UI state (where you have navigated possibly without understanding where to go) for that user after the language change to B. You might as well start over.

The case is a little different for web pages as users may end up there from external sources. If this is the case in Vaadin-based portlet the state is equally easy to maintain - just navigate back to URL to restore the state.

The solution proposed above is interesting. I have seen different kinds of methods to do this, but using debug id it was actually quite clever.

I’m not aware how they have planned to do i18n of Mail Portlet, but I guess that full UI reinitialize is the way to go.

Hi

I have finally developed a simple way to internationalize applications and portlets. This sample includes only support for Buttons and Labels but other components can be easily added.

The way I have chosen for not to recreate Label and Button classes, and because AbstractComponent is an interface, is to extend them. I’ve see that only a few getters must be overriden, only those called by the repaint() method.

The way to deal with internationalization is to set values with a certain structure and delimiters. If the overriden method detects such structure, it returns the value from a bundle.

The class I18nHelper has static methods to detect/expand internationalized texts and methods to format bundleName/key into an internationalized delimited structure. It has also a method to escape text case it may contain internationalization delimiters.

This class implements internationalization of strings, dates (the internationalized string is the date format), and has a very simple support for integers, floating point and currency numbers. Some additional work is required in the case of numbers as the NumberFormat class does not support an string format as SimpleDateFormat class has. At least this is a first step.

The portlet needs some additional work as there should be a listener to detect Liferay language changes and set the Application object locale to that value, then call requestRepaintAll(). Only that. The Vaadin Application object gets current locale at application load time. If the portal changes the locale, that change is not propagated to Application. I have not enough Liferay knowledge to do that, to know which is the service or the event to register. What I’m sure of is a Liferay hook is not the way because hooks are installed at boot time and portlets can be installed at run time.

I think internationalization should be included in the Vaadin components, with the ability to enable or disable it with parameters. The use of packaged alternate i18n components is also an alternate option because the developer can choose the internationalized components or not. In relation to the locale listener, in my opinion it should be included in the Application class and activated or not by a parameter. In this way the developer does not need to implement a listener.

Some of the participants in this thread think Vaadin should not take into account i18. Charles wrote about the debugId field. As you can see, every component may need one or more fields to internationalize. In this proposal the component model is not changed, only how to interpret the field content at rendering time. I think Vaadin toolkit should have full internationalization support. In fact, it has partial support for the DateField component.

There are two zip files attached with the sources. Both applications and packages are the same except for the web or portlet configuration. It’s extension is jar because zip is not an allowed extension. Sorry if the Vaadin development standards are not met in these eclipse projects.

I’d like to read your opinions.

Aniceto Pérez
11118.jar (13.7 KB)
11119.jar (14 KB)

Hi Antonio,

That looks pretty smart! I

I have just implemented my suggestion in our project, too - both approaches appear to me to be valid. However, I don’t believe either should be added to the core Vaadin project - because they both assume different rules, which are specific to our projects (Our project assumes that I can use the debugId to hold the resource bundle key, and I have extended rules about searching for messages based on key : your approach assumes that resource bundles will always be stored in property files, and your resource bundles have different search rules to mine - but by using a static helper class, there is no way for anyone to change the rules for retrieving the translatable strings)

I’m not associated with the core Vaadin developers at all, but I know that they work for IT Mill, a software consultancy that writes lots of projects for lots of different clients; I would guess that the i18n requirements of those projects (where i18n was required) were very different from project to project - I know that in my company, each of the projects I have worked on definitely had different requirements, none of which would have been solved by a catch-all solution.

I think, though, you have demonstrated how quick easy it is to extend Vaadin’s components to do whatever you want; I suggest that the effort you have spent doing this was not spent on the code, but on working out what to do. What would help others, I believe, is open discussions like these - and perhaps adding our solutions to the Vaadin contributions repository, where others can see the ideas in action, and use them as they see fit.

By the way, I particularly like your idea of overriding the AbstractComponent#getCaption() method, and I may take that approach myself - my main “disagreement” is that the assumption that code for looking up messages can be “dictated” by the framework.

All of the above, I offer as friendly discussion, not as argument-for-the-sake-of-it!

With Best Wishes,

Charles

Well, I am doing it w/o problems on Liferay. The reason why I found this thread is because I though: “OK, fine, my solutions works, but let’s go right way”. However, it rendered that there is no right way for portlets. :grin:

In portlets for Liferay, I do a bit different and simpler. Idea is to move captions out of the “init()” method and call it each time on page reload.

So In my case, each window subclasses some BaseWindow where I hide all this nasty add/remove window from parents (still wondering why these odds are not in main Window, but anyway) and each such Window has to implement an interface that has “localize()” method. Now, in your “localize” method you put all the captions from all the widgets of the current form/window by given locale (default is English) from the bundle and calling this method at the end of the constructor. It supposed to get a bundle for a locale and setup the captions on their places and then render the window/widgets/etc. Then application has also “localize()” method, where all “localize()” methods are called from all the windows/layouts at init() — yes, it works in Rube Goldberg machine fashion. :slight_smile: Now, when your Portlet is
writing ajax page
, you can pass a locale to the application from the RenderRequest that comes right from the Liferay and re-call “localize()” method that will simply refresh all the strings automatically, based on the locale.

So, reverted sequence, it works this way from the Request to the Response:

  1. Liferay calls portlet
  2. Portlet calls
    writeAjaxPage
    method
  3. There you get your locale from the RenderRequest and passing to your Application.
  4. Application calls main “localize()”
  5. Your main “localize()” will call in a chain all the rest “localize()” methods of entire app.

This works per each page reload just like expected, though app continues to keep its value.


Pros:
simple, straight-forward, always works (unless you forgot something to call) and does not breaks.

Cons:
ugly, manual and not automated.

Normally I want .setCaption(…) already deal with bundle and locales in its app core. E.g. I tell to app: use this Bundle and reload captions at each time page has been reloaded, based on Locale from the request.

Dunno, maybe Vaadin gurus could input here something better for us, struggling portlet programmers. :slight_smile:

Hope it helps.

Well, that’s a good, but partial solution to the problem.

I think a better way to handle the most part of it and without changing anything is to use AOP. If getters call are intercepted when rendering the page, text keys can be translated eassdily on the fly, includying captions, etc.

Regards

Here is proposal for a solution. What do you think?


http://dev.vaadin.com/ticket/6733

The trick is to translate the strings during component paint cycle. This way one can easily switch the language of the application at any time.

I should have answered some time ago. Sorry.

I’ve been working on addon i18n-aware with Pedro Rodriguez and it works fine. The trick is to averload component constructors and other methods so instead of passing a caption, we pass a caption key. There is also a I18nservice that registers all i18n components. So, when i18nservice is notified a change of locale, it notifies mainWindow and each component notifies it’s descendants that event. Every i18n component saves the caption-key, so it can retrieve the localized value from the bundle and set the caption to it.

This is a very quick explanation, because it also supports passing a caption-key and argument array to format messages. And it is applied to another fields like table-column captions. Recently I’ve added i18n support to the treetable component.

The advantage of this is that not a translation is required in every repaint cyle. Only when locale changes.

I’ve added also a maven plugin to i18n custom components created with the vaadin visual editor. And that saves a lot of work.

Pedro is currently working also in a AspectJ implementation. I like Spring a lot and I don’t create any application without it. And AspectJ for transactions is fantastic. But I don’t like to go further with AspectJ. IMO AspectJ is like back magic; I prefer to use it, but with measure.