1
votes

I want to implement an i18n-support for a wicket 7 application.

Requirements:

  • Translations must be easily editable by the admin-user
  • Translations must take place without redeployment

My actual apporach is to hold the translations inside a DB. All translations will be cached. If a translation is changed by a Frontend-task the cache and the db will be updated. So far so easy.

Actually I'm stuck in replacing the translations inside a page. A working solution would be loading every translation during implementation. These translations would be set inside many of wicket-elements. I don't like this approach, because it'll mess up the code (html + java) heavily.

I'll try to implement a replacement-mechanism in my actual approach. After the page is rendered, the mechanism is run through the whole page and is doing these jobs:

  1. search for all placeholders
  2. load the translation for the placeholder-keys(cache)
  3. replace the placeholders with the translations

This should work for body and header (site's title)

Here is an example of a wicket-template

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>${landingpage.site.title}</title>
</head>
<body>
    <header wicket:id="headerPanel">header</header>
    ${welcome.message}
    <footer wicket:id="footerPanel">footer</footer>
</body>
</html>

In this case ${landingpage.site.title} and ${welcome.message} should be recognized and replaced. As you can see it is directly definied inside the template, not in the java-code. And this is what I want to achieve.

I hope I made the requirements clear enough. If not, don't mind to comment. I'll update the question to make it more clear.

My approach is to implement a BasePage (extends Page) and overwrite the onAfterRender-Method

@Override
protected void onAfterRender() {
    super.onAfterRender();
    Response originalResponse = RequestCycle.get().getResponse();
    String updatedResponse = replaceWithTranslations(originalResponse);
    originalResponse.reset();
    originalResponse.write(updatedResponse);
}

The method replaceWithTranslations is not yet implemented and returns a simple String actually. This method should convert the outputstream of the originalRepsonse to a String, searches for placeholders and replace them with the values of the db.

This approach seems to have 2 difficulties:

  • I'm not getting the response as String
  • I'm getting a WicketRuntimeException (Page.checkRendering in Page.java:666)

Any advice would be great!

3
Why don't you use Wicket's i18n approach? With <wicket:message> etc? (See: ci.apache.org/projects/wicket/guide/7.x/guide/i18n.html) - Rob Audenaerde
First reading this article made me feel mad. All templates messed up with <wicket:message>. And only the usage of localized property-files are described. On other web-site I read: wicket is not able to handle other inputs. By now I know this information is rubbish. Thanks for your adivce! Can you have a look at the solution below, if this is the way do to? - Mike

3 Answers

1
votes

OK, the problems seems to be a very simple one.

The trick or the luck is, here we have a BufferedWebResponse. A simple cast will do the trick:

@Override
protected void onAfterRender() {
    super.onAfterRender();
    BufferedWebResponse originalResponse = (BufferedWebResponse) RequestCycle.get().getResponse();
    String translatedResponse = replaceWithTranslations(originalResponse);
    originalResponse.reset();
    originalResponse.write(translatedResponse);
}

private String replaceWithTranslations(BufferedWebResponse originalResponse) {
    String untranslatedText = originalResponse.getText().toString();
    String translatedText = doTheTranslation(untranslatedText);
    return translatedText;
}
1
votes

Inspired by @RobAu I gave the wicket's approach of i18n a chance. Here is with what I came up with:

The template:

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title><wicket:message key="landingpage.site.title">Site-Title</wicket:message></title>
  </head>
  <body>
    <header wicket:id="headerPanel">header</header>
    <wicket:message key="welcome.message">Welcome</wicket:message>
    <footer wicket:id="footerPanel">footer</footer>
  </body>
</html>

wicket:message for attributes:

<input type="text" placeholder="username" wicket:message="placeholder:login.username"/>

The IStringResourceLoader:

@org.springframework.stereotype.Component
public class I18NResourceLoader implements IStringResourceLoader {

  @Autowired
  private I18NCache i18nCache;

  @Override
  public String loadStringResource(final Class<?> clazz, final String key, final Locale locale, final String style, final String variation) {
    return loadTranslation(key, locale);
  }

  @Override
  public String loadStringResource(final Component component, final String key, final Locale locale, final String style, final String variation) {
    return loadTranslation(key, locale);
  }

  private String loadTranslation(final String key, final Locale locale) {
    final Optional<Translation> optional = i18nCache.get(key, locale);
    if (!optional.isPresent()) {
        return key;
    }
    return optional.get().getText();
  }
}

Translation and I18NCache are self-implemented classes.

And finally the registration:

public abstract class BasePage extends WebPage {

  @SpringBean
  private I18NResourceLoader i18NResourceLoader;

  public BasePage(){
    addI18NResourceLoader();

      ...

  }

  private void addI18NResourceLoader() {
    final List<IStringResourceLoader> resourceLoaders = Application.get().getResourceSettings().getStringResourceLoaders();
    final boolean existsResourceLoader = resourceLoaders.stream()
            .filter(p -> p instanceof I18NResourceLoader)
            .collect(Collectors.counting()) > 0L;
    if (!existsResourceLoader) {
        resourceLoaders.add(i18NResourceLoader);
    }
  }

  ...

}

Pro's:

  • Wicket's approach
  • No mess with RegEx-Replacement-Handling
  • SPR

Con's

  • Template feels a little more messy

Actually, I have no informations about the performance of this approach.

I decided to keep the logic of adding the ResourceLoader in BasePage by 2 reasons.

  1. BasePage is responsible for everything concering the page-representation (weak reason :-) )
  2. I'm using DI. If I would add the logic in the WebApplication, I would have to manually inject the I18NResourceLoader or its dependencies.
0
votes

I think you can extend IComponentResolver to replace the placeholders like WicketMessageResolver.