SpringBoot i18n 国际化实现案例

149 阅读3分钟

SpringBoot i18n 国际化实现案例

国际化(i18n)是使应用程序能够支持多种语言和地区的过程。SpringBoot提供了强大的国际化支持,本文将详细介绍如何在SpringBoot项目中实现国际化功能。

一、基本实现步骤

1. 创建国际化资源文件

src/main/resources 目录下创建 i18n 文件夹,然后创建以下资源文件:

  • messages.properties:默认语言配置文件
  • messages_zh_CN.properties:中文配置文件
  • messages_en_US.properties:英文配置文件

示例内容:

# messages.properties (默认)
welcome.message=Welcome to our application!
user.greeting=Hello, {0}
page.title=Home Page

# messages_zh_CN.properties (中文)
welcome.message=欢迎使用我们的应用程序!
user.greeting=你好,{0}
page.title=首页

# messages_en_US.properties (英文)
welcome.message=Welcome to our application!
user.greeting=Hello, {0}
page.title=Home Page

2. 配置application.yml

在配置文件中设置国际化相关属性:

spring:
  messages:
    encoding: UTF-8
    basename: i18n/messages
    fallback-to-system-locale: false

3. 创建国际化配置类

@Configuration
public class I18nConfig implements WebMvcConfigurer {
    
    /**
     * 配置区域解析器
     */
    @Bean
    public LocaleResolver localeResolver() {
        // 使用SessionLocaleResolver可以基于Session存储用户选择的语言
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        // 设置默认语言为中文
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }
    
    /**
     * 配置语言切换拦截器
     * 通过URL参数lang来切换语言,例如:?lang=zh_CN 或 ?lang=en_US
     */
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor localeChangeInterceptor = new LocaleChangeInterceptor();
        localeChangeInterceptor.setParamName("lang");
        return localeChangeInterceptor;
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
}

4. 在Controller中使用国际化

@RestController
@RequestMapping("/api/i18n")
public class I18nController {
    
    @Autowired
    private MessageSource messageSource;
    
    /**
     * 获取国际化消息
     */
    @GetMapping("/message")
    public String getMessage(@RequestParam(defaultValue = "welcome.message") String key) {
        // 使用当前线程上下文的区域设置
        return messageSource.getMessage(key, null, LocaleContextHolder.getLocale());
    }
    
    /**
     * 获取带参数的国际化消息
     */
    @GetMapping("/message/params")
    public String getMessageWithParams(@RequestParam(defaultValue = "user.greeting") String key,
                                     @RequestParam(defaultValue = "Guest") String username) {
        return messageSource.getMessage(key, new Object[]{username}, LocaleContextHolder.getLocale());
    }
    
    /**
     * 手动切换语言(可选,如果不使用拦截器)
     */
    @GetMapping("/change-language")
    public String changeLanguage(@RequestParam String lang, HttpSession session) {
        Locale locale;
        if ("en_US".equals(lang)) {
            locale = Locale.US;
        } else {
            locale = Locale.SIMPLIFIED_CHINESE;
        }
        
        // 如果使用SessionLocaleResolver
        session.setAttribute(SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME, locale);
        
        return "Language changed to: " + locale;
    }
}

二、支持表单验证的国际化

1. 增强配置类以支持验证国际化

@Configuration
public class I18nConfig implements WebMvcConfigurer {
    
    @Autowired
    private MessageSource messageSource;
    
    @Bean
    public LocaleResolver localeResolver() {
        SessionLocaleResolver localeResolver = new SessionLocaleResolver();
        localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
        return localeResolver;
    }
    
    @Bean
    public LocaleChangeInterceptor localeChangeInterceptor() {
        LocaleChangeInterceptor interceptor = new LocaleChangeInterceptor();
        interceptor.setParamName("lang");
        return interceptor;
    }
    
    /**
     * 配置验证国际化工厂
     */
    @Bean
    public LocalValidatorFactoryBean validatorFactoryBean() {
        LocalValidatorFactoryBean validatorFactoryBean = new LocalValidatorFactoryBean();
        validatorFactoryBean.setValidationMessageSource(messageSource);
        return validatorFactoryBean;
    }
    
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(localeChangeInterceptor());
    }
    
    @Override
    public Validator getValidator() {
        return validatorFactoryBean();
    }
}

2. 在实体类中使用国际化验证信息

@Data
public class User {
    
    @NotBlank(message = "{user.name.not.blank}")
    private String name;
    
    @Email(message = "{user.email.invalid}")
    private String email;
    
    @Min(value = 18, message = "{user.age.too.young}")
    private int age;
}

3. 在资源文件中添加验证消息

# messages.properties
user.name.not.blank=Name cannot be blank
user.email.invalid=Invalid email format
user.age.too.young=Age must be at least 18

# messages_zh_CN.properties
user.name.not.blank=用户名不能为空
user.email.invalid=邮箱格式不正确
user.age.too.young=年龄必须年满18岁

4. 在Controller中使用验证

@RestController
@RequestMapping("/api/user")
public class UserController {
    
    @PostMapping("/register")
    public ResponseEntity<?> register(@Valid @RequestBody User user, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            List<String> errors = bindingResult.getAllErrors().stream()
                    .map(DefaultMessageSourceResolvable::getDefaultMessage)
                    .collect(Collectors.toList());
            return ResponseEntity.badRequest().body(errors);
        }
        // 处理注册逻辑
        return ResponseEntity.ok("User registered successfully");
    }
}

三、自定义注解实现国际化验证

1. 创建自定义注解

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = I18nNotNullValidator.class)
public @interface I18nNotNull {
    String message() default "{validation.not.null}";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2. 创建验证器

@Component
public class I18nNotNullValidator implements ConstraintValidator<I18nNotNull, Object> {
    
    private final MessageSource messageSource;
    private String messageKey;
    
    public I18nNotNullValidator(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
    
    @Override
    public void initialize(I18nNotNull constraintAnnotation) {
        this.messageKey = constraintAnnotation.message();
    }
    
    @Override
    public boolean isValid(Object value, ConstraintValidatorContext context) {
        if (value == null) {
            Locale locale = LocaleContextHolder.getLocale();
            String message = messageSource.getMessage(messageKey, null, locale);
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message).addConstraintViolation();
            return false;
        }
        return true;
    }
}

四、使用Profile实现不同语言环境下的业务逻辑

1. 创建配置类

@Configuration
@Data
public class I18NConfiguration {
    
    @Value("${spring.profiles.active}")
    private String locale;
    
    @Profile("zh_CN")
    @Bean
    public IProductService zhCNProductService() {
        return new ZhCNProductService();
    }
    
    @Profile("en_US")
    @Bean
    public IProductService enUSProductService() {
        return new EnUSProductService();
    }
}

2. 创建服务接口和实现

public interface IProductService {
    Map<String, String> getProduct();
}

@Service
public class ZhCNProductService implements IProductService {
    @Override
    public Map<String, String> getProduct() {
        Map<String, String> product = new HashMap<>();
        product.put("name", "产品名称");
        product.put("description", "产品描述");
        return product;
    }
}

@Service
public class EnUSProductService implements IProductService {
    @Override
    public Map<String, String> getProduct() {
        Map<String, String> product = new HashMap<>();
        product.put("name", "Product Name");
        product.put("description", "Product Description");
        return product;
    }
}

五、其他实用技巧

1. 使用AcceptHeaderLocaleResolver

如果希望通过HTTP请求头中的Accept-Language来自动检测用户语言偏好:

@Bean
public LocaleResolver localeResolver() {
    AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
    // 设置支持的语言
    List<Locale> locales = new ArrayList<>();
    locales.add(Locale.SIMPLIFIED_CHINESE);
    locales.add(Locale.US);
    localeResolver.setSupportedLocales(locales);
    // 设置默认语言
    localeResolver.setDefaultLocale(Locale.SIMPLIFIED_CHINESE);
    return localeResolver;
}

2. 在Thymeleaf模板中使用国际化

如果使用Thymeleaf模板引擎,可以这样使用国际化:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title th:text="#{page.title}"></title>
</head>
<body>
    <h1 th:text="#{welcome.message}"></h1>
    <p th:text="#{user.greeting(${username})}"></p>
    
    <!-- 语言切换链接 -->
    <div>
        <a th:href="@{/(lang=zh_CN)}">中文</a>
        <a th:href="@{/(lang=en_US)}">English</a>
    </div>
</body>
</html>

3. 在自定义异常中使用国际化

@Service
public class UserService {
    
    @Autowired
    private MessageSource messageSource;
    
    public void validateUser(User user) {
        if (user == null) {
            String errorMessage = messageSource.getMessage("user.null", null, LocaleContextHolder.getLocale());
            throw new BusinessException(errorMessage);
        }
        // 其他验证逻辑
    }
}

六、完整项目结构

src/main/java/
└── com/example/demo/
    ├── DemoApplication.java
    ├── config/
    │   └── I18nConfig.java
    ├── controller/
    │   ├── I18nController.java
    │   └── UserController.java
    ├── service/
    │   ├── IProductService.java
    │   ├── ZhCNProductService.java
    │   └── EnUSProductService.java
    ├── model/
    │   └── User.java
    ├── exception/
    │   └── BusinessException.java
    └── annotation/
        └── I18nNotNull.java
src/main/resources/
    ├── i18n/
    │   ├── messages.properties
    │   ├── messages_zh_CN.properties
    │   └── messages_en_US.properties
    └── application.yml

通过以上步骤,您可以在SpringBoot项目中完整实现国际化功能,支持多语言切换、验证消息国际化以及根据不同语言环境提供不同的业务逻辑。这种方式既灵活又易于维护,适用于各种需要国际化的应用场景。