DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Trending

  • Why Documentation Matters More Than You Think
  • Emerging Data Architectures: The Future of Data Management
  • Comparing SaaS vs. PaaS for Kafka and Flink Data Streaming
  • Mastering React App Configuration With Webpack

Resource Bundle Tricks and Best Practices

By 
Markus Eisele user avatar
Markus Eisele
·
Aug. 22, 12 · Tutorial
Likes (0)
Comment
Save
Tweet
Share
115.5K Views

Join the DZone community and get the full member experience.

Join For Free

Today is resource bundle day. This is the most well known mechanism for internationalization (i18n) in Java in general. Working with it should be a breeze. But there are many little questions that come up while getting your hands dirty with it. If you are feeling the same, this post is for you.

Basics

The java.util.ResourceBundle defines a standardized way for accessing translations in java. They contain locale-specific resources. Resource bundles belong to families whose members share a common base name, but whose names also have additional components that identify

 their locales. Each resource bundle in a family contains the same items, but the items have been translated for the locale represented by that resource bundle. Those are key/value pairs. The keys uniquely identify a locale-specific object in the bundle.

The most basic example uses the following familiy:

Messages.properties

Messages_de.properties

Messages_en.properties

If you need to query a bundle in your application you simple call the

ResourceBundle bundle = ResourceBundle.getBundle("Messages");

method and query the returned bundle:

bundle.getString("welcome.message");

If you are wondering which Locale is going to be used here, you are right. The String constructor implicitly uses Locale.getDefault() to resolve the language. That might not be what you want. So you should ResourceBundle bundle = 

ResourceBundle.getBundle("Messages", locale);

You cannot set the locale after you have retrieved the bundle. Every ResourceBundle has one defined locale.

Naming stuff
Some thoughts about naming. Name the bundle properties after their contents. You can go a more general way by simply naming them "Messages" and "Errors" etc. but it also is possible to have a bundle per subsystem or component. Whatever fit's your needs. Maintaining the contents isn't easy with lots of entries. So any kind of contextual split makes developers happy. The bundle properties files are equivalent to classes; Name them accordingly. And further on you should find a common system for naming your keys. Depending on the split you have chosen for the property files you might also introduce some kind of subsystem or component namespace with your keys. Page prefixes are also possible. Think about this wisely and play around with it. You are aiming to have least possible dublicates in your keys.

Encapsulating
As you have seen, you use the string representation of the bundles a lot. The fact that those are actually file-names (or better class-names) you would be better of with a simple enum which encapsulates everything for you:

public enum ResourceBundles {
    MESSAGES("Messages"),
    ERRORS("Errors");
    private String bundleName; 

    ResourceBundles(String bundleName) {
        this.bundleName = bundleName;
    }

    public String getBundleName() {
        return bundleName;
    }

    @Override
    public String toString() {
        return bundleName;
    }
}

Having this you simply can write

ResourceBundle bundle = ResourceBundle.getBundle(MESSAGES.getBundleName());

Java Server Faces and ResourceBundles

 public void countryLocaleCodeChanged(ValueChangeEvent e) {
        String newLocaleValue = e.getNewValue().toString();
        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : countries.entrySet()) {
            if (entry.getValue().toString().equals(newLocaleValue)) {
                FacesContext.getCurrentInstance()
                        .getViewRoot().setLocale((Locale) entry.getValue());
            }
        }
    }

To use resource bundles in your jsf based application you simple have to define them in your faces-config.xml and use the shortcuts in your xhtml files.

<resource-bundle>
<base-name>Messages</base-name>
<var>msgs</var>
 <h:outputLabel value="#{msgs['welcome.general']}" />

JSF takes care of the rest. What about parameter substitution? Think about a key-value pair like the following:

welcome.name=Hi {0}! How are you?

You can pass the parameter via the f:param tag:

<h:outputFormat value="#{msgs['welcome.name']}">
         <f:param value="Markus" />
 </h:outputFormat>

To change the language you have to set a specific locale for your current FacesContext instance. It's best to do this via a value change listener:

 

public void countryLocaleCodeChanged(ValueChangeEvent e) {
        String newLocaleValue = e.getNewValue().toString();
        //loop country map to compare the locale code
        for (Map.Entry<String, Object> entry : countries.entrySet()) {
            if (entry.getValue().toString().equals(newLocaleValue)) {
                FacesContext.getCurrentInstance()
                        .getViewRoot().setLocale((Locale) entry.getValue());
            }
        }
    }

Resource Bundles in EJBs
JSF obviously is very easily integrated. What about using those bundles in EJBs? It is basically the same. You have the same mechanisms in place to get hand on the bundle and use it. There is one thing that you should keep in mind. You probably don't want to always use the default locale. So you have to find a way to pass the locale down from the UI. If you are thinking about @Injecting the MessageBundle via a @Produces annotation you have to think more than one time. Especially if you are working with @Stateless EJBs. Those instances get pooled and you have to pass the Locale to any business method that needs to know about the current Locale. You typically would do this with a parameter object or some kind of user session profile. Don't add the Locale as method signature all over.

Resource Bundles from the DB
In most of the cases I see you need to pull the keys from a DB. Given the inner workings of the ResourceBundle (one "class" per locale) you end up having to implement the logic in your own ResourceBundle implementation. Most of the examples you find on the web do this by overriding the handleGetObject(String key) method. I don't like this approach, especially since we have a far better way using the ResourceBundle.Control mechanism. Now you can override the newBundle() method and return your own ResourceBundle implementation. All you have to do is to set your own Control as a parent with your DatabaseResourceBundle:

public DatabaseResourceBundle() {
        setParent(ResourceBundle.getBundle(BUNDLE_NAME,
        FacesContext.getCurrentInstance().getViewRoot().getLocale(), new DBControl()));
    }

The DBControl returns MyResourceBundle which is a ListResourceBundle:

protected class DBControl extends Control {

        @Override
        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload)
                throws IllegalAccessException, InstantiationException, IOException {
            return new MyResources(locale);
        }

        /**
         * A simple ListResourceBundle
         */
        protected class MyResources extends ListResourceBundle {

            private Locale locale;

            /**
             * ResourceBundle constructor with locale
             *
             * @param locale
             */
            public MyResources(Locale locale) {
                this.locale = locale;
            }

            @Override
            protected Object[][] getContents() {
                TypedQuery<ResourceEntity> query = _entityManager.createNamedQuery("ResourceEntity.findForLocale", ResourceEntity.class);
                query.setParameter("locale", locale);

                List<ResourceEntity> resources = query.getResultList();
                Object[][] all = new Object[resources.size()][2];
                int i = 0;
  for (Iterator<ResourceEntity> it = resources.iterator(); it.hasNext();) {
  ResourceEntity resource = it.next();
  all[i] = new Object[]{resource.getKey(), resource.getValue()};
  values.put(resource.getKey(), resource.getValue());
   i++;
  }
                return all;
            }
        }
    }

As you can see, this is backed by an entitymanager and a simple ResourceEntity which has all the fields and NamedQueries necessary for building up the different bundles.

 @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @Column(name = "i18n_key")
    private String key;
    @Column(name = "i18n_value")
    private String value;
    @Column(name = "i18n_locale")
    private Locale locale;

By putting the bundles in a private Map<String, String> values = new HashMap<String, String>(); you also have a good way of caching the results after the bundles have been build up for the first time.
This still isn't the best solution as ResourceBundles have a way of caching. But I might dig into this in more detail later. Until now, this bundle is cached forever (or at least until the next redeployment).

Rewrite as Language Switch
On last thing to mention is that you also could have some fancy add-ons here. If you already have the JSF language switch magic in place it is simple to add ocpsoft's rewrite to your application. This is a simple way to encode the language in the URLs like this http://yourhost.com/Bundle-Provider-Tricks/en/index.html
All you have to do is to add rewrite to the game by adding two simple dependencies: 

<dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-servlet</artifactId>
            <version>1.1.0.Final</version>
        </dependency>
        <dependency>
            <groupId>org.ocpsoft.rewrite</groupId>
            <artifactId>rewrite-integration-faces</artifactId>
            <version>1.1.0.Final</version>
        </dependency>

Rewrite needs you to add your own ConfigurationProvider which is the central place to hold your rewriting rules. Implement the following: 

public class BundleTricksProvider extends HttpConfigurationProvider {

    @Override
    public Configuration getConfiguration(ServletContext context) {
        return ConfigurationBuilder.begin()
                // Locale Switch
                .addRule(Join.path("/{locale}/{page}.html").to("/{page}.xhtml")
                .where("page").matches(".*")
                .where("locale").bindsTo(PhaseBinding.to(El.property("#{languageSwitch.localeCode}")).after(PhaseId.RESTORE_VIEW)));
    }

    @Override
    public int priority() {
        return 10;
    }
}

Next is to add a file named "org.ocpsoft.rewrite.config.ConfigurationProvider" to your META-INF/services folder and put the fully qualified name of your ConfigurationProvider implementation there. One last thing to tweak is the logic in the LanguageSwitch bean. Rewrite isn't able to trigger a ValueChangeEvent (as far as I know :)) so you have to add some magic to change the Locale while the setter is called. That's it .. very easy!

This is all for today. Find the code on github.com. Happy to read about your thoughts. 

Locale (computer hardware)

Published at DZone with permission of Markus Eisele, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!