建立一个多语言网站不仅有助于提高访问量,同时还能减少维护成本(相对于同时维护2
个不同语言的网站)。
多语言网站也称为国际化(i18n),是对应于本地化(i10n)的叫法。
说明:i18n 来自英文单词(
Internationalization
),因为这个单词太长,所以取其首尾字母,加上中间的18
个字母,组合成i18n
。类似的还有 k8s(kubernetes)等。
Spring Boot 默认就包含有多种语言的国际化工具,包括拦截器、语言处理器(Resolver) 和语言包。
本文详细说明如何使用 Spring Boot 创建一个多语言网站。
用户切换不同语言有2
种方式,通过 query 参数以及通过路径方式。
第一种情况:语言参数在 URL query 上
第二种情况:语言参数在 URL path 上
本文会讲解这两种方式的实现。
1 创建 Spring Boot 工程
打开 Idea 编辑器,选择菜单 File – New – Project…,创建一个 Spring Boot 项目,如下:
组件只选择 Spring Web 和 Thymeleaf 模板引擎。
再点击 Finish 完成创建。
2 创建语言包文件
这里我们创建 2 个语言包:英文语言包和中文语言包,它们将由 messageResource
Bean 加载和管理,更多语言包可自行增加。
需要注意的是,非英文语言包创建后不是 UTF-8 格式的,显示的时候会是乱码,所以需要将 .properties
文件默认的编码格式由 ISO-8859-1
改为 UTF-8
,方法如下图。
创建英文语言包文件 src/main/resources/i18n/message_en.properties
:
title = Internationalization test
username = Username
password = Password
submit = Submit
简体中文语言包文件 src/main/resources/i18n/message_zh_CN.properties
:
title = 国际化测试
username = 用户名
password = 密码
submit = 提交
3 语言信息在 URL 参数上的情况
3.1 运行效果
最终效果如下,通过链接切换语言,相当于修改了请求参数,语言切换后即使刷新页面也同样有效。
3.2 拦截器和 LocaleResolver
创建一个 WebMvcConfig.java
配置类,配置 localeResolver
和 messageResource
两个 bean,以及addInterceptors
语言拦截器。
localeResolver
– 指明如何获取用户所选择的语言信息,CookieLocaleResolver
会从本 Cookie 中读取用户之前使用的的语言。messageResource
– 配置语言包文件所在的位置。addInterceptors
– 在 Controller 执行之前,注册LocaleChangeInterceptor
拦截器。这个拦截器会在展示内容给用户之前提前处理语言内容。
站点配置文件 src/main/java/com/awaimai/i18demo/config/WebMvcConfig.java
:
package com.awaimai.i18demo.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");
resolver.setCookieMaxAge(60 * 60); // 60 minutes
return resolver;
}
@Bean(name = "messageSource")
public MessageSource getMessageResource() {
ReloadableResourceBundleMessageSource messageResource = new ReloadableResourceBundleMessageSource();
messageResource.setBasename("classpath:i18n/message");
messageResource.setDefaultEncoding("UTF-8");
return messageResource;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
LocaleChangeInterceptor localeInterceptor = new LocaleChangeInterceptor();
localeInterceptor.setParamName("lang");
registry.addInterceptor(localeInterceptor).addPathPatterns("/*");
}
}
3.3 控制器和视图
控制器文件 src/main/java/com/awaimai/i18demo/controller/MainController.java
:
package com.awaimai.i18demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController {
@RequestMapping(value = { "login1" })
public String login() {
return "login1";
}
}
Thymeleaf 视图文件 src/main/resources/templates/login1.html
:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:utext="#{title}"></title>
</head>
<body>
<div style="text-align: right;padding:5px;margin:5px 0px;background:#ccc;">
<a th:href="@{/login1?lang=en}">English</a>
|
<a th:href="@{/login1?lang=cn}">简体中文</a>
</div>
<form method="post" action="">
<table>
<tr>
<td>
<strong th:utext="#{username}"></strong>
</td>
<td><input name="userName"/></td>
</tr>
<tr>
<td>
<strong th:utext="#{password}"></strong>
</td>
<td><input name="password"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" th:value="#{submit}"/>
</td>
</tr>
</table>
</form>
</body>
</html>
全部保存后运行 Spring Boot 应用,访问链接: http://localhost:8080/login1?lang=en ,即可看如开始的效果。
4 语言信息在 URL 路径上的情况
这种情况主要在需要做 SEO 的网站,语言选项在 URL 路径上,如下:
这种情况我们需要多一些配置,包括 UrlLocaleInterceptor
URL 拦截器和UrlLocaleResolver
语言处理器。
URL 拦截器文件 src/main/java/com/awaimai/i18demo/config/UrlLocaleInterceptor.java
:
package com.awaimai.i18demo.config;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.support.RequestContextUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class UrlLocaleInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
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
语言处理器 src/main/java/com/awaimai/i18demo/config/UrlLocaleResolver.java
:
package com.awaimai.i18demo.config;
import org.springframework.web.servlet.LocaleResolver;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;
public class UrlLocaleResolver implements LocaleResolver {
private static final String URL_LOCALE_ATTRIBUTE_NAME = "URL_LOCALE_ATTRIBUTE_NAME";
@Override
public Locale resolveLocale(HttpServletRequest request) {
String uri = request.getRequestURI();
System.out.println("URI=" + uri);
String prefixEn = request.getServletContext().getContextPath() + "/en/";
String prefixCn = request.getServletContext().getContextPath() + "/cn/";
Locale locale = null;
if (uri.startsWith(prefixEn)) {
locale = Locale.ENGLISH;
} else if (uri.startsWith(prefixCn)) {
locale = Locale.SIMPLIFIED_CHINESE;
}
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
}
}
WebMvcConfig
配置文件需要做一些修改,如下:
package com.awaimai.i18demo.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;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Bean(name = "localeResolver")
public LocaleResolver getLocaleResolver() {
return new UrlLocaleResolver();
}
@Bean(name = "messageSource")
public MessageSource getMessageResource() {
ReloadableResourceBundleMessageSource messageResource = new ReloadableResourceBundleMessageSource();
messageResource.setBasename("classpath:i18n/message");
messageResource.setDefaultEncoding("UTF-8");
return messageResource;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UrlLocaleInterceptor()).addPathPatterns("/en/*", "/cn/*");
}
}
创建一个新的控制器 src/main/java/com/awaimai/i18demo/controller/MainController2.java
:
package com.awaimai.i18demo.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class MainController2 {
@RequestMapping(value = { "/{locale:en|cn}/login2" })
public String login() {
return "login2";
}
}
以及新的 Thymeleaf 视图文件 src/main/resources/templates/login2.html
:
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title th:utext="#{title}"></title>
</head>
<body>
<div style="text-align: right;padding:5px;margin:5px 0px;background:#ccc;">
<a th:href="@{/en/login2}">English</a>
|
<a th:href="@{/cn/login2}">简体中文</a>
</div>
<form method="post" action="">
<table>
<tr>
<td>
<strong th:utext="#{username}"></strong>
</td>
<td><input name="userName"/></td>
</tr>
<tr>
<td>
<strong th:utext="#{password}"></strong>
</td>
<td><input name="password"/></td>
</tr>
<tr>
<td colspan="2">
<input type="submit" th:value="#{submit}"/>
</td>
</tr>
</table>
</form>
</body>
</html>
视图文件仅仅修改了切换语言按钮的链接格式,由@{/login1?lang=en}
格式改成了@{/en/login2}
。
运行项目,访问:http://localhost:8080/en/login2 即可看到开始的效果。
5 内容存储在数据库的多语言网站
如果你需要根据语言信息从不同的数据库、数据表或数据字段中读取数据,那么以上两个例子就无法满足了。
解决方法:为 Spring Boot 配置不同的数据源(Datasources),每个数据源包含不同语言的内容。