Abstract
Locale is a technology that is often overlooked in day-to-day development. Especially when you’re developing something that’s just for the domestic market and only in Chinese, locales can be ignored. And when the project proposes multi-language support, because does not have the good understanding, may bury a lot of holes for oneself.
What is the Locale
The Java Doc for java.util.Locale is pretty detailed, but I won’t go into that.
A Locale object represents a specific geographical, political, or cultural region. An operation that requires a Locale to perform its task is called locale-sensitive and uses the Locale to tailor information for the user.
The main thing to remember about Locale instances is that they contain the following information. In years of development experience, Script and Variant are basically not used. No more introductions.
- language
- script
- country (region)
- variant
lanugage
ISO 639 alpha-2 or alpha-3 language code
In practical use, we almost never touch a three-letter language.
country
ISO 3166 alpha-2 country code or UN M.49 numeric-3 area code.
Also in practice, the basic use of 2 – letter country
Scirpt and variant
The IANA Language Subtag Registry defines the complete list. A sense script is an alternate name for a region, and a variant is a dialect. It’s good to know.
In practice, the first thing most students might think about is writing the following Locale.
- zh_CN
- en_US
- ja_JP
If that’s all you’re thinking about, go to your browser -> Developer Tools -> Console and type the following JS
window.navigator.language
Copy the code
The browser is In Chinese and you are in China. The output is zh-cn and then if you reset your browser language, say to English. Once again, the output is en-cn What?? en-CN?? What the hell is this? So the first thing we know about Locale is that country depends on where you’re actually living. How do you deal with that? I’ll explain how to apply it later.
Application scenarios
A Locale is basically a way of displaying things differently, depending on the country and the language. Practical experience is mainly based on the following two points.
- Multilingual (more on that below)
- Amount displayed.
- Date format display.
Taking the amount display as an example, if you have no experience with this kind of development, you might think that there are different formats for the amount. It’s not always 1,000,000.00. You’d be wrong. Here are two examples.
- Japanese. Japanese money is not decimal point.
- French. In French, the thousands separator is a space and the decimal point is a comma. Like 1 000, 000,00
Understanding locales correctly, and using them correctly, can produce code that is both formal, concise, and of high quality. Rather than writing their own implementation for each language.
Creates the correct posture for Locale instances
It is important to use the correct pose for creation, which will be important later in the Spring application.
The following code is the most common creation I’ve seen.
Locale locale = new Locale("zh_CN");
Copy the code
Where zh_CN may be passed in directly from the front end as a Locale constructor argument for convenience. This is actually the wrong way to use it. Using this, the locale. language created is zh_cn.
Here are two correct postures. Then compare the results
- Locale API
// Use the Locale constructor
// If "zh_CN" is passed to the front end, it needs to be resolved and split by itself
Locale locale = new Locale("zh"."CN");
// Use Locale to preset constants. Check out the Locale source code for yourself.
Locale locale = Locale.SIMPLIFIED_CHINESE
Copy the code
- Commons-Lang
LocaleUtils.toLocale()
Locale locale = LocaleUtils.toLocale("zh_CN");
Copy the code
The preceding three creation methods can create the same Locale object. Commons Lang3 localeutils.tolocale ()
So let’s compare the difference between the wrong Locale and the correct Locale.
public class LocaleShowCase {
public static void main(String[] args) {
logLocale(new Locale("zh_CN"));
logLocale(Locale.SIMPLIFIED_CHINESE);
}
private static void logLocale(Locale locale) {
System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =");
System.out.println(String.format("Locale.toString: %s", locale.toString()));
System.out.println(String.format("Language: %s", locale.getLanguage()));
System.out.println(String.format("Country: %s", locale.getCountry()));
System.out.println(String.format("LanguageTag: %s", locale.toLanguageTag()));
System.out.println("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = ="); }}Copy the code
The output
=================================
Locale.toString: zh_cn
Language: zh_cn
Country:
LanguageTag: und
=================================
=================================
Locale.toString: zh_CN
Language: zh
Country: CN
LanguageTag: zh-CN
=================================
Copy the code
Let’s analyze the results
- Let’s start with the data. Incorrect creation of zh_CN as language. Country and LanguageTag are empty
- Language output is all lowercase
- Country is output in uppercase
- LanugageTag connects Language and Country with “-“
- Locale.toString connects Language and Country with “_”
zh_CN vs zh-CN
When to use “_” and when to use “-“, it’s really tricky. For example, when parsing Locale in Request.getLocale (), you can handle both formats. The Commons Lang3 localeutils.tolocale () input argument only supports the underscore format.
However, we can define a specification that uses only the “_” format in the back-end service and only the “-” format in the front-end.
The front end
There are too many front-end frameworks, just to mention umi+ Dva + React.
UMI
The Locale processing
Umi-plugin-react /locale is used in umi development projects to handle locale.
import { setLocale, getLocale } from 'umi-plugin-react/locale';
setLocale(language, true);
getLocale();
Copy the code
multilingual
Resource files are named using the “-” format.
.
|-- en-US
| |-- common.ts
| `-- form.ts
|-- ja-JP
| |-- common.ts
| `-- form.ts
|-- zh-CN
| |-- common.ts
| `-- form.ts
|-- en-US.ts
|-- ja-JP.ts
`-- zh-CN.ts
Copy the code
Display multilanguage
import { formatMessage } from 'umi-plugin-react/locale';
formatMessage({id: 'xxx'})
Copy the code
The date shown
import { formatDate } from 'umi-plugin-react/locale';
formatDate(new Date());Copy the code
Digital display
formatNumber(10000000.00);
Copy the code
The back-end service
This section describes only the Stateless Rest API developed based on Spring Boot. SpringMVC is out of date, so I won’t cover it.
The Locale processing
How do I determine the Locale used for the current API call?
Spring Boot uses LocaleResolver to determine which Locale is used for the current API call. After the LocaleResolver gets the Locale, it stores the Locale into LocaleContextHolder.
Spring Boot provides several standard implementations. The main difference is that it provides a method for obtaining locales based on where they are stored
- AcceptHeaderLocaleResovler from the Request Header Accept – Language
- CookieLocaleResolver Obtained from a Cookie
- FixedLocaleResolver FixedLocale, using only the system-configured Locale
- SessionLocaleResolver Obtained from the Session
While Spring already provides multiple implementations of fetching LocaleResolver, there are more complex scenarios in specific business scenarios. For example, it needs to be set based on the language of the current login user. This is when we need to implement our own set of LocaleResovler.
multilingual
Resource file
In Spring, we can add properties files to do multilingual support.
.
|-- java
....
`-- resources
`-- i18n
|-- messages.properties
|-- messages_ja.properties
|-- messages_ja_JP.properties
|-- messages_xx.properties
|-- messages_zh.properties
|-- messages_zh_CN.properties
|-- another.properties
|-- another_zh_CN.properties
|-- another_zh_TW.properties
|-- another_en.properties
`-- another_ja.properties
Copy the code
You can see that in the example
- There are two sets of resource files. One group is message and the other is anthor. Modularized management can be done in this way in a project.
- Each set of resource files can have its own list of supported locales
- Each file defines the translation for the locale
- No locale resource file is the default language. Such as messages. The properties and another. The properties. The default resource file content is used when there is no locale match.
- messages_xx.properties? It’s legal. But the proposal uses, also won’t run into basically. Just to clarify, the framework is supported. Use new Locale(“xx”) to create locales whose language is XX.
Locale Matches the priority
language+country+variant > language+country > lanaguage
Take message*.properties as an example:
- zh_CN -> messages_zh_CN.properties
- En or zh_JP -> messages_zh.properties
- en_US -> messages.properties.
configurationMessageSource
@Configuration
public class MessageConfiguration {
@Bean
public MessageSource messageSource(a) {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setDefaultEncoding("UTF-8");
messageSource.setBasenames("classpath:i18n/messages"."classpath:i18n/another");
returnmessageSource; }}Copy the code
Note here messageSource. SetBasenames (” classpath: i18n/messages “, “the classpath: i18n/another”) the BaseName is the group name resource file.
use
@Service
public class XXXService {
private final MessageSource messageSource;
public XXXService(MessageSource messageSource) {
this.messageSource = messageSource;
}
public String getI18N(String key, Object[] params) {
return messageSource.getMessage(key, params, LocaleContextHolder.getLocale())
}
}
Copy the code
The date shown
DateFormat fullDF = DateFormat.getDateInstance(DateFormat.FULL, locale);
System.out.println(fullDF.format(new Date()));
Copy the code
Digital display
System.out.println(NumberFormat.getInstance(locale).format(10000000));
Copy the code
Application in actual scenarios
Most products require users to log in, as mentioned in LocaleResovler. We can use Locale based on the language of the current user. This makes it easier to control the Locale the service receives. And we can define the language supported by the system during development, such as support zh_CN, en_US, ja_JP. This way, API calls after the user logs in don’t have to worry about receiving unsupported locales. Since we need to use the user Settings language, we need to implement a LocaleResovler ourselves.
@Data
public class Principal {
private String username;
privateString language; . }public class CustomLocaleResolver implements LocaleResolver {
private Locale defaultLocale;
public CustomLocaleResolver(Locale defaultLocale) {
this.defaultLocale = defaultLocale;
}
public Locale resolveLocale(HttpServletRequest request) {
Principal principal = (Principal) SecurityContextHolder.getContext().getAuthentication();
if(principal ! =null && !StringUtils.isEmpty(principal.getLanguage())) {
return LocaleUtils.toLocale(principal.getLanguage());
} else {
return request.getHeader("Accept-Language") != null ? request.getLocale() : this.defaultLocale; }}public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
throw new UnsupportedOperationException("Cannot change Principal data - use a different locale resolution strategy"); }}@Configuration
public class LocaleConfiguration {
@Bean
public CustomLocaleResolver localeResolver(@Value("${default-language:zh_CN}") String defaultLanguage) {
return newCustomLocaleResolver(LocaleUtils.toLocale(defaultLanguage)); }}Copy the code
Before the user logs in and the front end does nothing, the back end receives a Locale like en_CN and fails to match the resource file. If you follow the resource file design below and set a default translation for each language, you can resolve the problem of receiving irregular locales.
.
|-- java
....
`-- resources
`-- i18n
|-- messages.properties
|-- messages_ja.properties
|-- messages_ja_JP.properties
|-- messages_en.properties
|-- messages_en_US.properties
|-- messages_en_GB.properties
|-- messages_zh.properties
|-- messages_zh_CN.properties
`-- messages_zh_TW.properties
Copy the code
It doesn’t matter what country you’re in. There is a default match for Chinese, English, and Japanese.
Cheers~