Spring Boot two lines of code to easily achieve internationalization

Posted by milind24 on Thu, 03 Mar 2022 22:11:38 +0100

i18n internationalization

In development, Internationalization, also known as localization, means that a website (or application) can support multiple different languages, that is, different words can be displayed according to the language type and country / region of the user. It can make it easy for users in different countries and languages to use and improve the user experience.

To realize internationalization, a relatively simple implementation scheme is to develop different programs according to different countries and languages and display them in corresponding languages, such as Oracle English official website address: https://www.oracle.com/index.html , Chinese official website address: https://www.oracle.com/cn/index.html .

Generally, large companies will use this form of developing different programs according to different countries and languages to realize nationalization. Firstly, the company has resources to invest in development, and secondly, it can develop according to the user habits of different countries and languages, which is more in line with the layout style and interaction of local people.

There is another nationalization implementation scheme, that is to develop a set of programs that can display different languages according to the user's region, but the layout style of the website / application will not change greatly. This scheme is also the realization of i18n Internationalization. i18n is actually the abbreviation of English word Internationalization. i and n represent the first and last letters of the word, and 18 represents the middle 18 letters.

i18n implementation

In Java, through Java util. The Locale class represents a localized object. It determines the creation of a localized object through elements such as language type and country / region. The Locale object represents the specific geography, time zone, language, politics, etc.

We can obtain the language, country and other information of the Local system through the following methods; And obtain the language, country information and Local object representing the specified region. Of course, you can also call {locale The getavailablelocales () method looks at all available Local objects.

package com.nobody;

import java.util.Locale;

/**
 * @Description
 * @Author Mr.nobody
 * @Date 2021/4/15
 * @Version 1.0
 */
public class LocalTest {
    public static void main(String[] args) {
        Locale defaultLocale = Locale.getDefault();
        Locale chinaLocale = Locale.CHINA;
        Locale usLocale = Locale.US;
        Locale usLocale1 = new Locale("en", "US");
        System.out.println(defaultLocale);
        System.out.println(defaultLocale.getLanguage());
        System.out.println(defaultLocale.getCountry());
        System.out.println(chinaLocale);
        System.out.println(usLocale);
        System.out.println(usLocale1);
    }
}

//Output results
zh_CN
zh
CN
zh_CN
en_US
en_US

We usually store the attribute values of different languages in different configuration files. The ResourceBundle class can find the corresponding configuration file according to the specified baseName and Local objects, so as to read the corresponding language text and build the ResourceBundle object. Then we can use ResourceBundle GetString (key) can obtain the language and text of the key in different regions.

Naming rules for Properties configuration files: baseName_local.properties

If baseName is i18n, the corresponding configuration file should be named as follows:

  • Chinese configuration file: i18n_zh_CN.properties

  • Configuration file in English: i18n_en_US.properties

Then, key value pairs are stored in the two configuration files, corresponding to different languages

# In i18n_zh_CN.properties file
userName=dried tangerine peel

# In i18n_en_US.properties file
userName=Peel

We can obtain the information in the corresponding language environment in the following ways:

Locale chinaLocale = Locale.CHINA;
ResourceBundle resourceBundle = ResourceBundle.getBundle("i18n", chinaLocale);
String userName = resourceBundle.getString("userName");
System.out.println(userName);

Locale usLocale = Locale.US;
resourceBundle = ResourceBundle.getBundle("i18n", usLocale);
userName = resourceBundle.getString("userName");
System.out.println(userName);

//Output results
dried tangerine peel
Peel

How do we deal with internationalization for users in different regional language environments? In fact, the principle is very simple. Suppose that the client sends a request to the server and sets a key value pair in the request header, "accept language": "zh CN". According to this information, a localized object Locale representing the region can be constructed. According to the baseName and Locale objects of the configuration file, you can know which properties of the configuration file to read, Format the text to be displayed and finally return it to the client for display.

Springboot integration i18n

In Springboot, we will use a MessageSource interface to access internationalization information. This interface defines several overloaded methods. code is the attribute name (key) of the internationalized resource; args is the runtime parameter value passed to the placeholder in the format string; local is the localization object; resolvable encapsulates the international resource attribute name, parameters, default information, etc.

  • String getMessage(String code, @Nullable Object[] args, @Nullable String defaultMessage, Locale locale)

  • String getMessage(String code, @Nullable Object[] args, Locale locale)

  • String getMessage(MessageSourceResolvable resolvable, Locale locale)

Springboot provides an international information automatic configuration class MessageSourceAutoConfiguration, which can generate the implementation class ResourceBundleMessageSource of the MessageSource interface and inject it into the Spring container. The MessageSource configuration takes effect by relying on the ResourceBundleCondition condition and reading Spring. Com from the environment variable messages. The value of basename (the default value is messages), which is the resource file name corresponding to MessageSource, and the resource file extension is properties, and then read the corresponding resource file from the classpath *: Directory through pathmatchingresolder. If the resource file can be read normally, load the configuration class. The source code is as follows:

package org.springframework.boot.autoconfigure.context;

@Configuration
@ConditionalOnMissingBean(value = MessageSource.class, search = SearchStrategy.CURRENT)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Conditional(ResourceBundleCondition.class)
@EnableConfigurationProperties
public class MessageSourceAutoConfiguration {

    private static final Resource[] NO_RESOURCES = {};

    //We can do it in application Modify spring.com in the properties file The default value of the messages prefix, such as modifying the value of basename
    @Bean
    @ConfigurationProperties(prefix = "spring.messages")
    public MessageSourceProperties messageSourceProperties() {
        return new MessageSourceProperties();
    }

    //Generate a ResourceBundleMessageSource instance and inject it into the container
    @Bean
    public MessageSource messageSource(MessageSourceProperties properties) {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
        if (StringUtils.hasText(properties.getBasename())) {
            messageSource.setBasenames(StringUtils
                                       .commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename())));
        }
        if (properties.getEncoding() != null) {
            messageSource.setDefaultEncoding(properties.getEncoding().name());
        }
        messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale());
        Duration cacheDuration = properties.getCacheDuration();
        if (cacheDuration != null) {
            messageSource.setCacheMillis(cacheDuration.toMillis());
        }
        messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat());
        messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage());
        return messageSource;
    }

    protected static class ResourceBundleCondition extends SpringBootCondition {

        private static ConcurrentReferenceHashMap<String, ConditionOutcome> cache = new ConcurrentReferenceHashMap<>();

        @Override
        public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
            String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
            ConditionOutcome outcome = cache.get(basename);
            if (outcome == null) {
                outcome = getMatchOutcomeForBasename(context, basename);
                cache.put(basename, outcome);
            }
            return outcome;
        }

        private ConditionOutcome getMatchOutcomeForBasename(ConditionContext context, String basename) {
            ConditionMessage.Builder message = ConditionMessage.forCondition("ResourceBundle");
            for (String name : StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(basename))) {
                for (Resource resource : getResources(context.getClassLoader(), name)) {
                    if (resource.exists()) {
                        return ConditionOutcome.match(message.found("bundle").items(resource));
                    }
                }
            }
            return ConditionOutcome.noMatch(message.didNotFind("bundle with basename " + basename).atAll());
        }

        //Read the configuration file under classpath *:
        private Resource[] getResources(ClassLoader classLoader, String name) {
            String target = name.replace('.', '/');
            try {
                return new PathMatchingResourcePatternResolver(classLoader)
                    .getResources("classpath*:" + target + ".properties");
            }
            catch (Exception ex) {
                return NO_RESOURCES;
            }
        }

    }

}

The following class is the property configuration class of Spring internationalization processing. We can use it in application Modify these default values in the properties file, for example: Spring messages. basename=i18n.

package org.springframework.boot.autoconfigure.context;

/**
 * Configuration properties for Message Source.
 *
 * @author Stephane Nicoll
 * @author Kedar Joshi
 * @since 2.0.0
 */
public class MessageSourceProperties {

    /**
  * Comma-separated list of basenames (essentially a fully-qualified classpath
  * location), each following the ResourceBundle convention with relaxed support for
  * slash based locations. If it doesn't contain a package qualifier (such as
  * "org.mypackage"), it will be resolved from the classpath root.
  */
    private String basename = "messages";

    /**
  * Message bundles encoding.
  */
    private Charset encoding = StandardCharsets.UTF_8;

    /**
  * Loaded resource bundle files cache duration. When not set, bundles are cached
  * forever. If a duration suffix is not specified, seconds will be used.
  */
    @DurationUnit(ChronoUnit.SECONDS)
    private Duration cacheDuration;

    /**
  * Whether to fall back to the system Locale if no files for a specific Locale have
  * been found. if this is turned off, the only fallback will be the default file (e.g.
  * "messages.properties" for basename "messages").
  */
    private boolean fallbackToSystemLocale = true;

    /**
  * Whether to always apply the MessageFormat rules, parsing even messages without
  * arguments.
  */
    private boolean alwaysUseMessageFormat = false;

    /**
  * Whether to use the message code as the default message instead of throwing a
  * "NoSuchMessageException". Recommended during development only.
  */
    private boolean useCodeAsDefaultMessage = false;

    //Omit get/set
}

After creating the internationalization configuration file in the classpath, we can inject the MessageSource instance for internationalization processing:

i18n. When the configuration file cannot be found, the default language is properties.

@Autowired
private MessageSource messageSource;

@GetMapping("test")
public GeneralResult<String> test() {
    //Obtain the Locale object of the client, that is, the value of the accept language key of the request header. We can also customize the request header key to obtain the language ID
    Locale locale = LocaleContextHolder.getLocale();
    String userName = messageSource.getMessage("userName", null, locale);
    System.out.println(userName);
    return GeneralResult.genSuccessResult(userName);
}

Above, we use Spirng's own LocaleContextHolder to obtain the local object Locale, which is the language value of the accept language key in the request header to judge the generation of the corresponding Locale object. We can also generate the Locale object according to other methods, such as the value of the custom key in the request header, and then through MessageSource GetMessage () method to realize the final nationalization.

 

Recommended reading

Why do Alibaba programmers grow so fast

This is the case when you enter a large factory. Operation guide for entering a large factory after working for 2 to 3 years

Ali architect [Bai Xi] takes you to uncover the actual combat and source code interpretation of the architecture project: microblog + B station architecture design, JUC core and Mybatis source code

After reading three things

If you think this article is very helpful to you, I'd like to invite you to help me with three small things:

Like, forward, and have your "praise and comments" is the driving force of my creation.

Pay attention to the official account of "Java didi" and share original knowledge without any time.

At the same time, we can look forward to the follow-up articles 🚀

 

 

63

Topics: Java Oracle Mybatis Spring Spring Boot