Skip to content
CMO & CTO
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

  • Digital Experience
    • Experience Strategy
    • Experience-Driven Commerce
    • Multi-Channel Experience
    • Personalization & Targeting
    • SEO & Performance
    • User Journey & Behavior
  • Marketing Technologies
    • Analytics & Measurement
    • Content Management Systems
    • Customer Data Platforms
    • Digital Asset Management
    • Marketing Automation
    • MarTech Stack & Strategy
    • Technology Buying & ROI
  • Software Engineering
    • Software Engineering
    • Software Architecture
    • General Software
    • Development Practices
    • Productivity & Workflow
    • Code
    • Engineering Management
    • Business of Software
    • Code
    • Digital Transformation
    • Systems Thinking
    • Technical Implementation
  • About
CMO & CTO

Closing the Bridge Between Marketing and Technology, By Luis Fernandez

Internationalization in Java: Resource Bundles without Pain

Posted on November 4, 2007 By Luis Fernandez
\n\n\n\n\n\n

Shipping a Java app to more than one country should not feel like pulling teeth. The pieces are there. We just need to wire them in a way that does not punish the team.

\n\n\n\n

Here is a straight path to ResourceBundle that avoids the usual traps and keeps your strings sane, your dates right, and your users happy.

\n\n\n\n

Why care before you ship?

\n\n\n\n

Every string baked into code will come back to haunt you. Internationalization is cheaper now than after QA finds English in places it should not be.

\n\n\n\n

Plan keys, files, and formats from day one. Your future self will thank you when a new market shows up out of nowhere.

\n\n\n\n

Where does it hurt in Java today?

\n\n\n\n

Plain .properties files are read as ISO 8859 1. Anything outside that set must use native2ascii escapes. That breaks eyeballs and slows translators.

\n\n\n\n
# ISO 8859 1 with escapes\ngreeting=\\u3053\\u3093\\u306B\\u3061\\u306F\nbutton.save=Save\nerror.user=Usuario no v\\u00E1lido
\n\n\n\n

You can keep running native2ascii in Ant or Maven, or skip the pain with a tiny Java 6 feature.

\n\n\n\n

Can we keep properties in UTF 8 and move on?

\n\n\n\n

Yes. Java 6 added ResourceBundle.Control. With one class we can read bundles as UTF 8, no escapes, human friendly for translators.

\n\n\n\n
import java.io.*;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.util.*;\n\npublic class UTF8Control extends ResourceBundle.Control {\n  @Override\n  public ResourceBundle newBundle(String baseName, Locale locale, String format,\n                                  ClassLoader loader, boolean reload)\n      throws IllegalAccessException, InstantiationException, IOException {\n\n    String bundleName = toBundleName(baseName, locale);\n    String resourceName = toResourceName(bundleName, "properties");\n    InputStream stream = null;\n\n    if (reload) {\n      URL url = loader.getResource(resourceName);\n      if (url != null) {\n        URLConnection connection = url.openConnection();\n        connection.setUseCaches(false);\n        stream = connection.getInputStream();\n      }\n    } else {\n      stream = loader.getResourceAsStream(resourceName);\n    }\n\n    if (stream == null) return null;\n    try (InputStreamReader reader = new InputStreamReader(stream, "UTF-8")) {\n      return new PropertyResourceBundle(reader);\n    }\n  }\n}
\n\n\n\n
// Usage\nLocale es = new Locale("es", "AR");\nResourceBundle msg = ResourceBundle.getBundle("i18n.messages", es, new UTF8Control());\nSystem.out.println(msg.getString("greeting"));
\n\n\n\n

Now i18n/messages_es_AR.properties can live as real UTF 8 text. No more escapes. Less build glue.

\n\n\n\n

How should we name bundles and keys?

\n\n\n\n

Match bundle files with code packages. Keep keys stable and boring. Avoid full sentences in keys. The value holds the sentence.

\n\n\n\n
    \n
  • i18n.messages.properties default for en
  • \n
  • i18n.messages_es.properties Spanish generic
  • \n
  • i18n.messages_es_AR.properties Spanish Argentina
  • \n
\n\n\n\n
# Good keys\nnav.home=Home\nuser.login.title=Sign in\nuser.login.error=Wrong user or password\n\n# Avoid keys like:\n# SIGN_IN_PAGE_TITLE
\n\n\n\n

How do we format messages with data?

\n\n\n\n

Use MessageFormat. It respects the Locale and gives translators a stable order of parts.

\n\n\n\n
// messages.properties\nwelcome=Hello {0}, you have {1,number} new messages\n\n// Java\nResourceBundle rb = ResourceBundle.getBundle("i18n.messages", locale, new UTF8Control());\nString text = java.text.MessageFormat.format(rb.getString("welcome"), userName, 3);\nSystem.out.println(text);
\n\n\n\n

Remember that single quotes are special in MessageFormat. Escape them by doubling: It”s.

\n\n\n\n

What about plurals without ifs everywhere?

\n\n\n\n

Use ChoiceFormat inside MessageFormat. It keeps logic inside the text where translators can adjust it.

\n\n\n\n
# messages.properties\ninbox={0,choice,0#No new mail|1#One new mail|1<{0,number} new mails}\n\n// Java\nString s = MessageFormat.format(rb.getString("inbox"), count);
\n\n\n\n

For languages with tricky plurals, split into separate keys per form. Keep it clear for translators.

\n\n\n\n

How do we plug this into web views?

\n\n\n\n

With JSP and JSTL fmt, load the bundle and print keys. The tag lib respects the request locale.

\n\n\n\n
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>\n<fmt:setBundle basename="i18n.messages" />\n<fmt:message key="welcome">\n  <fmt:param value="${user.name}" />\n  <fmt:param value="${inboxCount}" />\n</fmt:message>
\n\n\n\n

With JSF, use f:loadBundle. With Spring MVC, wire a ResourceBundleMessageSource.

\n\n\n\n
<f:loadBundle basename="i18n.messages" var="msg"/>\n#{msg['user.login.title']}
\n\n\n\n

What is the fallback story?

\n\n\n\n

Given base name and locale, Java looks in this order: language plus country, then language, then default. So messages_es_AR then messages_es then messages.

\n\n\n\n

Keep base files complete. Region files should be small and only override what truly changes.

\n\n\n\n

How do we format dates, numbers and money?

\n\n\n\n

Never hardcode patterns unless you must. Use DateFormat and NumberFormat with the user locale. Currency picks symbols and separators for you.

\n\n\n\n
Locale userLocale = locale;\nDate now = new Date();\n\nDateFormat df = DateFormat.getDateInstance(DateFormat.MEDIUM, userLocale);\nNumberFormat nf = NumberFormat.getNumberInstance(userLocale);\nNumberFormat cf = NumberFormat.getCurrencyInstance(userLocale);\n\nSystem.out.println(df.format(now));\nSystem.out.println(nf.format(12345.678));\nSystem.out.println(cf.format(49.9));
\n\n\n\n

Any tips for right to left languages?

\n\n\n\n

For Swing, set ComponentOrientation from the locale. In HTML, use dir=”rtl” for layout and keep strings translated in the proper order.

\n\n\n\n
component.applyComponentOrientation(\n  ComponentOrientation.getOrientation(new Locale("ar"))\n);
\n\n\n\n

How do we test this without breaking the dev box?

\n\n\n\n

Write a small helper to set the Locale for a test run. Drive screens and logs in that locale and scan for English leaks.

\n\n\n\n
@Test\npublic void es_ar_smoke() {\n  Locale.setDefault(new Locale("es", "AR"));\n  ResourceBundle rb = ResourceBundle.getBundle("i18n.messages", Locale.getDefault(), new UTF8Control());\n  assertEquals("Guardar", rb.getString("button.save"));\n}
\n\n\n\n

What about the build?

\n\n\n\n

If you use the UTF 8 control, you can skip native2ascii. Keep your editor and repo set to UTF 8 for properties. Do not filter these files in the build.

\n\n\n\n
<resources>\n  <resource>\n    <directory>src/main/resources</directory>\n    <filtering>false</filtering>\n    <includes>\n      <include>i18n/</include>\n    </includes>\n  </resource>\n</resources>
\n\n\n\n

If you must stick to ISO 8859 1, add an Ant native2ascii step. It works but adds moving parts.

\n\n\n\n
<target name="i18n-escape">\n  <native2ascii encoding="UTF-8" src="src/main/resources/i18n"\n                dest="target/classes/i18n"/>\n</target>
\n\n\n\n

What mistakes should we avoid?

\n\n\n\n
    \n
  • Concatenating pieces like “Hello ” plus name plus “!”. Use MessageFormat instead.
  • \n
  • Reusing a key for two different sentences. One key per meaning.
  • \n
  • Putting HTML in bundles for desktop apps or Swing mnemonics in web text. Keep contexts clean.
  • \n
  • Hardcoding units and time zones. Pass data already in the right unit and zone.
  • \n
  • Letting translators edit keys. Give them only values and notes.
  • \n
\n\n\n\n

How do we brief translators without confusion?

\n\n\n\n

Add comments next to tricky keys. Describe placeholders. Mention brand names that never change.

\n\n\n\n
# Shows on the first run wizard. {0} is product name. Do not translate {0}.\nsetup.welcome=Welcome to {0}! Let us set things up.
\n\n\n\n

A short glossary for the project helps a lot. Keep it in the repo next to the bundles.

\n\n\n\n

What is a simple rollout plan?

\n\n\n\n

Pick two target locales. Externalize all strings. Add the UTF 8 control. Wire JSTL or your view tech. Run UI smoke tests for both locales.

\n\n\n\n

Then hand off the base file to translators. Pull back their UTF 8 files, commit, and ship a build to QA with locale toggles visible.

\n\n\n\n

Quick win: Add a hidden query param like ?lang=es_AR in dev to flip the locale per request. It speeds up testing and demos.

\n\n\n\n

Eclipse Europa and NetBeans 6 both handle UTF 8 properties well if the file encoding is set. Check your editor settings before you blame Java.

\n\n\n\n

Wrap up

\n\n\n\n

Keep bundles in UTF 8, load them with a tiny ResourceBundle.Control, format text with MessageFormat, and let the Locale do the heavy lifting for dates and numbers.

\n\n\n\n

Do this and internationalization in Java** stops being a headache. Fewer build hacks, clearer text for translators, and smoother releases for you.

\n
Software Architecture Software Engineering

Post navigation

Previous post
Next post
  • Digital Experience (94)
    • Experience Strategy (19)
    • Experience-Driven Commerce (5)
    • Multi-Channel Experience (9)
    • Personalization & Targeting (21)
    • SEO & Performance (10)
  • Marketing Technologies (92)
    • Analytics & Measurement (14)
    • Content Management Systems (45)
    • Customer Data Platforms (4)
    • Digital Asset Management (8)
    • Marketing Automation (6)
    • MarTech Stack & Strategy (10)
    • Technology Buying & ROI (3)
  • Software Engineering (310)
    • Business of Software (20)
    • Code (30)
    • Development Practices (52)
    • Digital Transformation (21)
    • Engineering Management (25)
    • General Software (82)
    • Productivity & Workflow (30)
    • Software Architecture (85)
    • Technical Implementation (23)
  • 2025 (12)
  • 2024 (8)
  • 2023 (18)
  • 2022 (13)
  • 2021 (3)
  • 2020 (8)
  • 2019 (8)
  • 2018 (23)
  • 2017 (17)
  • 2016 (40)
  • 2015 (37)
  • 2014 (25)
  • 2013 (28)
  • 2012 (24)
  • 2011 (30)
  • 2010 (42)
  • 2009 (25)
  • 2008 (13)
  • 2007 (33)
  • 2006 (26)

Ab Testing Adobe Adobe Analytics Adobe Target AEM agile-methodologies Analytics architecture-patterns CDP CMS coding-practices content-marketing Content Supply Chain Conversion Optimization Core Web Vitals customer-education Customer Data Platform Customer Experience Customer Journey DAM Data Layer Data Unification documentation DXP Individualization java Martech metrics mobile-development Mobile First Multichannel Omnichannel Personalization product-strategy project-management Responsive Design Search Engine Optimization Segmentation seo spring Targeting Tracking user-experience User Journey web-development

©2025 CMO & CTO | WordPress Theme by SuperbThemes