Prefacio
La compañía ha transferido el proyecto de Struts2 a SpringMVC. Dado que el negocio de la compañía es servicios en el extranjero, la demanda de funciones internacionales es muy alta. La función de internacionalización de STRUTS2 es más perfecta que SpringMVC, pero la gran característica de Spring es que es personalizable y personalizada, por lo que su función de internacionalización se agrega cuando el proyecto de la compañía se trasplanta a SpringMVC. He compilado los registros y los he mejorado aquí.
Las funciones principales implementadas en este artículo:
Cargue directamente múltiples archivos internacionales desde una carpeta. La página Configuración de fondo muestra información internacional. El archivo que muestra información internacional se establece automáticamente utilizando interceptores y anotaciones. El archivo que muestra información internacional en la página front-end muestra información internacional.
Nota: Este artículo no introduce en detalle cómo configurar la internacionalización, el analizador regional, etc.
lograr
Inicialización del proyecto internacional
Primero cree un proyecto básico de Boot+Thymeleaf+Información de internacionalización (Message.Properties). Si lo necesita, puede descargarlo desde mi github.
Una breve mirada al directorio y los archivos del proyecto
Entre ellos, i18napplication.java establece una CookieLocaleResolver , que utiliza cookies para controlar los idiomas internacionales. También configure un interceptor LocaleChangeInterceptor para los cambios de intercepción en los idiomas internacionales.
@SpringBootApplication@ConfigurationPublic Class I18Napplication {public static void main (string [] args) {springapplication.run (i18napplication.class, args); } @Bean public localeSolver localeresolver () {Cookielocaleresolver SLR = new Cookielocaleresolver (); slr.setcookiemaxage (3600); slr.setcookiename ("idioma"); // Establezca el nombre de la cookie almacenada en el idioma return Slr; } @Bean public WebMVCCONFIGURER WebMVCConfigurer () {return New WebMVCConfigurer () {// interceptor @anular public void addInterceptors (InterceptorRegistry Registry) {Registry.adDinterceptor (new LocalEdangeInterceptor ()). AddPathPatterns ("/**"); }}; }}Echemos un vistazo a lo que está escrito en Hello.html:
<! Doctype html> <html xmlns = "http://www.w3.org/1999/xhtml" xmlns: th = "http://www.thymeleaf.org"> <Head> <title> Hola World! </Título> th: text = "#{i18n_page}"> </h1> <h3 th: text = "#{hello}"> </h3> </body> </html> Ahora comience el proyecto y visite http://localhost:9090/hello (configuré el puerto en 9090 en aplicaciones.
Dado que el lenguaje predeterminado del navegador es chino, predeterminará la búsqueda en Messages_ZH_CN.Properties, y si no, buscará en Message.Properties para palabras internacionales.
Luego ingresamos http://localhost:9090/hello?locale=en_US en el navegador y el idioma se cortará al inglés. Del mismo modo, si el parámetro después de la URL se establece en locale=zh_CH , el idioma se cortará a chino.
Cargue múltiples archivos internacionales directamente desde una carpeta
En nuestra página Hello.html, solo hay dos información internacional 'i18n_page' y 'hola'. Sin embargo, en proyectos reales, definitivamente no habrá tan pequeña como algunas información internacional, generalmente cientos de ellos. Entonces no debemos poner tanta información internacional en un archivo con messages.properties , y generalmente la información internacional se clasifica y almacena en varios archivos. Sin embargo, cuando el proyecto se hace más grande, estos archivos internacionales se volverán cada vez más. En este momento, es inconveniente configurar este archivo uno por uno en el archivo application.properties . Así que ahora implementamos una función para cargar automáticamente todos los archivos internacionales en el directorio de formulación.
Heredar recursos de recursos
Cree una clase bajo el proyecto para heredar ResourceBundleMessageSource o ReloadableResourceBundleMessageSource y nombrarlo MessageResourceExtension . E inyectarlo en el bean y nombrar messageSource , donde heredamos ResourceBundLemesSageAsurce.
@Component ("Messagesource") Class pública MessageReurceExtension extiende ResourceBundLemesSageurce {} Tenga en cuenta que nuestro nombre de componente debe ser 'Messagesurce', porque al inicializar ApplicationContext , el bean con el bean llamado 'Messagesource' se buscará. Este proceso está en AbstractApplicationContext.java . Echemos un vistazo al código fuente
/*** Inicializar el MessageOurce.*Use los padres si ninguno se define en este contexto.*/Proteged void initMessageurce () {configuableListableBeanFactory beanFactory = getBeanFactory (); if (beanFactory.ContainsLocalBean (Message_source_bean_name)) {this.messageSource = beanFactory.getBean (Message_source_bean_name, Messessource.class); ...}} ... En este método para inicializar Messessource, BeanFactory busca frijoles inyectados con el nombre MESSAGE_SOURCE_BEAN_NAME(messageSource) . Si no se encuentra, buscará si hay un frijol con el nombre en su clase principal.
Implementar la carga de archivo
Ahora podemos iniciar MessageResourceExtension que acabamos de crear
Se escribe el método para cargar el archivo.
@Component ("Messagesource") Public Class MessageReurceExtension extiende ResourceBundLemesSageSource {private final de logger estático final de logger = loggerFactory.getLogger (MessageSourceExtension.class); / *** Directorio de archivos de internacionalización especificado*/ @Value (value = "$ {spring.messages.basefolder: i18n}") privado string basefolder; / *** Archivo de internacionalización especificado por Messagesurce principal*/ @Value (value = "$ {Spring.Messages.Basename: Mensaje}") String BaseName de cadena privada; @PostConstruct public void init () {logger.info ("init MessagerSourceExtension ..."); if (? } catch (ioException e) {logger.error (e.getMessage ()); }} // Establecer mensajes de mensajes parentes de recursos de recursos de los bueblesurce parent.setBasename (Basename); this.setParentMessageSource (padre); } / ** * Obtenga todos los nombres de archivos internacionales en la carpeta * * @param Nombre del archivo de FolderName * @return * @throws ioexception * / private string [] getAllBaseNames (String FolderName) lanza IOException {recursos de recursos = new classpathResource (fOLOTERNAME); Archivo archivo = resource.getFile (); List <String> Basenames = new ArrayList <> (); if (file.exists () && file.isDirectory ()) {this.getallFile (basenames, file, ""); } else {logger.error ("El base especificado no existe o no es una carpeta"); } return Basenames.toarray (nueva cadena [BaseNames.Size ()]); } / ** * Traverse todos los archivos * * * @param Basenames * @param carpeta * @param path * / private void getAllFile (list <string> basenames, carpeta de archivos, string path) {if (carpeta.isDirectory ()) {for (file file.listFiles ()) {this.getallfile (basenames, archivo, archivo, archivo, archivo, file, plieve. File.separator); }} else {String i18Name = this.geti18FileName (PATH + carpeta.getName ()); if (! Basenames.contains (i18name)) {BaseNames.Add (i18name); }}} / ** * Convierta los nombres de archivo normales en nombres de archivos internacionales * * @param FileName * @return * / private String geti18FileName (string fileName) {filename = filename.replace (". Propiedades", ""); for (int i = 0; i <2; i ++) {int index = filename.lastIndexOf ("_"); if (index! = -1) {filename = filename.substring (0, index); }} return Nombre de archivo; }}Explique varios métodos a su vez.
@PostConstruct en init() , que llamará automáticamente init() después de instanciar la clase MessagerSourceExtension. Este método obtiene todos los archivos de internacionalización en baseFolder y los establece en basenameSet . Y establezca un ParentMessageSource , que llamará a los mensajes de los padres para encontrar la información internacional cuando no se pueda encontrar la información internacional.getAllBaseNames() obtiene la ruta a baseFolder y luego llama getAllFile() para obtener los nombres de archivos de todos los archivos internacionales en el directorio.getAllFile() atraviesa el directorio, si se trata de una carpeta, continúa atravesando, si es un archivo, llame getI18FileName() para convertir el nombre del archivo en un nombre de recurso internacional en el formato 'i18n/basename/'. Por lo tanto, simplemente, simplemente, después de que se instanciará MessageResourceExtension , el nombre del archivo de recursos en la carpeta 'i18n' se carga en Basenames . Ahora veamos el efecto.
Primero, agregamos un spring.messages.baseFolder=i18n al archivo Application.Properties, que asignará el valor 'i18n' a baseFolder en MessageResourceExtension .
Después de comenzar, vi que la información de inicio se imprimió en la consola, lo que indica que el método init () anotado por @PostConstruct se ha ejecutado.
Luego creamos dos conjuntos de archivos de información internacional: 'Dashboard' y 'Merchant', que solo tiene una información internacional: 'Dashboard.hello' y 'Merchant.hello' respectivamente.
Luego modifique el archivo Hello.html y luego visite la página de Hello.
... <body> <h1> Página de internacionalización! </h1> <p th: text = "#{hola}"> </p> <p th: text = "#{comerciante.hello}"> </p> <p th: text = "#{Dashboard.hello}"> </p> </body> ... ...Puede ver que la información de internacionalización en 'Mensaje', 'Dashboard' y 'Comerciante' se carga en la página web, lo que indica que hemos cargado con éxito el archivo en la carpeta 'I18N' a la vez.
Archivos que muestran información internacional en la página front-end de la configuración de fondo
En la sección anterior, cargamos con éxito múltiples archivos de internacionalización y mostramos su información de internacionalización. Pero la información de internacionalización en 'Dashboard.Properties' es 'Dashboard.hello' y 'Merchant.Properties' es 'Merchant.hello'. Por lo tanto, sería muy problemático escribir un prefijo para cada uno. Ahora solo quiero escribir 'Hola' en los archivos de internacionalización del 'Dashboard' y 'Merchant', pero se muestra la información de internacionalización en 'Dashboard' o 'Comerciante'.
Reescribir el método resolveCodeWithoutArguments en MessageResourceExtension (reescribir resolveCode si es necesario formatear por el carácter).
@Component ("Messagesource") Public Class MessageReurceExtension extiende ResourceBundLemesSageSource {... public static String i18n_attribute = "i18n_attribute"; @Override Cadena protegida resolvecodewithouTarguments (código de cadena, localidad local) {// Obtenga el nombre de archivo internacional especificado establecido en la solicitud ServletRequestatTributes attr = (ServLetRequestTributes) requestContexTholder.CurrentRequestatTributes (); Final String i18File = (String) attr.getAttribute (i18n_attribute, requestattributes.scope_request); if (! StringUtils.isEmpty (i18File)) {// Obtenga el nombre de archivo internacional coincidente en la cadena de BaseNameset BaseName = getBaseameset (). Stream () .Filter (nombre -> StringUtils.endswInignRoCase (name, i18File)) .FindFirst (). orelse (null); if (! StringUtils.isEmpty (BaseName)) {// Obtenga el archivo internacional especificado recursos recursos bundle = getResourceBundle (Basename, loce); if (bundle! = null) {return getStringOnnull (paquete, código); }}} // Si no hay un campo de internacionalización en la carpeta I18 especificada, devolver nulo buscará retorno nulo en parentMessageSource; } ...} En el método resolveCodeWithoutArguments que reescribimos, obtenga 'i18n_attribute' de httpservletRequest (hablaremos sobre dónde establecer esto) correspondiente al nombre de archivo internacional que queremos mostrar. Luego buscamos el archivo en BasenameSet , luego obtenemos el recurso a través de getResourceBundle y finalmente getStringOrNull para obtener la información internacional correspondiente.
Ahora agreguemos dos métodos a nuestro HelloController .
@ControllerPublic Class Hellocontroller {@getMapping ("/Hello") Public String Index (httpservletRequest request) {request.setTribute (MessageSourceExtension.i18n_attribute, "Hello"); return "System/Hello"; } @Getmapping ("/panel de control") Public String Dashboard (solicitud httpservletRequest) {request.setAttribute (MessageSourceExtension.i18n_attribute, "Dashboard"); regresar "Panel de control"; } @Getmapping ("/comerciante") Merchant de cadena pública (solicitud httpservletRequest) {request.setAttribute (MessageSourceExtension.i18n_attribute, "Merchant"); devolver "comerciante"; }} Vea que establecemos un 'i18n_attribute' correspondiente en cada método, que establecerá el archivo de internacionalización correspondiente en cada solicitud y luego lo obtendrá en MessageResourceExtension .
En este momento, observamos nuestro archivo de internacionalización y podemos ver que todas las palabras clave son 'hola', pero la información es diferente.
Al mismo tiempo, dos nuevos archivos HTML son 'Dashboard.html' y 'Merchant.html', que solo tiene una información internacional para 'Hola' y un título para distinguir.
<!-esto es hello.html-> <body> <h1> página de internacionalización! </h1> <p th: text = "#{hello}"> </p> </body> <!-Esto es tablboard.html-> <body> <h1> Página de internacionalización (tablero)! </h1> <p th: text = "#{hello}"> </p> </body> <!-Esto es comerciante.html-> <body> <h1> Página de internacionalización (comerciante)! </h1> <p th: text = "#{hello}"> </p> </body>En este momento, comencemos el proyecto y echemos un vistazo.
Puede ver que aunque la palabra de internacionalización en cada página es 'hola', mostramos la información que queremos mostrar en la página correspondiente.
Use interceptores y anotaciones para configurar automáticamente archivos que muestran información internacional en la página front-end
Aunque se puede especificar la información de internacionalización correspondiente, es demasiado problemático establecer el archivo de internacionalización en el HttpServletRequest en cada controlador, por lo que ahora implementamos un juicio automático para mostrar el archivo correspondiente.
Primero creamos una anotación, que se puede colocar en una clase o un método.
@Target ({elementtype.type, elementtype.method}) @retention (retentionPolicy.runtime) public @Interface i18n { / *** Nombre de archivo internacionalizado* / String Value ();} Luego ponemos la anotación I18n creada en el método del controlador en este momento. Para mostrar su efecto, creamos un ShopController y UserController , y también creamos los correspondientes archivos internacionales 'Shop' y 'User', y el contenido también es un 'Hola'.
@ControllerPublic Class HELOCONTROLLER {@getMapping ("/Hello") public String index () {return "System/Hello"; } @I18n ("Dashboard") @getMapping ("/Dashboard") public String Dashboard () {return "Dashboard"; } @I18n ("comerciante") @getmapping ("/comerciante") public string comerciante () {return "comerciante"; }} @I18n ("shop") @controlerpublic class shopController {@getmapping ("shop") public string shop () {return "shop"; }} @ControllerPublic Class UserController {@getMapping ("user") public String user () {return "User"; }} Colocamos I18n bajo dashboard y los métodos merchant bajo HelloController , y en ShopController , respectivamente. Y la declaración que establece 'i18n_attribute' bajo el dashboard original y los métodos merchant se elimina.
Todos los preparativos se realizan, ahora vea cómo especificar automáticamente archivos de internacionalización en función de estas anotaciones.
public class MessageSourceInterceptor implementa HandlerInterceptor {@Override public void postthandle (httpservletRequest req, httpservletResponse Rep, Handler de objetos, modelandView modelandView) {// establece la ruta i18 en el método si (nulL! = req.getattribute (messageReSourceExtension.i18n) } Handlermethod Method = (Handlermethod) Handler; // anotación en el método i18 i18n i18nmethod = método.getMethodannotation (i18n.class); if (null! = i18nmethod) {req.setAttribute (MessageSourceExtension.i18n_attribute, i18nmethod.value ()); devolver; } // anotación en el controlador i18n i18ncontroller = método.getBeanType (). getAnnotation (i18n.class); if (null! = i18ncontroller) {req.setAttribute (MessageSourceExtension.i18n_attribute, i18ncontroller.value ()); devolver; } // establecer i18 string controlador = método.getBeanType (). GetName (); int index = controler.lastIndexOf ("."); if (index! = -1) {controler = controlador.substring (index + 1, controler.length ()); } index = controler.toupperCase (). indexOf ("controlador"); if (index! = -1) {controler = controlador.substring (index + 1, controler.length ()); } index = controler.toupperCase (). indexOf ("controlador"); if (index! = -1) {controler = controler.substring (0, index); } req.SetAttribute (MessagerSourceExtension.i18n_attribute, controlador); } @Override public boolean prehandle (httpservletrequest req, httpservletreponse represente, manejador de objetos) {// Al saltar a este método, primero borre la información de internacionalización en la solicitud req.removeattribute (MessageresourceExtension.i18n_attribute); devolver verdadero; }}Permítanme explicar brevemente este interceptor.
Primero, si ya hay 'i18n_attribute' en la solicitud, significa que la configuración se especifica en el método del controlador y ya no se hace un juicio.
Luego determine si hay una anotación I18n en el método para ingresar al interceptor. Si lo hay, establezca 'i18n_attribute' en la solicitud y salga del interceptor. Si no hay, continúa.
Luego determine si hay una anotación I18n en la clase que ingresó a la intersección. Si lo hay, establezca 'i18n_attribute' en la solicitud y salga del interceptor. Si no hay, continúa.
Finalmente, si no hay anotación I18n en el método y la clase, podemos establecer automáticamente el archivo de internacionalización especificado de acuerdo con el nombre del controlador. Por ejemplo, 'UserController', buscaremos el archivo de internacionalización 'Usuario'.
Ahora volvamos a ejecutarlo para ver el efecto y ver el contenido en su información internacional correspondiente que se muestra en cada enlace.
por fin
Acabo de completar las funciones básicas de toda nuestra mejora de la internacionalización. Finalmente, ordené todo el código y Bootstrap4 integrado para mostrar el efecto de implementación de la función.
Para un código detallado, consulte el código de Spring-Boot-i18n-Pro en GitHub
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.