DarkDust
DarkDust

Reputation: 92414

Load localized resources

In a project I'm working on, I'd like to load different resources depending on the current locale. With strings, this is pretty easy: just create a resource bundle by adding some files, for example Localization.properties and Localization_de.properties and then load it via ResourceBundle.getBundle("domain.project.Localization").

But how do I load localized resources that are not simple strings?

Specifically, I'd like to load images and HTML files based on locale. It would suffice to get the correct URL to that resource. For example, I'd like to have a Welcome.html and Welcome_de.html file and load the later when the current locale is German.

Simply using getClass().getResource("Welcome.html") doesn't work, it's always returning exactly that file and doesn't do locale handling. I haven't found a convenient way to solve this yet, apart from manually messing with the filename based on the current locale.

One solution I came up with is to add a key to my Localization.properties file that contains the name of the correct localized HTML file. But this feels hacky and wrong.

Note that this is not an Android application, just a standard Java 8 desktop app.

Upvotes: 4

Views: 5072

Answers (5)

DarkDust
DarkDust

Reputation: 92414

It actually turned out to pretty easy to get the same behaviour as ResourceBundle.getBundle. I've looked at the code and found that the most important part for handling of the locales and filenames is ResourceBundle.Control.

So here's a simple method that returns the URL to localized resource (following the same filename scheme as resource bundles) without caching and support for just the current locale:

/** Load localized resource for current locale.
 * 
 * @param baseName Basename of the resource. May include a path.
 * @param suffix File extension of the resource.
 * @return URL for the localized resource or null if none was found.
 */
public static URL getLocalizedResource(String baseName, String suffix) {
    Locale locale = Locale.getDefault();
    ResourceBundle.Control control = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_DEFAULT);
    List<Locale> candidateLocales = control.getCandidateLocales(baseName, locale);

    for (Locale specificLocale : candidateLocales) {
        String bundleName = control.toBundleName(baseName, specificLocale);
        String resourceName = control.toResourceName(bundleName, suffix);

        // Replace "Utils" with the name of your class!
        URL url = Utils.class.getResource(resourceName);
        if (url != null) {
            return url;
        }
    }

    return null;
}

If someone wants to extend it to support generic locales as an argument: the important part in the ResourceBundle.getBundleImpl implementation is:

for (Locale targetLocale = locale;
     targetLocale != null;
     targetLocale = control.getFallbackLocale(baseName, targetLocale)) {
         List<Locale> candidateLocales = control.getCandidateLocales(baseName, targetLocale);
         ...
}

The last entry of getCandidateLocales is the empty locale (Locale.ROOT). It should be ignored when the locales since the default resource would then be found in the first for iteration, before the fallback locale is tested.

Upvotes: 5

Cyphrags
Cyphrags

Reputation: 528

(This is an addition to my comment to have a more detailed explanation)

You could put the ressources in different folders, based on their language.

File tree could look like this:

  • pathToRessources

    • DE

      • Images
        • Image1.png
        • Image2.png
      • Localization
        • MainPage // More for a website here....
        • Impressum
    • EN

      • ...

And using like a "file-localization-path-builder-class" (claiming best name ever)

public static Locale currentLocale = Locale.GERMAN;

public static String baseRessourcePath = "Path to your basic folder for ressources";

public static String getLocalizedPathToFile(String ressourcePath)
{
    return Paths.get(currentLocale.getLanguage(), ressourcePath).toString();
}

Wherever you need to load ressource just call your loader with

getLocalizedPath(PathToFile)

where PathToFile is like /Images/Image1.png or something else.

This should also be pretty easily be used for client applications.

Hope this helps :)

Upvotes: 1

Reza
Reza

Reputation: 1556

The way that you are doing it is kind of weird for me. I developed some international applications and never used this approach. The maintenance cost of your approach is so high. Imagine that you would like to change one part of welcome.html file you need to change all other welcome files which is a huge effort. the common solution for this problem in java is using JSTL fmt taglib with getting help of some property files containing key-value pair which the keys are fixed and the values change according the the locale that you are in. For example you in your Localization.properties file you have key-value(s) like:

page.welcome.welcome=welcome

and you have another file with name Localization_de.properties that has the same key with different value

page.welcome.welcome=willkommen

Now in your welcome.jsp you may use something like this <fmt:message key="page.welcome.welcome"/> in order to render the proper value in your page. For images you can get the same approach. just import the name of image into each property file as key and name it properly as their value and use the key in your jsp. For i18n use eclipse plugins(like Resource Bundel Editor) to make the life easier for you.

In other languages framework that I used like django I've seen the same approach as above.

Upvotes: 0

ToYonos
ToYonos

Reputation: 16833

What about ListResourceBundle ?

Define your bundles :

public class MyBundle_fr_FR extends ListResourceBundle
{
    public Object[][] getContents()
    {
        return new Object[][] 
        {
            { "welcome_file", getClass().getResource("Welcome_fr.html") },
            { "another_file", getClass().getResource("foo_fr.html") }
        };
    }
}

public class MyBundle_de_DE extends ListResourceBundle
{
    public Object[][] getContents()
    {
        return new Object[][] 
        {
            { "welcome_file", getClass().getResource("Welcome_de.html") },
            { "another_file", getClass().getResource("foo_de.html") }
        };
    }
}

And then use it like this :

ResourceBundle bundle = ResourceBundle.getBundle("MyBundle", Locale.GERMANY);
URL url = (URL) bundle.getObject("welcome_file");

Upvotes: 0

David Rabinowitz
David Rabinowitz

Reputation: 30448

Having the resource to hold the name of the localized file is indeed a valid solution, but as you have mentioned is cumbersome. If you need to display HTML page in your application, why not to have small web server embedded, and have it server JSP pages? This way, the entire HTML will remain the same, and the pages themselves will be localized.

Another option, is to use a local templating engine (such as FreeMarker) and generate HTMLs dynamically before serving them.

Upvotes: 0

Related Questions