logo

Internacionalización i18 con Spring MVC

Introducción

En este proyecto (descargar de GitHub) vamos a internacionalizar el proyecto anterior, esto es tener el formulario en varios idiomas, en este caso lo vamos a tener en español y en inglés. Principalmente para que todo el mundo pueda entender los mensajes de error que puedan surgir al rellenar el formulario.

El numerónimo "i18n" se debe a que entre la primera i y la última ene de la palabra internationalization hay 18 letras.

Para quien quiera saber más sobre el tema, wikipedia es una fuente fenomenal.

El Proyecto

La estructura del proyecto
estructura

Podemos observar dos archivos properties nuevos en la carpeta resources, messages_es y messages_en que son los archivos que contienen el texto en los idiomas que queremos que se vea nuestra página. Aunque el nombre del archivo puede ser el que nosotros queramos, el sufijo "_es" es obligatorio para el idioma español y el sufijo "_en" es para el ingles.

Las dependencias
pom.xml

<dependencies>
  <!-- Spring -->
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.1.5.RELEASE</version>
  </dependency>
  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
      <scope>test</scope>
  </dependency>
  <!-- Hibernate Validator -->
    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>${hibernate.version}</version>
    </dependency>
  <!-- javax.servlet -->
  <dependency>   
      <groupId>javax.servlet</groupId>   
      <artifactId>javax.servlet-api</artifactId>    
      <version>3.1.0</version>
      <scope>provided</scope>   
  </dependency>
  <dependency>  
      <groupId>javax.servlet.jps</groupId> 
      <artifactId>javax.servlet.jsp-api</artifactId> 
      <version>2.3.1</version> 
      <scope>provided</scope> 
  </dependency>
  <!-- jstl -->
  <dependency>  
      <groupId>javax.servlet</groupId> 
      <artifactId>jstl</artifactId> 
      <version>1.2</version>  
  </dependency>
  <!-- lombok -->
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.16.16</version>
      <scope>provided</scope>
  </dependency>
</dependencies>
                    

La dependecia nueva lombok la veremos cuando lleguemos al modelo del proyecto.

La Configuración

Las clases SpringWebAppInitializer y SpringappBusinessConfig no sufren ninguna modificación respecto a las del proyecto anterior, Formulario con Spring Mvc, pero la clase WebMvcConfig si que cambia, pues necesitamos un bean que que implemente la interface MessageSource para localizar el origen de nuestros archivos que contienen el texto en varios idiomas

WebMvcConfig.java

package com.wanchopi.spring.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;


/**
 * Configure Spring MVC
 * @author Wanchopi
 *
 */
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.wanchopi.spring")
public class WebMvcConfig implements WebMvcConfigurer {
  
  @Bean(name = "viewResolver")
  public InternalResourceViewResolver getViewResolver() {
    InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
    
    viewResolver.setPrefix("/WEB-INF/views/");
    viewResolver.setSuffix(".jsp");
    
    return viewResolver;
  }
  
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/public/**").addResourceLocations("/public/"); 
    }
  
  @Bean
  public MessageSource messageSource() {
      ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
      messageSource.setBasename("messages");
      messageSource.setDefaultEncoding("UTF-8");
      //messageSource.setCacheSeconds(1); 
      return messageSource;
  }

}
                  

Os dejo un enlace interesante a una página que explica esto bastante bien.

El Modelo
Student.java

package com.wanchopi.spring.model;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;

import lombok.Getter;
import lombok.Setter;

/**
 * Entity
 * @author Wanchopi
 *
 */
public class Student {
  
  @NotNull
  @Size(min = 3, max = 25)
  @Getter @Setter
  private String userName;
  
  @NotNull
  @Email
  @Getter @Setter
  private String email;
  
  @NotNull
  @Pattern(regexp = "^(?=\\w*\\d)(?=\\w*[A-Z])(?=\\w*[a-z])\\S{4,6}$")
  @Getter @Setter
  private String password;
  
  @NotNull 
  @Getter @Setter
  private String gender;
  
  @NotNull
  @Getter @Setter
  private String country;
  
  @Getter @Setter
  private boolean receivePaper;
  
  @NotEmpty
  @Getter @Setter
  private String[] favoriteFrameworks;

}
                    

Esta clase ha adelgazado bastante, esto se debe a que por una parte, se han eliminado los mensajes personalizados de error ya que ahora se encuentran en los properties correspondientes como deciamos al principio y por otra se han eliminado los setters y getters y se han sustituido por las anotaciones @Getter y @Setter en cada campo. Esto es lo que hace Lombok, genera los getters y setters a partir de la anotaciones @Getter y @Setter.

Visitar esta página Proyecto Lombok para quien quiera conocer todo lo que puede hacer Lombok por los desarrolladores de software

El Controlador

Nada nuevo en el controlador.

StudentController.java

package com.wanchopi.spring.controller;


import javax.validation.Valid;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.propertyeditors.StringTrimmerEditor;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.wanchopi.spring.model.Student;


/**
 * Spring MVC Controller
 * @author Wanchopi
 *
 */
@Controller
public class StudentController {
  
  @Value("${countries}")
  private String countries; // load field countries of properties
  
  @Value("${frameworks}")
  private String frameworks;  // load field frameworks of properties
  
  @ModelAttribute("countryList")
  public String[] loadCountries() {
    String[] items = this.countries.split(","); // converts a comma-separated string into an array
    return items; 
  }
  
  @ModelAttribute("frameworkList")
  public String[] loadFrameworks() {
    String[] items = this.frameworks.split(","); // convers a comma-separated string into an array
    return items;
  }
  
  @InitBinder
    public void initBinder(WebDataBinder dataBinder) {
        StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
        dataBinder.registerCustomEditor(String.class, stringTrimmerEditor);
    }
  
  @GetMapping("/")
  public String student(Model model) {
    
    Student student = new Student();
    model.addAttribute("student", student);
    return "index";
  }
  

  @PostMapping("/save")
  public String saveForm(@Valid @ModelAttribute("student") Student student,
      BindingResult theBindingResult) {
    
    if (theBindingResult.hasErrors()) {
      return "index";
    }
    else {
      return "success";
    }
  }

}
                    
Las Vistas
index.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
...
<form:form action="save" method="post" modelAttribute="student" cssClass="form-horizontal">
    <!-- user name -->
    <div class="form-group">
        <label for="username" class="col-md-3 control-label font-weight-bold">
          <spring:message code="message.field.name"/>
        </label>
        <div class="col-md-9">
          <form:input path="userName" type="text" cssClass="form-control" />
        </div>
    </div>
    <!-- email -->
    <div class="form-group">
        <label for="email" class="col-md-3 control-label font-weight-bold">
          <spring:message code="message.field.email"/>
        </label>
        <div class="col-md-9">
          <form:input path="email" type="email" cssClass="form-control" />
        </div>
    </div>
    <!-- password -->
    <div class="form-group">
        <label for="password" class="col-md-3 control-label font-weight-bold">
          <spring:message code="message.field.password"/>
        </label>
        <div class="col-md-9">
          <form:input path="password" type="password" cssClass="form-control" />
        </div>
    </div>
    <!-- radiobutton responsive -->
    <div class="container form-group">
      <div class="row">
        <label for="gender" class="col-md-3 control-label font-weight-bold">
          <spring:message code="message.field.gender"/>
        </label>
      </div>
      <div class="row">
        <div class="col-lg-3">
          <form:radiobutton path="gender" value="Hombre"/>
          <label class="text-muted" for="male">
            <strong><spring:message code="message.field.gender.male"/></strong>
          </label>
        </div>
        <div class="col-lg-3">
          <form:radiobutton path="gender" value="Mujer"/>
          <label class="text-muted" for="female">
            <strong><spring:message code="message.field.gender.male"/></strong>
          </label>
        </div>
      </div>
    </div>
    <!-- country -->
    <div class="form-group">
      <label for="country" class="col-md-3 control-label font-weight-bold">
        <spring:message code="message.field.country"/>
      </label>
      <div class="col-md-9">
        <form:select path="country" class="text-muted">
          <form:option value="NONE" label="--- Select ---"/>
          <form:options items="${countryList}"/>
        </form:select>
      </div>
    </div>
    <!-- favoriteFrameworks responsive -->
    <div class="container form-group">
      <div class="row">
        <label for="frameworks" class="col-md-9 control-label font-weight-bold">
          <spring:message code="message.field.frameworks"/>
        </label>
      </div>
      <div class="row">
        <c:forEach var="i" items="${frameworkList}">
          <div class="col-lg-3">
            <form:checkbox path="favoriteFrameworks" value="${i}"/>
            <label class="text-muted" for="male"><strong><c:out value="${i}"></c:out></strong></label>
          </div>
        </c:forEach>
      </div>
    </div>
    <!-- receivePaper -->
    <div class="container form-group">
      <div class="row">
        <div class="col-lg-4">
          <label for="receivePaper" class="control-label font-weight-bold">
            <spring:message code="message.field.newsletter"/>
          </label>
        </div>
        <div class="col-lg-4">
          <form:checkbox path = "receivePaper" />
        </div>
      </div>
    </div>
    <div class="form-group">
      <!-- Button -->
      <div class="col-md-offset-3 col-md-9">
        <form:button type="Submit" class="btn btn-primary">
          <spring:message code="message.field.btn"/>
        </form:button>
      </div>
    </div>
  </form:form>
                  

Importamos los Tags que nos hacen falta:


<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
                  

Obtenemos el valor de los properties:


                    ...
                    <spring:message code="message.field.name"/>
                    ...
                  
success.jsp

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>success :: i18n</title>
<link rel="stylesheet" href="public/css/bootstrap.min.css">
<link rel="stylesheet" href="public/css/mystyle.css">
</head>
<body>
  <div class="container">
    <div class="row">
      <div class="col-lg-12" align="center">
        <img alt="logo" src="public/images/logo.png">
      </div>
    </div>
  </div>
  <div class="container">
    <div class="row">
      <div class="col-lg-12" align="center">
        <h3><spring:message code="message.success"/></h3>
        <table class="table table-striped table-bordered">
          <thead align="center">
            <tr>
              <th scope="col"><spring:message code="message.field.name"/></th>
              <th scope="col"><spring:message code="message.field.email"/></th>
              <th scope="col"><spring:message code="message.field.gender"/></th>
              <th scope="col"><spring:message code="message.field.password"/></th>
              <th scope="col"><spring:message code="message.field.country"/></th>
              <th scope="col"><spring:message code="message.field.newsletter"/></th>
              <th scope="col"><spring:message text="Framework"/></th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>${student.userName}</td>
              <td>${student.email}</td>
              <td>${student.gender}</td>
              <td>${student.password}</td>
              <td>${student.country}</td>
              <td>
                <c:choose>
                  <c:when test="${student.receivePaper==true}">
                    accepted
                  </c:when>
                  <c:otherwise>
                    denied
                  </c:otherwise>
                </c:choose>
              </td>
              <td>
                <c:forEach items="${student.favoriteFrameworks}"  var="name" varStatus="status">
                  <c:out value="${name}"></c:out>
                  <c:if test="${!status.last}">,</c:if>
                </c:forEach>
              </td>
            </tr>
          </tbody>
        </table>
      </div>
    </div>
  </div>

  <!-- jQuery core JavaScript -->
  <script src="public/js/jquery.min.js"></script>
  <!-- Bootstrap core JavaScript -->
  <script src="public/js/bootstrap.min.js"></script>
</body>
</html>
                  

Para probar que muestra los mensajes en el idioma en el que está configurado el navegador, ejecutar el programa y abrir la url en un navegador (Firefox, por ejemplo), después configuraís Chrome en inglés y haceís lo mismo y vereís la diferencia.

Envío del formulario con errores en Firefox:

aplicación

Envío del formulario con errores en Chrome (configurado en inglés):

aplicación

Herramientas y Software

  • Eclipse Version: 2019-03 (4.11.0)
  • jdk 1.8.0
  • Maven 3.6
  • Spring MVC 5.1.5
  • bootstrap 4.3.1
  • Server Tomcat v8
anterior

Validar formulario con Spring

siguiente

Spring MVC - Hibernate