Internationalization
To provide a better user experience, it is important to reach user with applicative messages that match his language.
Prerequisites
- Apache Maven
- SDK
- JDK 21
Locale information
Information about the user's language preference is intercepted by the SDK (from HTTP Accept-Language
header) as a locale code: the locale code is expected to be in the format [LANGUAGE]-[COUNTRY]
, en-US
or it-IT
simply by language
like en
or it
(although other combinations can be possible). The preferred locale code for the current user is picked as the first in the string. An example of locale code is: en-US,en;q=0.9,it;q=0.8
.
SDK will look for the language part only of the first locale string token, to know which language file to load, and for now, the country part will be discarded. The language token will be used to load a [LANGUAGE].json
file from the resources/lang
directory. In the example above the picked language is en
(that is stripped from en-US
), so the SDK will look for a file named en.json
.
Create a language file
A language file is a free-form JSON file.
// en.json
{
"auth": {
"unknownUser": "User ID is not known: {}",
}
}
// it.json
{
"auth": {
"unknownUser": "ID utente sconosciuto: {}"
}
}
The corresponding folder structure will be:
resources/
└── lang/
├── en.json
└── it.json
Each JSON value will be reachable by a path that is the concatenation of the JSON keys separated by a dot (.
). In the example above the path of the message ID utente sconosciuto: {}
is auth.unknownUser
.
Use a message translation
The user language preference comes to the mEx encapsulated into an ExecutionContext
object, that is the context of the currently executing FUN: since each FUN can come from different users each FUN can bring its own language preference. During ExecutionContext
creation the chosen language file is loaded so it's ready to be used.
Suppose to be in the presence of a variable context
of type ExecutionContext
; translation method is available by using:
String message = context.getLanguage().translate("auth.unknownUser");
If the user locale is it
this will be the output of the message:
System.out.println(message);
// ID utente sconosciuto: {}
If the JSON path requested does not exist, the SDK will return a warning message (e.g. "Translation for '[PATH]' not found"
) when a translation of any key is attempted.
String message = context.getLanguage().translate("a.b.c");
System.out.println(message);
// Translation for 'a.b.c' not found"
Message interpolation
The message can contain placeholders that will be replaced by the SDK with the values passed as arguments. The placeholders are represented by {}
and the values are passed as arguments to the translate
method.
String message = context.getLanguage().translate("auth.unknownUser", "John");
System.out.println(message);
// ID utente sconosciuto: John
A better approach
Using an enum
object is a better approach to store application messages, and it will be enough to implement LanguageMessageProvider
.
public enum ApplicationMessage implements LanguageMessageProvider {
UNKNOWN_USER("auth.unknownUser");
private final String path;
ApplicationMessage(String path) { this.path = path; }
@Override
public String getPath() {
return path;
}
}
and use it in this way:
String message = context.getLanguage().translate(ApplicationMessage.UNKNOWN_USER);
Language fallback
When a language extracted from locale code does not match any language file, the SDK will fallback to the default language file. The default language file is en.json
and it is always loaded from the directory resources/lang
.
If the fallback language file is not found, the SDK will return a warning message (e.g. "Translation file not found"
) when a translation of any key is attempted.
String message = context.getLanguage().translate("auth.unknownUser");
System.out.println(message);
// "Translation file not found"