11

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?)
assylias
  • 321,522
  • 82
  • 660
  • 783
yankee
  • 38,872
  • 15
  • 103
  • 162
  • 1
    Why do you use a Set for `localizedStrings` and not a Map? – Jimmy T. May 30 '13 at 14:54
  • Have you considered using Spring [LocaleContextHolder](http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/context/i18n/LocaleContextHolder.html). Its springs own way of storing the current locale (stored by the DispatcherServlet) for a thread. – sbk May 30 '13 at 14:57
  • If the scope of your localization variable is a single thread then this seems like a smart (and simple) solution. If you're venturing into multiple threads then, obviously, this requires something more. If your API has localization information in each request then it is an entirely valid solution. KISS, that's the best argument you can make. – Daniel B. Chapman May 30 '13 at 15:04
  • @JimmyT.: Because of reasons that have to do with hibernate mappings. Another interesting problem, but not part of the question here. – yankee May 30 '13 at 15:36
  • But hibernate supports Maps very well. – Jimmy T. May 30 '13 at 15:57

4 Answers4

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.

Guillaume Darmont
  • 5,002
  • 1
  • 23
  • 35
  • 1
    Be aware that this solution requires you to make sure you copy your thread locals any time you switch to another thread. Something as simple as myCompletableFuture.thenRunAsync(...) will make you loose your thread locals. Even worse, you might switch thread locals with some other request that also does theRunAsync or uses java.nio.* or basically any asynchronous code. – Mikael Vandmo Nov 23 '18 at 09:18
2

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.

cruftex
  • 5,545
  • 2
  • 20
  • 36
1

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().

Keith
  • 4,144
  • 1
  • 19
  • 14
1

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.

inor
  • 2,781
  • 2
  • 32
  • 42