Localizing an application for multiple languages

Applications often need to be localized in multiple natural languages. Textual elements of an application user interface such as banners, messages, and questions should use the native language of the user. The best practice in this case is to decouple the application core logic from the natural language used when interacting with the user. Customizing the application for additional natural languages should be as simple as defining and loading the new text translations. Moreover, applications typically allow the user to select a language at runtime. Therefore, loading multiple language translation resources at application startup should not be an issue.

In Logtalk and some Prolog systems, textual user interaction is preferable handled by a structured message printing mechanism. Logtalk complements message printing support with a question asking mechanism for a comprehensive solution to user interaction. In this blog post, we show how these mechanisms can be used for application localization.

As a simple example, assume that we are developing a game that prints a banner at startup that should be localized. The game core logic prints the banner using a banner message term and that is the extent of its responsibilities. The use of message terms allows us to abstract not only how and where a message is printed but also the actual text that will be used. But how do we specify the language for the text? The Logtalk structured message printing API predicates include a component argument that can be any bound term. This argument allows easy partition of messages for applications made of multiple components, helping avoiding conflicts and allowing all messages for a given component to be handled as a set. We can use this argument to parameterize the messages using the selected language. Assuming our game uses the compound term my_game(Language) as the component identifier, the core logic could print the banner using the following code:

:- object(my_game(_Language_)).

    % we use an object parameter to pass the country code
    % of the language to be used when printing messages

    :- public(banner/0).
    banner :-
        logtalk::print_message(comment, my_game(_Language_), banner).


:- end_object.

The logtalk::print_message/3 predicate takes as arguments the message kind, the component, and the message.

We now need to be able to define the different message translations. The translations should be independent, allowing any number of them to be loaded at startup without conflicts and new translations to be written without requiring changes to core logic or existing translations. Here we can use a category per translation. For example:

:- category(my_game_en_localization).

    :- multifile(logtalk::message_tokens//2).
    :- dynamic(logtalk::message_tokens//2).

    logtalk::message_tokens(banner, my_game(en)) -->
        ['Welcome to my great game!'-[], nl].

:- end_category.
:- category(my_game_pt_localization).

    :- multifile(logtalk::message_tokens//2).
    :- dynamic(logtalk::message_tokens//2).

    logtalk::message_tokens(banner, my_game(pt)) -->
        ['Bem vindo ao meu grande jogo!'-[], nl].

:- end_category.

The logtalk::message_tokens//2 multifile non-terminal translate message terms, banner in this case, into a list of tokens that define the actual message output.

At startup, the core logic can peek the language setting and call the banner/0 predicate as illustrated by the following queries:

| ?- my_game(de)::banner.
Willkommen Sie bei Mein tolles Spiel!

| ?- my_game(en)::banner.
Welcome to my great game!

| ?- my_game(fr)::banner.
Bienvenue sur mon grand jeu!

| ?- my_game(pt)::banner.
Bem vindo ao meu grande jogo!

We could of course use instead a banner(Language) message term. But assuming that all messages are localized, it is simpler and uniform to parameterize the component term itself. Not a command-line text game? No problem. We can always abstract user interaction using the same structured message printing and question asking mechanisms.


This post is based on the localizations example distributed with Logtalk.