I'm trying to internationalize an OSGi application using the "OSGi way" but I haven't had progress. By OSGi way I mean, using the capabilities the framework provides for it. I have internationalized Java applications before but I'd like to know how to do it as an OSGi application.
I have created this simple demo [GitHub repo] which aims to create a bundle that will log a message once it is activated and another message once it is deactivated.
Project structure:
src
|- org.example.i18n
|- SimpleLoggingComponent // where the actual strings are
|- SimpleLogService
|- SimpleLogServiceImpl
META-INF
|- MANIFEST.MF
OSGI-INF
|- org.example.i18n.SimpleLoggingComponent
|- org.example.i18n.SimpleLogServiceImpl
build.properties
SimpleLoggingComponent source
@Component
public class SimpleLoggingComponent {
private SimpleLogService simpleLogService;
@Reference
public void bindLogger(SimpleLogService logService) {
this.simpleLogService = logService;
}
public void unbindLogger(SimpleLogService logService) {
this.simpleLogService = null;
}
@Activate
public void activate() {
if (simpleLogService != null) {
simpleLogService.log("Yee ha, I'm logging!"); // <-- need this message internationalized
}
}
@Deactivate
public void deactivate() {
if (simpleLogService != null) {
simpleLogService.log("Done, I'm finishing logging!"); // <-- need this message internationalized
}
}
}
For now, the strings are fixed in the code and I'd like to be able to internationalize these. Let's say, have English and Spanish languages supported.
Later I'm planning to add support for more languages by means of Fragment Bundles, so the solution should be ready to be extensible by this means.
I have read all these but I haven't found anything consistent that helps me.
Also, neither the OSGi Alliance Tutorial Archive nor the OSGi enRoute contains anything about it.
Environment:
PS: I'm sure this is not a complicated task, it's just that I haven't found any useful (to me) documentation about it.
Bundle localization entries share a common base name. To find a potential localization entry, an underscore ('_' \u005F) is added plus a number of suffixes, separated by another underscore, and finally appended with the suffix .properties
. The suffixes are defined in java.util.Locale
. The order for the suffixes must be:
language
country
variant
For example, the following files provide manifest translations for English, Dutch (Belgium and the Netherlands) and Swedish.
OSGI-INF/l10n/bundle_en.properties
OSGI-INF/l10n/bundle_nl_BE.properties
OSGI-INF/l10n/bundle_nl_NL.properties
OSGI-INF/l10n/bundle_sv.properties
Localized values are stored in property resources within the bundle. The default base name of the bundle localization property files is OSGI-INF/l10n/bundle
. The Bundle-Localization manifest header can be used to override the default base name for the localization files. This location is relative to the root of the bundle and bundle fragments.
A localization entry contains key/value entries for localized information. All headers in a bundle's manifest can be localized. However, the Framework must always use the non-localized versions of headers that have Framework semantics.
A localization key can be specified as the value of a bundle's manifest header using the following syntax:
header-value ::= '%'text
text ::= < any value which is both a valid manifest headervalue
and a valid property key name >
For example, consider the following bundle manifest entries:
Bundle-Name: %acme bundle
Bundle-Vendor: %acme corporation
Bundle-Description: %acme description
Bundle-Activator: com.acme.bundle.Activator
Acme-Defined-Header: %acme special header
User-defined headers can also be localized. Spaces in the localization keys are explicitly allowed.
The previous example manifest entries could be localized by the following entries in the manifest localization entry OSGI-INF/l10n/bundle.properties
.
# bundle.properties
acme\ bundle=The ACME Bundle
acme\ corporation=The ACME Corporation
acme\ description=The ACME Bundle provides all of the ACME\ services
acme\ special\ header=user-defined Acme Data
1. First, let's create the bundle files, the ones which will contain the key/value pairs. In this case one for English Language (bundle.properties
) which will be the default one and one for Spanish Language (bundle_es.properties
)
...
OSGI-INF
|- l10n
|- bundle.properties
|- bunlde_es.properties
|- ...
... which will contain our previously hard-coded string values.
#bundle.properties
startMessage = Yeah ha, I'm logging!
endMessage = Done, I'm finishing logging!
#bundle_es.properties
startMessage = Si, Estoy registrando logs!
endMessage = Terminado, He concluido de registrar logs!
2. Now let's create an utility component which will help us in getting the values associated to each key according to the locale.
src
...
|- org.example.i18n.messages
|- MessageProvider
|- MessagesProviderImpl
...
There are two files: the interface and an actual implementation which is the one that contains the logic for getting the key/value pairs.
public interface MessageProvider {
String get(String key);
}
@Component
public class MessagesProviderImpl implements MessageProvider {
private BundleLocalization bundleLocalization;
private LocaleProvider localeProvider;
private ResourceBundle resourceBundle;
@Reference
public void bindBundleLocalization(BundleLocalization bundleLocalization) {
this.bundleLocalization = bundleLocalization;
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC)
public void bindLocaleProvider(LocaleProvider localeProvider) {
this.localeProvider = localeProvider;
setResourceBundle()
}
/*unbind methods omitted*/
@Activate
public void activate() {
setResourceBundle();
}
@Override
public String get(String key) {
return resourceBundle.getString(key);
}
private String getLocale() {
return localeProvider != null ? localeProvider.getLocale().toString() : Locale.getDefault().toString();
}
private void setResourceBundle() {
resourceBundle = bundleLocalization.getLocalization(FrameworkUtil.getBundle(getClass()), getLocale());
}
}
3. Use the MessageProvider
component in SimpleLoggingComponent
.
@Component
public class SimpleLoggingComponent {
/*previously code omitted for brevity*/
private MessageProvider messages;
@Reference
public void bindMessageProvider(MessageProvider messageProvider) {
messages = messageProvider;
}
/*unbind methods omitted*/
@Activate
public void activate() {
simpleLogService.log(messages.get("startMessage")); // <- use now the key: startMessage
}
@Deactivate
public void deactivate() {
simpleLogService.log(messages.get("endMessage")); // <- use now the key: endMessage
}
}
On the Arguments tab use the runtime parameter -nl
for this purpose, e.g. -nl en
org.eclipse.osgi.service.localization.BundleLocalization
org.eclipse.osgi.service.localization.LocaleProvider