春季休息I18N消息
#java #springboot #i18n

什么是国际化?

在本文中,我们将为我们的春季REST API客户端点增加国际化支持。国际化是创建可以适应不同语言和地区的应用程序的过程。我们要实现的目标是增加对多种语言的消息的支持。因此,具有不同语言偏好的客户将根据语言获取特定的消息。

默认的自动配置

默认情况下,Spring Boot在类Path的根部寻找消息资源束的存在。当默认属性文件可用时,适用自动配置(默认情况下)。 Spring Boot在SCR/MAIN/RESOSDER文件夹中搜索消息文件。可以通过属性更改,如下所示:

spring.messages.basename=messages,config.i18n.messages

如果找到了属性文件,则Spring Boot配置了一个用于解决消息的MessageRebe Bean,并支持此类消息的参数化和国际化。

Spring Boot还配置了寻找位置的LocalerSolver实现。默认实现是Acceptheaderlocalereresolver,它检查接受接受语言标头以匹配请求的语言环境。

创建消息文件

消息存储在Message_xx.properties文件中的键值对中。 XX表示语言代码(通常是两个字母代码,例如en,fr,it ...)。此外,可以添加区域/国家/地区(通常是两个字母代码),以便对地区进行更多细粒度的控制。

对于我们的应用程序,将在资源文件夹中创建两个属性文件。第一个是后备消息。带英文内容的Properties文件。当请求中未指定语言环境时,将使用它。第二种是支持西班牙语,并将被命名为messages_es.properties

每个本地化文件中的密钥相同。它们的值或描述在每种语言中都有特殊性。例如,后备文件中的英语内容contians以下条目

customer.name.required=Name is required.
customer.name.size=Name must be at least 3 characters and at most 20 characters.
customer.name.invalid=Name can only contain letters and spaces.
customer.email.required=Email is required.
customer.email.invalid=Email is invalid. 
customer.dob.past=Date of Birth must be in the past.

和西班牙语文件

customer.name.required=Nombre es obligatorio.
customer.name.size=Nombre debe tener al menos 3 caracteres y 20 a lo sumo.
customer.name.invalid=Nombre solo puede estar formado por letras y espacios.
customer.email.required=Email es obligatorio.
customer.email.invalid=Email es invalido. 
customer.dob.past=Fecha de nacimiento debe estar en el pasado.

使用代码中的键

现在,消息键可以在我们的应用程序中使用。客户控制器在发送帖子或投票请求时验证客户请求。由于两种情况的验证均相同,让我们看一下客户更新的情况

@PutMapping("{customerId}")
public ResponseEntity<Customer> updateCustomer( 
    @PathVariable("customerId") Long id, 
    @Valid @RequestBody CustomerRequest customerRequest)  {
    Customer customer = customerRepo.findById(id)
        .orElseThrow(() -> new EntityNotFoundException(id, 
         Customer.class));

    Customer updatedCustomer = CustomerUtils.convertToCustomer( 
        customerRequest);
    updatedCustomer.setId(id);
    customerRepo.save(updatedCustomer);

    return ResponseEntity.ok(updatedCustomer);
}

在“客户”类中,键放在约束的消息属性中。

public record CustomerRequest(
    @NotBlank(message = "{customer.name.required}")
    @Size(min = 3, max = 20, message = "{customer.name.size}")
    @Pattern(regexp = "[a-zA-Z\\s]+", message = " 
    {customer.name.invalid}") String name,
    @NotBlank(message = "{customer.email.required}") 
    @Email(message = "{customer.email.invalid}") String email,
    @Past(message = "{customer.dob.past}") LocalDate dateOfBirth) {}

最后,处理验证异常的顾问

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
RestErrorResponse handleException(MethodArgumentNotValidException ex) {
    String message = ex.getFieldErrors()
        .stream()
        .map(e -> e.getDefaultMessage())
        .reduce("Errors found:", String::concat);
        return new RestErrorResponse( 
        HttpStatus.BAD_REQUEST.value(), message, 
        LocalDateTime.now());
}

现在该测试了。我们的第一个尝试是在不通知接受语言标题的情况下提出一个请求,如下图所示

Image description

它落后于消息。专业文件,因此以英语显示错误。第二个请求将发送带有价值ES的接受语言标头。结果可以在下面查看

Image description

现在,错误现在以西班牙语回来。如果我们的应用程序需要支持更多的语言,那么它就像为所需语言环境创建新的消息文件一样简单。

仍然用英语进行硬编码的一件事是“发现错误:”字面前缀作为错误消息的一部分。让我们将其移至下一节中的属性文件。

MessageReSource类

org.springframework.context的类MessageRece提供接收翻译消息的方法。 Springboot将为我们创建此豆。然后,将其注入将使用消息的类中。就我们而

@RestControllerAdvice
public class GlobalExceptionHandler {
    MessageSource messageSource;
    public GlobalExceptionHandler(MessageSource messageSource) {
        this.messageSource = messageSource;
    }
...
}

接下来,键值消息将添加到特定于语言的属性文件中。对于英语

errors.found=Errors found: 

和西班牙语

errors.found=Errores encontrados : 

最后一步是用处理程序方法中的密钥替换硬编码值。

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
RestErrorResponse handleException(MethodArgumentNotValidException ex, Locale locale) {
    String message = ex.getFieldErrors().stream()
        .map(e -> e.getDefaultMessage())
        .reduce(messageSource.getMessage("errors.found", null, 
             locale), String::concat);
        return new RestErrorResponse( 
           HttpStatus.BAD_REQUEST.value(), message, 
           LocalDateTime.now());
}

请注意,可以在异常处理程序中以参数作为参数传递。正如预期的那样,当重新运行测试时,值正确翻译。在告知Accept语言标题的ES值时,返回以下响应

{
    "status": 400,
    "message": "Errores encontrados : Nombre debe tener al menos 3 caracteres y 20 a lo sumo.Fecha de nacimiento debe estar en el pasado.Email es invalido. ",
    "timestamp": "2023-05-27T19:10:57.0064035"
}

自定义异常消息

在本节中,我们将翻译自定义异常消息。在我们的应用程序中,如果不存在要更新的客户,则会抛出EntityNotFoundException。该消息在异常类中进行了硬编码。但是,我们希望此消息可用于受支持的语言。

第一步是在提出此异常时为错误添加一个新键。

--- english file
entity.notFound=Entity {0} for id {1} was not found.

--- spanish file
entity.notFound=Entity {0} for id {1} was not found.

值{0}和{1}是参数的占位符。第一个参数将进入位置{0},第二个参数将应用于{1}。

第二步是为其声明一种新的异常处理程序方法。同样,MessagesRyce类提供了获取消息的方法。参数是在GetMethodMessage作为对象数组的第二个参数中传递的。这两个值都存储在自定义中,并在Instatiation时间设置。

 @ExceptionHandler(EntityNotFoundException.class)
 @ResponseStatus(HttpStatus.BAD_REQUEST)
RestErrorResponse handleException(EntityNotFoundException ex, 
    Locale locale) {
    return new RestErrorResponse(HttpStatus.BAD_REQUEST.value(),
        messageSource.getMessage(
        "entity.notFound", 
        new Object[]{ ex.getTheClassName(), ex.getId()} , locale),
        LocalDateTime.now());
    }

自定义异常代码低于

public class EntityNotFoundException extends RuntimeException {
    private Long id;
    private Class theClass;
    public EntityNotFoundException(@NotNull Long id,@NotNull Class 
        theClass) {
        super(theClass.getName()+" "+id+" not found!");
        this.id = id;
        this.theClass = theClass;
    }

    public Long getId() {
        return id;
    }

    public String getTheClassName() {
        return theClass.getSimpleName();
    }
}

测试时间。让我们尝试更新不存在的客户。

Image description

结论

让我们回顾一下您在本文中学到的知识的要点:

  1. 什么是国际化及其在Springboot中如何启用。
  2. 什么是消息文件。
  3. 创建多语言自定义约束错误消息。
  4. 创建多语言自定义异常错误消息。

现在,您可以在春季项目中实现自己的多语言解决方案。可以找到源代码here

在Java和Spring上订阅更多内容!