Reputation: 14717
I am doing a Spring web application.
I need to support a few locales such as ko (Korean), ru (Russian), en (English), etc.
I am able to catch what locale is from a browser via ways such as RequestContextUtils.getLocale(request) or LocaleChangeInterceptor.
However, the browser's locale may not be what my web app supports. I have to resolve it to the closet or default locale.
Basically, I need to know how to get the resolved locale given the browser's locale AND a few locale values such as ko, ru, and en.
My understanding is that Spring has such locale resolution code because it is able to find right resource bundles given a browser's locale. I am hoping to reuse Spring's code for locale resolution, but not sure how to do it. Please note that this question has nothing to do with finding the brwoser's locale or displaying proper messages.
EDIT
Based on my tracing Spring's code, it appears that Spring depends on JDK to find the exact or closest locale. I just found out this and think this is what I am looking for:
Resource Bundle Lookup Order https://sites.google.com/site/openjdklocale/design-notes/resource-bundle-lookup-order
Please note that I don't need to find right resource bundle. I just need to get the locale the existing JDK code returns given a locale in question and a few known locales. So I am hoping to reuse existing JDK's lookup code. Any idea?
I am using JDK 7.
Thanks for any help and input!
Regards.
Upvotes: 2
Views: 4263
Reputation: 120
Complete Reuse
To reuse the JDK logic, you could create a property file within the classpath for each of the known locales (such as test_fr_CA.properties, test_fr.properties, test_en_US.properties, test_en.properties, test.properties). Don't forget the root locale (test.properties) if you want to be able to match to it. Then simply create a resource bundle for the locale in question and inspect it to see the actual locale used.
ResourceBundle rb = ResourceBundle.getBundle("test", Locale.FRENCH);
System.out.println("Locale used is:"+rb.getLocale().toString());
The files can be created dynamically and cleaned up after the test.
High Level Code Replication, Low Level Reuse
You could replicate the high level code in java.util.ResourceBundle.getBundleImpl(...). This is basically going through looking for a match (using your own matching logic like equal toString() representations) in a candidate list of locales reusing java.util.ResourceBundle.Control.getCandidateLocales(...) on the locale in question. If there is no match you get the next fallback locale for the locale in question by reusing java.util.ResourceBundle.Control.getFallbackLocale(...) For each fallback locale you try to match a locale in it's candidate list repeating the fallback in a loop until there are no fallback locales left. Note that the root locale will be the last candidate in each candidate list but it should be skipped unless you have exhausted all fallback locales.
This method does not require creating files. You use a non-existent baseName in the getCandidateLocales(...) and get FallbackLocale(...) calls and compare each candidate locale to your list of known locales looking for a match.
A simple example of this would be like the following:
ResourceBundle.Control rbControl = ResourceBundle.Control.getControl(ResourceBundle.Control.FORMAT_PROPERTIES);
Locale localeInQuestion = Locale.CHINA;
List<Locale> knownLocales = Arrays.asList(new Locale[] {Locale.CANADA_FRENCH, Locale.FRENCH, Locale.US, Locale.UK, Locale.ENGLISH, Locale.ROOT});
String nonExistentBaseName = "bogus";
Locale matchingLocale = null;
Boolean reachedRootLocaleMatch = false;
outerloop:
for (Locale targetLocale = localeInQuestion;
targetLocale != null;
targetLocale = rbControl.getFallbackLocale(nonExistentBaseName, targetLocale)) {
List<Locale> candidateLocales = rbControl.getCandidateLocales(nonExistentBaseName, targetLocale);
for (Iterator iterator = candidateLocales.iterator(); iterator.hasNext();) {
Locale currentCandidateLocale = (Locale) iterator.next();
if (knownLocales.contains(currentCandidateLocale)) {
if (currentCandidateLocale.equals(Locale.ROOT)) {
reachedRootLocaleMatch = true;
}
else {
matchingLocale = currentCandidateLocale;
break outerloop;
}
}
}
}
if (matchingLocale == null && reachedRootLocaleMatch) {
matchingLocale = Locale.ROOT;
}
if (matchingLocale != null) {
System.out.println("The matching locale is: "+matchingLocale.toString());
}
else {
System.out.println("There was no matching locale");
}
Upvotes: 1
Reputation: 18194
Have you checked the official documentation (chapter 17.8 Using locales)? You need to configure LocaleResolver
and possibly a LocaleChangeInterceptor
(or write your own).
Note, that resolving client's locale is different task from getting a correct resource bundle.
LocaleResolver
to get or set the current locale. There are several implementations for different strategies to LocaleResolver
:
FixedLocaleResolver
- will always resolve locale to predefined value (not capable of setting different locale)SessionLocaleResolver
- stores and resolves locale to value store on session under special keyAcceptHeaderLocaleResolver
- this is the resolver which actually tries to get locale from the browser (not capable of setting different locale)CookieLocaleResolver
- stores and resolves locale to value stored in a browser cookeLocaleResolver
is used to populate LocaleContextHolder
(btw. that is the class you should be getting locale from).
There is a second mechanism LocaleChangeInterceptor
, which is able to set locale via your selected LocaleResolver
based on user request parameter.
Now this infrastructure is unrelated to your resource bundles (messages.properties, messages_en.properties, ...) and the mechanism used to resolve your messages. The following examples will show why.
messages.properties
- with ru
messages (default messages)messages_ko.properties
- with ko
messagesSessionLocaleResolver
with default locale ru
LocaleChangeInterceptor
SCENARIO I - First requets:
DispatcherServlet
it queries LocaleResolver
to get locale for the requestru
(default)<spring:message>
tag...MessageSource
(ResourceBundleMessageSource
) with request locale (this is the one resolved by your resolver).messages_ru.properties
which does not exist, so it moves to more general file messages.properties
(which "by accident" contains your default language - ru
)SCENARIO II - User clicks link to change his language to ko
:
locale=ko
DispatcherServlet
resolves request locale to ru
(this is what your locale resolver returns)LocaleChangeInterceptor
handler interceptor.LocaleChangeInterceptor
detects locale
query parameter and calls setLocale
method on your LocaleResolver
, which leads to changing request locale and storing new locale on the session for future requests.<spring:message>
is calling MessageSource
with ko
locale.messages_ko.properties
and succeeds.SCENARIO III - User tries to change to invalid locale:
locale=en
.ko
from the session)en
(this will be stored on the session as well)<spring:message>
is calling MessageSource
with en
locale.messages_en.properties
which does not exist so it moves to a more general file messages.properties
and messages are translated to ru
, even thou the request locale is set to en
.Now the last example is probably what bothers you - there is no check whether the locale user selects is supported or not. If you don't want to allow user to switch to unsupported locale, then you need to either subclass some LocaleResolver
or write your own LocaleChangeInterceptor
.
Upvotes: 5