Skip to content

Localization

jte has no built-in keywords for localization. Instead, it provides a flexible interface so that you can easily use the same localization mechanism you're used to in your project. This has several advantages:

  • No need to learn yet another way to localize things
  • No need to reverse engineer another opinionated localization implementation
  • Your users receive the exact localization through jte as they do from the rest of your application

Let's implement gg.jte.support.LocalizationSupport. There's only one method to implement:

public class JteLocalizer implements gg.jte.support.LocalizationSupport {

    private final OtherFrameworkLocalizer frameworkLocalizer;
    private final Locale locale;

    public JteLocalizer(OtherFrameworkLocalizer frameworkLocalizer, Locale locale) {
        this.frameworkLocalizer = frameworkLocalizer;
        this.locale = locale;
    }

    @Override
    public String lookup(String key) {
        // However this works in your localization framework
        return frameworkLocalizer.get(locale, key);
    }
}
1
2
3
4
5
6
class JteLocalizer(frameworkLocalizer: OtherFrameworkLocalizer, locale: Locale) : gg.jte.support.LocalizationSupport {
    override fun lookup(String key): String {
        // However this works in your localization framework
        return frameworkLocalizer.get(locale, key)
    }
}

Now, you can create a JteLocalizer whenever you render a page and pass it to the page template:

1
2
3
4
@param JteLocalizer localizer

<h1>${localizer.localize("my.title")}</h1>
<p>${localizer.localize("my.greetings", user.getName())}</p>
1
2
3
4
@param localizer: JteLocalizer

<h1>${localizer.localize("my.title")}</h1>
<p>${localizer.localize("my.greetings", user.name)}</p>

Note

Why is the gg.jte.support.LocalizationSupport interface even needed? It mainly helps with proper output escaping in HTML mode. Localized texts are considered safe and are not output escaped, but all user-provided parameters are! Here are some good examples in the form of unit tests.

This works fine, but passing a parameter to every template might feel repetitive. If it does, you could use a java.lang.ThreadLocal, filled with the required information before rendering a page and destroyed afterwards.

public class JteContext {
    private static final ThreadLocal<JteLocalizer> context = new ThreadLocal<>();

    public static Content localize(String key) {
        return context.get().localize(key);
    }

    public static Content localize(String key, Object... params) {
        return context.get().localize(key, params);
    }

    static void init(JteLocalizer localizer) {
        context.set(localizer);
    }

    static void dispose() {
        context.remove();
    }
}
1
2
3
4
5
6
7
8
9
object JteContext {
    private val context = ThreadLocal<JteLocalizer>()

    fun localize(key: String): Content = context.get().localize(key)
    fun localize(key: String, vararg params: Any): Content = context.get().localize(key, params)

    fun init(localizer: JteLocalizer) = context.set(localizer)
    fun dispose() = context.remove()
}

And the, when rendering the page:

1
2
3
4
5
6
7
8
public void renderPage(String template, Locale locale) {
    try {
        JteContext.init(new JteLocalizer(this.frameworkLocalizer, locale));
        templateEngine.render(template);
    } finally {
        JteContext.dispose();
    }
}
1
2
3
4
5
6
7
8
fun renderPage(template: String, locale: Locale) {
    try {
        JteContext.init(JteLocalizer(this.frameworkLocalizer, locale))
        templateEngine.render(template)
    } finally {
        JteContext.dispose()
    }
}

Localization in the template is now possible with a simple static method call:

1
2
3
4
@import static my.JteContext.*

<h1>${localize("my.title")}</h1>
<p>${localize("my.greetings", user.getName())}</p>

Whether you prefer a parameter or a static method call is a matter of taste. The nice thing about both ways is that everything is under your control, and if you want to know what happens under the hood, that is just a click away in your IDE.

Further reading: