mscharhag, Programming and Stuff;

A blog about programming and software development topics, mostly focused on Java technologies including Java EE, Spring and Grails.

Friday, 4 October, 2013

Single quote escaping in Java resource bundles

In this post I will describe a small pitfall when using Java's ResourceBundle and MessageFormat classes, especially in combination with Spring's ResourceBundleMessageSource.

ResourceBundle is a widely used class for localizing resources like message strings while MessageFormat can be used to replace placeholders within string messages. The typical usage of ResourceBundle and MessageFormat look like this:

messages_en.properties:

myMessage=Hello {0}

Java code:

ResourceBundle bundle = ResourceBundle.getBundle("messages");
String pattern = bundle.getString("myMessage");         // Hello {0}
String message = MessageFormat.format(pattern, "John"); // Hello John

Code similar to this is used for example in the fmt JSP Tag library or in Spring's ResourceBundleMessageSource for retrieving localized messages.

Whenever you are using MessageFormat you should be aware that the single quote character (') fulfils a special purpose inside message patterns. The single quote is used to represent a section within the message pattern that will not be formatted. A single quote itself must be escaped by using two single quotes ('').

Let's look at some examples:

messages_en.properties:

test.message1=test {0} {1} {2}
test.message2=test {0} '{1}' {2}
test.message3=test {0} ''{1}'' {2}
test.message4=test {0} '''{1}''' {2}
test.message5=test {0} '{1} {2}
test.message6=test {0} ''{1} {2}

Java code:

for (int i = 1; i <= 6; i++) {
  String pattern = bundle.getString("test.message" + i);
  String message = MessageFormat.format(pattern, 'A', 'B', 'C');
  System.out.println(message);
}

Output:

test A B C
test A {1} C
test A 'B' C
test A '{1}' C
test A {1} {2}
test A 'B C 

As we can see placeholders between two simple single quotes are not replaced and all single quotes that are not escaped by another single quote don't show up in the output.

It is important that everyone working on localization is aware of this. Note that the usage of single quotes varies a lot between languages. You can write pages of German text that do not contain a sole single quote character. In contrast, French text is typically full of single quotes. So it is a good idea to double-check messages of single quote heavy languages like French for proper escaping.

Single quote escaping with Spring's ResourceBundleMessageSource

Single quote escaping can become a bit more tricky if you are building a Spring application that makes use of Spring's ResourceBundleMessageSource class. If you have properly set up a ResourceBundleMessageSource bean it can be used to retrieve string messages like this:

messages_en.properties:

myMessage=Hello {0}

Java code:

Object[] args = new Object[] { "John" };
Locale locale = new Locale("en");
String message = messageSource.getMessage("myMessage", args, locale); // Hello John

Internally ResourceBundleMessageSource uses Resourcebundle and MessageFormat for receiving string messages. However, ResourceBundleMessageSource does a small optimization by default. It will only parse a message through MessageFormat if one or more arguments have been passed in for the message. If no message arguments are passed to getMessage() the message text will be returned as-is. This means that the rules for escaping single quotes only need to be applied if the message takes any arguments.

Let's look at another example:

messages_en.properties:

test.message1=John's message
test.message2={0}'s message
test.message3=John's {0}

Java code:

private void printMessage(String code, Object... args) {
  Locale locale = new Locale("en");
  String message = messageSource.getMessage(code, args, locale);
  System.out.println(message);
}
printMessage("test.message1");
printMessage("test.message2", "John");
printMessage("test.message3", "message");

Output:

John's message
Johns message
Johns {0}

The first message does not take any arguments, so no MessageFormat is applied and the single quote does not need to be escaped. The second and third messages, however, are formatted by a MesssageFormat which processes the single quote characters. In these messages the single quotes should better be escaped with another single quote character otherwise they won't show up in the output.

From a developer point of view the rules for escaping single quotes might make sense. However, you will have a hard time explaining these rules to localizers who have only a very basic technical knowledge. It just won't work!

Luckily ResourceBundleMessageSource has a very useful flag called alwaysUseMessageFormat that can be enabled if all messages should be parsed by MessageFormat. If this flag is set to true (default value is false) all single quotes need to be escaped.

If you are working with multiple localizers you should enable this option to simplify the rule of escaping single quotes. Otherwise, you (or the localizer) will go down in localization tickets if you have to support French ;-)

Comments

  • John Z - Wednesday, 4 February, 2015

    Thanks!

  • Andrew Swan - Thursday, 23 April, 2015

    Thanks from Fusion!

  • Kari dev - Monday, 1 June, 2015

    Thanks

  • Kevin C - Monday, 16 May, 2016

    Thanks! Is any special handling needed for double quotes in the property file?

  • Behr - Thursday, 23 June, 2016

    Very nice! Thanks!

  • Clinton - Wednesday, 13 September, 2017

    Thanks! The tip about "alwaysUseMessageFormat" was extremely helpful!

  • JB - Wednesday, 31 January, 2018

    Thanks for this. Saved me some time.

  • Thyag - Thursday, 8 February, 2018

    Spring quote escaping information was very helpful. Thanks for saving lot of developer's time.

  • NJ - Tuesday, 28 January, 2020

    Thanks for the explanation !
    The french language leads to so many weird issues.

Leave a reply