一、整体交互流程图
前端切换语言 → 存储本地 → 后续请求携带语言标识 → 后端解析标识 → 返回对应语言资源
二、前端详细步骤
1. 语言切换组件实现
<!-- src/components/LangSelect.vue -->
<template>
<el-select
v-model="currentLang"
@change="handleLanguageChange"
size="small"
style="width: 120px"
>
<el-option
v-for="item in languages"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<script setup>
import { useI18n } from 'vue-i18n'
import { ElMessage } from 'element-plus'
import { setLanguage } from '@/api/system'
const { locale } = useI18n()
const currentLang = ref(localStorage.getItem('lang') || 'zh-CN')
const languages = [
{ label: '中文', value: 'zh-CN' },
{ label: 'English', value: 'en-US' }
]
// 切换语言逻辑
const handleLanguageChange = async (lang) => {
try {
// 1. 调用后端接口同步语言偏好(可选)
await setLanguage(lang)
// 2. 更新前端i18n实例
locale.value = lang
// 3. 持久化存储
localStorage.setItem('lang', lang)
// 4. 刷新页面使路由meta生效
window.location.reload()
ElMessage.success(lang === 'zh-CN' ? '语言切换成功' : 'Language changed')
} catch (err) {
console.error('语言切换失败', err)
}
}
</script>
2. 请求拦截器配置
// src/utils/request.js
import axios from 'axios'
import { getToken } from '@/utils/auth'
import i18n from '@/lang'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
config => {
// 携带当前语言标识
config.headers['Accept-Language'] = i18n.global.locale.value
// 如果已登录,携带token
if (getToken()) {
config.headers['Authorization'] = 'Bearer ' + getToken()
}
return config
},
error => {
return Promise.reject(error)
}
)
三、后端详细步骤(Spring Boot)
1. 语言标识处理策略
优先级顺序:
- 请求头
Accept-Language
(前端主动传递) - Cookie
lang
(可选持久化方案) - 默认
zh-CN
2. 国际化配置类
@Configuration
public class I18nConfig {
// 区域解析器
@Bean
public LocaleResolver localeResolver() {
return new SmartLocaleResolver();
}
// 消息源配置
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource =
new ReloadableResourceBundleMessageSource();
messageSource.setBasename("classpath:i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setCacheSeconds(3600);
return messageSource;
}
}
// 自定义区域解析器
public class SmartLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
// 1. 检查请求头
String headerLang = request.getHeader("Accept-Language");
if (StringUtils.hasText(headerLang)) {
return Locale.forLanguageTag(headerLang);
}
// 2. 检查Cookie
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if ("lang".equals(cookie.getName())) {
return Locale.forLanguageTag(cookie.getValue());
}
}
}
// 3. 默认中文
return Locale.CHINA;
}
@Override
public void setLocale(HttpServletRequest request,
HttpServletResponse response,
Locale locale) {
throw new UnsupportedOperationException();
}
}
3. 统一响应处理
// 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {
@Autowired
private MessageSource messageSource;
// 处理业务异常
@ExceptionHandler(ServiceException.class)
public AjaxResult handleServiceException(ServiceException e) {
String message = messageSource.getMessage(e.getCode(),
e.getArgs(),
LocaleContextHolder.getLocale());
return AjaxResult.error(e.getCode(), message);
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public AjaxResult handleValidException(MethodArgumentNotValidException e) {
String code = "err.param_invalid";
String defaultMsg = e.getBindingResult().getFieldError().getDefaultMessage();
String message = messageSource.getMessage(code,
null,
defaultMsg, // 默认消息作为fallback
LocaleContextHolder.getLocale());
return AjaxResult.error(code, message);
}
}
四、前后端交互协议
1. 语言切换API(可选)
// 后端接口
@PostMapping("/system/user/language")
public AjaxResult setLanguage(@RequestParam String lang) {
// 1. 验证语言合法性
if (!Arrays.asList("zh-CN", "en-US").contains(lang)) {
throw new ServiceException("err.invalid_language");
}
// 2. 更新用户语言偏好(需用户登录)
LoginUser loginUser = getLoginUser();
SysUser user = userService.selectUserById(loginUser.getUserId());
user.setLang(lang);
userService.updateUser(user);
// 3. 设置Cookie(可选)
response.addCookie(new Cookie("lang", lang));
return AjaxResult.success();
}
2. 异常响应格式
{
"code": "err.user_not_exist",
"msg": "用户不存在", // 根据语言动态变化
"data": null
}
五、关键配置细节
1. 消息资源文件示例
# messages_en_US.properties
login.title=MDM System
login.username=Username
button.submit=Submit
err.user_not_exist=User not found
validation.phone=Invalid phone format: {0}
# messages_zh_CN.properties
login.title=物料主数据管理系统
login.username=用户名
button.submit=提交
err.user_not_exist=用户不存在
validation.phone=手机号 {0} 格式不正确
2. Element Plus国际化
// src/lang/en-US.js
import elementLocale from 'element-plus/dist/locale/en.mjs'
export default {
el: elementLocale.el,
message: {
// 自定义组件文本...
}
}
六、测试验证方案
1. 前端测试用例
// 测试语言切换组件
describe('LangSelect', () => {
it('切换英语应更新localStorage', async () => {
const wrapper = mount(LangSelect)
await wrapper.find('.el-select').trigger('click')
await wrapper.findAll('.el-option')[1].trigger('click')
expect(localStorage.getItem('lang')).toBe('en-US')
})
})
2. 后端测试方法
// 测试消息解析
@SpringBootTest
public class I18nTest {
@Autowired
private MessageSource messageSource;
@Test
void testEnMessage() {
LocaleContextHolder.setLocale(Locale.US);
String msg = messageSource.getMessage("err.user_not_exist", null, Locale.US);
assertEquals("User not found", msg);
}
}
七、部署注意事项
- Nginx配置:
# 设置默认语言
proxy_set_header Accept-Language $http_accept_language;
- 浏览器缓存清理:
// 在语言切换时添加时间戳
axios.interceptors.request.use(config => {
if (config.url.includes('?')) {
config.url += `&t=${Date.now()}`
} else {
config.url += `?t=${Date.now()}`
}
return config
})
- 多语言热更新:
// 开发环境开启热加载
spring:
messages:
reloadable: true # 生产环境应设为false
八、排错指南
问题现象 | 排查步骤 | 解决方案 |
---|---|---|
切换语言后部分文本未更新 | 1. 检查Vue DevTools的i18n状态 2. 查看网络请求是否携带正确header 3. 检查后端消息文件编码 |
确保文件保存为UTF-8 |
后端返回的提示语仍是中文 | 1. 调试LocaleContextHolder.getLocale() 2. 检查拦截器是否生效 |
确认请求头传递正确 |
切换语言后页面样式错乱 | 检查Element Plus的locale是否同步 | 正确导入element语言包 |
此方案可实现:前端无刷新切换、后端动态响应、用户偏好持久化存储(本地+服务端)。建议先完成基础框架集成,再通过脚本批量提取现有中文文案进行翻译。