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.
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.
<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.
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
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.
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
Nada nuevo en el controlador.
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";
}
}
}
<%@ 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"/>
...
<%@ 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:
Envío del formulario con errores en Chrome (configurado en inglés):