Question

I am working on internationalizing user entered data in a rather large Client/Server (HTTP (Hessian) is used for communication) application which is stored in a database. Users can choose the language they want to see and there is a default language which is used when a translation in the requested language is not present.

Currently a data class may look like this:

class MyDataClass {
  private Long id;
  private String someText;
  /* getters and setters */
}

After internationalization it could look like this:

class MyDataClass {
  private Long id;
  private Set<LocalizedStrings> localizedStrings;
  /* getters and setters */
}
class LocalizedStrings {
  private Locale locale;
  private String someText;
  /* getters and setters */
}

Of course it may be interesting to create a delegate getter in MyDataClass which takes care of getting the text in the correct locale:

public String getSomeText(Locale locale) {
  for(LocalizedString localized : localizedStrings) {
    if (localized.getLocale().equals(locale)) {
      return localized.getSomeText();
    }
  }
}

In my team there were some concerns though about the need to pass the locale around all the time until they reach the data class. Since all this stuff happens on the server and every request to the server is handled in a dedicated Thread, some people suggested to store the requested locale in a ThreadLocal object and create a backward compatible no-argument getter:

public String getSomeText() {
  return getSomeText(myThreadLocalLocale.get());
}

The ThreadLocal then needs to be a global variable (static somewhere) or it needs to be injected into MyDataClass on every single instance creation (we are using spring, so we could inject it if we make our data classes spring managed (which feels wrong to me)).

Using a ThreadLocal for the locale somehow feels wrong to me. I can vaguely argue that I don't like the invisible magic in the getter and the dependency on a global variable (in a data class!). However, having a "bad feeling" about this is not really a good way to argue with my colleagues about it. To help I need an answer with one of the following:

  1. Tell me that my feeling sucks and the solution is great for reasons X,Y and Z.
  2. Give me some good quotable arguments I can use to argue with my colleagues and tell me how to do it better (just always pass locale around or any other idea?)
Was it helpful?

Solution 2

This approach is perfectly valid. For example, Spring makes Locale available using ThreadLocal through RequestContextListener and LocaleContextHolder.

If you create a custom implementation, make sure you handle your ThreadLocal (set/remove) properly.

OTHER TIPS

Although, common practise I don't like to do localizing "deep" within the application.

Intead of this:

public String getSomeText() {
  return getSomeText(myThreadLocalLocale.get());
}

We do this:

public LocalizableText getSomeText() {
  return new LocalizableText(resourceBundle, "someText");
}

And then do, e.g. in a JSP or output layer:

<%= localizable.getString(locale) %>

The logic itself is language agnostic. We have cases where, after some processing, the application sends out the result by mail, logs it and presents it to the web user in all different languages. So processing together with result generation and then localization must be separate.

Using a thread local like you describe is a very common pattern in web applications. See this class in the Spring API as an example:

org.springframework.web.context.request.RequestContextHolder

Use a servlet filter (or similar) to both set the locale in a thread local, and then CLEAR the locale value after the server finished each request. Instead of injecting it in each place it is used, use a static factory/accessor method similar to RequestContextHolder: RequestContextHolder.getRequestAttributes().

ThreadLocal is bad practice. It's global variables and there are plenty of articles about how bad that is, in any language. The fact that Spring uses it does not justify using it. I like the solution cruftex has given. Avoid passing data via global variables.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top