Создание веб-приложения с несколькими языками с помощью Spring Boot

View more Tutorials:

1- Цель статьи

Иногда вам нужно создать многоязычный вебсайт, многоязычный вебсайт помогает вам иметь доступ к большему количеству пользователей. Многоязычный вебсайт так же называется  Internationalization (i18n) (Интернационализированный), напротив  Localization (L10n) (Локализированный).
Вебсайт, который вы сейчас смотрите ( o7planning.org) является интернационализированным вебсайтом. На данный момент вебсайт поддерживает 5 языков это Английский, Вьетнамский, Французский, Немецкий и Русский.
Примечание: Internationalization это слово с 18 символами, первый символ это i и последний символ это n, поэтому оно обычно пишется аббревиатурой  i18n.
Spring предоставляет поддержку расширения для интернационализации (Internationalization) (i18n) через использование  Spring Interceptor, Locale Resolvers и Resource Bundles для разных регионов.
В данной статье я покажу вам как создать простой многоязычный вебсайт, используя  Spring Boot.

Вы можете предварительно посмотреть пример ниже:

В данном примере, локальная информация ( Locale) находится в параметре  URL. Информация  Locale сохранится в  Cookie, и в следующих страницах пользователю не понадобится еще раз выбирать язык.
  • http://localhost:8080/SomeContextPath/login1?lang=vi
  • http://localhost:8080/SomeContextPath/login1?lang=fr
Другой пример с информацией  Locale на  URL:
  • http://localhost:8080/SomeContextPath/vi/login2
  • http://localhost:8080/SomeContextPath/en/login2

2- Создать проект Spring Boot

3- Message Resources

Здесь я создал 3 файла  properties для английского, французского и вьетнамского языков. Эти файлы будут загружены (load), и управляемы с помощью  messageResource Bean.
i18n/messages_en.properties
#Generated by Eclipse Messages Editor (Eclipse Babel)

label.password = Password
label.submit   = Login
label.title    = Login Page
label.userName = User Name

 
i18n/messages_fr.properties
#Generated by Eclipse Messages Editor (Eclipse Babel)

label.password = Mot de passe
label.submit   = Connexion
label.title    = Connectez-vous page
label.userName = Nom d'utilisateur

 
i18n/messages_vi.properties
#Generated by Eclipse Messages Editor (Eclipse Babel)

label.password = M\u1EADt kh\u1EA9u
label.submit   = \u0110\u0103ng nh\u1EADp
label.title    = Trang \u0111\u0103ng nh\u1EADp
label.userName = T\u00EAn ng\u01B0\u1EDDi d\u00F9ng
 
Eclipse поддерживает вас в редактировании содержания этих файлов, используя  "Message Editor".

4- Interceptor & LocaleResolver

Вам нужно объявить 2 Spring BEAN это  localeResolver и messageResource.

localeResolver - Определяет способ получения информации Locale, которым будет пользователься пользователь. CookieLocaleResolver возьмет информацию Locale из Cookie, чтобы узнать на каком языке раньше пользователь смотрел вебсайт.

messageResource - Загружает содержание файлов  properties.
Перед тем как запрос обрабатывается Controller-ом, он должен пройти через Interceptors, здесь вам нужно зарегистрировать  LocaleChangeInterceptor, Interceptor обрабатывает изменения  Locale со стороны пользователя.
WebMvcConfig.java
package org.o7planning.sbi18n.config;

 
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.i18n.CookieLocaleResolver;
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;

@Configuration 
public class WebMvcConfig implements WebMvcConfigurer {

	 
	@Bean(name = "localeResolver")
	public LocaleResolver getLocaleResolver()  {
		CookieLocaleResolver resolver= new CookieLocaleResolver();
		resolver.setCookieDomain("myAppLocaleCookie");
		// 60 minutes 
		resolver.setCookieMaxAge(60*60); 
		return resolver;
	} 
	
	@Bean(name = "messageSource")
	public MessageSource getMessageResource()  {
		ReloadableResourceBundleMessageSource messageResource= new ReloadableResourceBundleMessageSource();
		
		// Read i18n/messages_xxx.properties file.
		// For example: i18n/messages_en.properties
		messageResource.setBasename("classpath:i18n/messages");
		messageResource.setDefaultEncoding("UTF-8");
		return messageResource;
	}
	@Override
	public void addInterceptors(InterceptorRegistry registry) {
		LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
		localeInterceptor.setParamName("lang");
		
		
		registry.addInterceptor(localeInterceptor).addPathPatterns("/*");
	}
	
}

5- Controller & Views

MainController.java (Locale on Parameter)
package org.o7planning.sbi18n.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController {

    @RequestMapping(value = { "/", "/login1" })
    public String staticResource(Model model) {
        return "login1";
    }

}
login1.html (Thymeleaf View)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title th:utext="#{label.title}"></title>
   </head>
   <body>
      <div style="text-align: right;padding:5px;margin:5px 0px;background:#ccc;">
         <a th:href="@{/login1?lang=en}">Login (English)</a>
         &nbsp;|&nbsp;
         <a th:href="@{/login1?lang=fr}">Login (French)</a>
         &nbsp;|&nbsp;
         <a th:href="@{/login1?lang=vi}">Login (Vietnamese)</a>
      </div>
      <form method="post" action="">
         <table>
            <tr>
               <td>
                  <strong th:utext="#{label.userName}"></strong>
               </td>
               <td><input name="userName" /></td>
            </tr>
            <tr>
               <td>
                  <strong  th:utext="#{label.password}"></strong>
               </td>
               <td><input name="password" /></td>
            </tr>
            <tr>
               <td colspan="2">
                  <input type="submit" th:value="#{label.submit}" />
               </td>
            </tr>
         </table>
      </form>
   </body>
</html>
В случае если вы используете технологию  JSP на уровне  View.
Смотрите так же:
src/main/webapp/WEB-INF/jsp/login1.jsp (JSP View)
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page session="false"%>

<!DOCTYPE html>

<html>
<head>

<meta charset="UTF-8">

<title><spring:message code="label.title" /></title>
</head>
<body>

    <div style="text-align: right;padding:5px;margin:5px 0px;background:#ccc;">
       <a href="${pageContext.request.contextPath}/login1?lang=en">Login (English)</a>
       &nbsp;|&nbsp;
       <a href="${pageContext.request.contextPath}/login1?lang=fr">Login (French)</a>
       &nbsp;|&nbsp;
       <a href="${pageContext.request.contextPath}/login1?lang=vi">Login (Vietnamese)</a>
    </div>

    <form method="post" action="">
        <table>
            <tr>
                <td>
                 <strong>
                <spring:message    code="label.userName" />
                </strong>
                </td>
                <td><input name="userName" /></td>
            </tr>
            <tr>
                <td>
                 <strong>
                <spring:message    code="label.password" />
                </strong>
                </td>
                <td><input name="password" /></td>
            </tr>
            <tr>
                <td colspan="2">
                <spring:message code="label.submit" var="labelSubmit"></spring:message>
                <input type="submit" value="${labelSubmit}" />
                </td>
            </tr>
        </table>
    </form>
</body>
</html>

6- Информация Locale на URL

В случае если вы хотите создать многоязычный вебсайт, у которого информация Locale находится на  URL. Вам нужно изменить некоторые конфигурации:
  • http://localhost:8080/SomeContextPath/vi/login2
  • http://localhost:8080/SomeContextPath/en/login2
Создать 2 класса  UrlLocaleInterceptor и UrlLocaleResolver.
UrlLocaleInterceptor.java
package org.o7planning.sbi18n.interceptor;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.support.RequestContextUtils;

public class UrlLocaleInterceptor extends HandlerInterceptorAdapter {

   @Override
   public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
           throws Exception {

       LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request);

       if (localeResolver == null) {
           throw new IllegalStateException("No LocaleResolver found: not in a DispatcherServlet request?");
       }

       // Get Locale from LocaleResolver
       Locale locale = localeResolver.resolveLocale(request);

       localeResolver.setLocale(request, response, locale);

       return true;
   }

}
UrlLocaleResolver.java
package org.o7planning.sbi18n.resolver;

import java.util.Locale;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.LocaleResolver;

public class UrlLocaleResolver implements LocaleResolver {

    private static final String URL_LOCALE_ATTRIBUTE_NAME = "URL_LOCALE_ATTRIBUTE_NAME";

    @Override
    public Locale resolveLocale(HttpServletRequest request) {
        // ==> /SomeContextPath/en/...
        // ==> /SomeContextPath/fr/...
        // ==> /SomeContextPath/WEB-INF/pages/...
        String uri = request.getRequestURI();

        System.out.println("URI=" + uri);

        String prefixEn = request.getServletContext().getContextPath() + "/en/";
        String prefixFr = request.getServletContext().getContextPath() + "/fr/";
        String prefixVi = request.getServletContext().getContextPath() + "/vi/";

        Locale locale = null;

        // English
        if (uri.startsWith(prefixEn)) {
            locale = Locale.ENGLISH;
        }
        // French
        else if (uri.startsWith(prefixFr)) {
            locale = Locale.FRANCE;
        }
        // Vietnamese
        else if (uri.startsWith(prefixVi)) {
            locale = new Locale("vi", "VN");
        }
        if (locale != null) {
            request.getSession().setAttribute(URL_LOCALE_ATTRIBUTE_NAME, locale);
        }
        if (locale == null) {
            locale = (Locale) request.getSession().getAttribute(URL_LOCALE_ATTRIBUTE_NAME);
            if (locale == null) {
                locale = Locale.ENGLISH;
            }
        }
        return locale;
    }

    @Override
    public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
        // Nothing
    }

}
Изменить кофигурацию  Interceptor в WebMvcConfig:
WebMvcConfig.java
package org.o7planning.sbi18n.config;

import org.o7planning.sbi18n.interceptor.UrlLocaleInterceptor;
import org.o7planning.sbi18n.resolver.UrlLocaleResolver;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

	@Bean(name = "messageSource")
	public MessageSource getMessageResource() {
		ReloadableResourceBundleMessageSource messageResource = new ReloadableResourceBundleMessageSource();

		// Read i18n/messages_xxx.properties file.
		// For example: i18n/messages_en.properties
		messageResource.setBasename("classpath:i18n/messages");
		messageResource.setDefaultEncoding("UTF-8");
		return messageResource;
	}

	// To solver URL like:
	// /SomeContextPath/en/login2
	// /SomeContextPath/vi/login2
	// /SomeContextPath/fr/login2
	@Bean(name = "localeResolver")
	public LocaleResolver getLocaleResolver() {
		LocaleResolver resolver = new UrlLocaleResolver();
		return resolver;
	}

	@Override
	public void addInterceptors(InterceptorRegistry registry) {

		UrlLocaleInterceptor localeInterceptor = new UrlLocaleInterceptor();

		registry.addInterceptor(localeInterceptor).addPathPatterns("/en/*", "/fr/*", "/vi/*");
	}

}
Controller:
MainController2.java (Locale on URL)
package org.o7planning.sbi18n.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class MainController2 {

    @RequestMapping(value = "/{locale:en|fr|vi}/login2")
    public String login2(Model model) {
        return "login2";
    }

}
index2.html (Thymeleaf View)
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <meta charset="UTF-8">
      <title th:utext="#{label.title}"></title>
   </head>
   <body>
      <div style="text-align: right;padding:5px;margin:5px 0px;background:#ccc;">
         <a th:href="@{/en/login2}">Login (English)</a>
         &nbsp;|&nbsp;
         <a th:href="@{/fr/login2}">Login (French)</a>
         &nbsp;|&nbsp;
         <a th:href="@{/vi/login2}">Login (Vietnamese)</a>
      </div>
      <form method="post" action="">
         <table>
            <tr>
               <td>
                  <strong th:utext="#{label.userName}"></strong>
               </td>
               <td><input name="userName" /></td>
            </tr>
            <tr>
               <td>
                  <strong  th:utext="#{label.password}"></strong>
               </td>
               <td><input name="password" /></td>
            </tr>
            <tr>
               <td colspan="2">
                  <input type="submit" th:value="#{label.submit}" />
               </td>
            </tr>
         </table>
      </form>
   </body>
</html>
login2.jsp (JSP View)
<%@taglib uri="http://www.springframework.org/tags" prefix="spring"%>
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page session="false"%>

<!DOCTYPE html>

<html>
<head>

<meta charset="UTF-8">

<title><spring:message code="label.title" /></title>
</head>
<body>

    <div style="text-align: right;padding:5px;margin:5px 0px;background:#ccc;">
       <a href="${pageContext.request.contextPath}/en/login2">Login (English)</a>
       &nbsp;|&nbsp;
       <a href="${pageContext.request.contextPath}/fr/login2">Login (French)</a>
       &nbsp;|&nbsp;
       <a href="${pageContext.request.contextPath}/vi/login2">Login (Vietnamese)</a>
    </div>
 
    <form method="post" action="">
        <table>
            <tr>
                <td>
                 <strong>
                <spring:message    code="label.userName" />
                </strong>
                </td>
                <td><input name="userName" /></td>
            </tr>
            <tr>
                <td>
                 <strong>
                <spring:message    code="label.password" />
                </strong>
                </td>
                <td><input name="password" /></td>
            </tr>
            <tr>
                <td colspan="2">
                <spring:message code="label.submit" var="labelSubmit"></spring:message>
                <input type="submit" value="${labelSubmit}" />
                </td>
            </tr>
        </table>
    </form>
</body>
</html>
Запуск приложения:

7- Многоязычный вебсайт с содержанием сохраненным в базе данных 

Возможно вы не довольны с примером про многоязычный вебсайт выше. Вы хотите иметь вебсайт новостей на разных языках, и содержание будет храниться в Базе данных. Вы можете использовать такое решение, как использование разных  Datasource. И каждый  datasource это база данных, хранящая содержание определенного языка.
Смотрите так же:
  • TODO

View more Tutorials: