ברוך הבא למדריך לבינה לקיץ (i18n). כל אפליקציה web עם משתמשים מכל העולם, בינה לקיץ (i18n) או לוקליזציה (L10n) הם חשובים מאוד לאינטראקציה משתמש טובה יותר. רוב הסביבות לפיתוח אפליקציות web מספקות דרכים קלות ללוקליזציה של האפליקציה בהתבסס על הגדרות האזרחות של המשתמש. Spring גם עוקבת אחרי התבנית זו ומספקת תמיכה נרחבת לבינה לקיץ (i18n) דרך שימוש ב-interceptors של Spring, ב-resolvers של השפה וב-bundles של המשאבים לשפות שונות. ישנם מאמרים קודמים בנושא i18n ב-Java.
בינה לקיץ ב-Spring
בואו ניצור פרויקט פשוט של Spring MVC בו נשתמש בפרמטר בקשה כדי לקבל את לוקל המשתמש ובהתאם לכך נקבע את ערכי התוויות בדף התגובה מחבילות משאבים ספציפיות ללוקל. ניצור פרויקט Spring MVC ב-Spring Tool Suite כדי לקבל את קוד הבסיס ליישום שלנו. אם אינך מכיר את Spring Tool Suite או פרויקטי Spring MVC, קרא את דוגמת Spring MVC. הפרויקט הסופי שלנו עם שינויי הלוקליזציה נראה כמו בתמונה למטה. נבחן את כל חלקי היישום אחד לאחד.
הגדרת Maven של Spring i18n
ה-pom.xml שלנו של Spring MVC נראה כמו בתמונה למטה.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.journaldev</groupId>
<artifactId>spring</artifactId>
<name>Springi18nExample</name>
<packaging>war</packaging>
<version>1.0.0-BUILD-SNAPSHOT</version>
<properties>
<java-version>1.6</java-version>
<org.springframework-version>4.0.2.RELEASE</org.springframework-version>
<org.aspectj-version>1.7.4</org.aspectj-version>
<org.slf4j-version>1.7.5</org.slf4j-version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework-version}</version>
<exclusions>
<!-- Exclude Commons Logging in favor of SLF4j -->
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- AspectJ -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${org.aspectj-version}</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${org.slf4j-version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.15</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mail</artifactId>
</exclusion>
<exclusion>
<groupId>javax.jms</groupId>
<artifactId>jms</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jdmk</groupId>
<artifactId>jmxtools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun.jmx</groupId>
<artifactId>jmxri</artifactId>
</exclusion>
</exclusions>
<scope>runtime</scope>
</dependency>
<!-- @Inject -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.7</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<additionalProjectnatures>
<projectnature>org.springframework.ide.eclipse.core.springnature</projectnature>
</additionalProjectnatures>
<additionalBuildcommands>
<buildcommand>org.springframework.ide.eclipse.core.springbuilder</buildcommand>
</additionalBuildcommands>
<downloadSources>true</downloadSources>
<downloadJavadocs>true</downloadJavadocs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.5.1</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgument>-Xlint:all</compilerArgument>
<showWarnings>true</showWarnings>
<showDeprecation>true</showDeprecation>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<configuration>
<mainClass>org.test.int1.Main</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
רוב הקוד נוצר באופן אוטומטי על ידי STS, חוץ מזה שעדכנתי את גרסת ה-Spring להשתמש בגרסה האחרונה 4.0.2.RELEASE. ניתן להסיר תלויות או לעדכן את הגרסאות של תלויות אחרות גם, אך השארתי אותם כפי שהם לצורך פשטות.
חבילת משאבי Spring
עבור פשטות, נניח שהיישום שלנו תומך רק בשני locales – en ו־fr. אם לא צוין locale של משתמש, נשתמש ב־english כ־locale ברירת המחדל. ניצור חבילות משאב Spring עבור שני הlocales אלו שישמשו בעמוד JSP. קוד messages_en.properties:
label.title=Login Page
label.firstName=First Name
label.lastName=Last Name
label.submit=Login
קוד messages_fr.properties:
label.title=Connectez-vous page
label.firstName=Pr\u00E9nom
label.lastName=Nom
label.submit=Connexion
שים לב שאני משתמש ב־unicode עבור תווים מיוחדים בחבילות משאב של הlocale הצרפתי, כך שיתורגם בצורה תקינה ב-HTML שמועבר לבקשות הלקוח. נקודה חשובה נוספת לשים לב אליה היא ששתי חבילות המשאב נמצאות במסלול של היישום ושמסננות כמו "messages_{locale}.properties". נראה למה אלו חשובות מאוחר יותר.
מחלקת ניהול התרגום של Spring
מחלקת הבקר שלנו היא פשוטה מאוד, היא רק רושמת את locale של המשתמש ומחזירה את העמוד home.jsp כתגובה.
package com.journaldev.spring;
import java.util.Locale;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* Handles requests for the application home page.
*/
@Controller
public class HomeController {
private static final Logger logger = LoggerFactory.getLogger(HomeController.class);
/**
* Simply selects the home view to render by returning its name.
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
logger.info("Welcome home! The client locale is {}.", locale);
return "home";
}
}
עמוד JSP של Spring i18n
קוד הדף home.jsp שלנו נראה כמו שמופיע למטה.
<%@taglib uri="https://www.springframework.org/tags" prefix="spring"%>
<%@ page session="false"%>
<html>
<head>
<title><spring:message code="label.title" /></title>
</head>
<body>
<form method="post" action="login">
<table>
<tr>
<td><label> <strong><spring:message
code="label.firstName" /></strong>
</label></td>
<td><input name="firstName" /></td>
</tr>
<tr>
<td><label> <strong><spring:message
code="label.lastName" /></strong>
</label></td>
<td><input name="lastName" /></td>
</tr>
<tr>
<spring:message code="label.submit" var="labelSubmit"></spring:message>
<td colspan="2"><input type="submit" value="${labelSubmit}" /></td>
</tr>
</table>
</form>
</body>
</html>
החלק היחיד ששווה לציין הוא השימוש ב-spring:message כדי לאחזר את ההודעה עם הקוד הנתון. ודא שספריות התגי Spring מוגדרות באמצעות taglib jsp directive. Spring דואגת לטעון את אוסף ההודעות המתאים ולהפוך אותו לזמין לעמודי ה-JSP לשימוש.
בינלאומיות ב-Spring i18n – קובץ התצורה של ה-Bean
קובץ התצורה של Bean ב-Spring הוא המקום בו כל הקסם קורה. זהו יופיו של Spring framework מכיוון שהוא עוזר לנו להתמקד יותר בלוגיקת העסקית ופחות בכתיבת משימות טריוויאליות. בואו נראה איך נראה קובץ התצורה של ה-Bean שלנו ונביט בכל אחד מה-Beans אחד אחד. קוד servlet-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="https://www.springframework.org/schema/mvc"
xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xmlns:beans="https://www.springframework.org/schema/beans"
xmlns:context="https://www.springframework.org/schema/context"
xsi:schemaLocation="https://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
https://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!-- DispatcherServlet Context: defines this servlet's request-processing
infrastructure -->
<!-- Enables the Spring MVC @Controller programming model -->
<annotation-driven />
<!-- Handles HTTP GET requests for /resources/** by efficiently serving
up static resources in the ${webappRoot}/resources directory -->
<resources mapping="/resources/**" location="/resources/" />
<!-- Resolves views selected for rendering by @Controllers to .jsp resources
in the /WEB-INF/views directory -->
<beans:bean
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<beans:property name="prefix" value="/WEB-INF/views/" />
<beans:property name="suffix" value=".jsp" />
</beans:bean>
<beans:bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<beans:property name="basename" value="classpath:messages" />
<beans:property name="defaultEncoding" value="UTF-8" />
</beans:bean>
<beans:bean id="localeResolver"
class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
<beans:property name="defaultLocale" value="en" />
<beans:property name="cookieName" value="myAppLocaleCookie"></beans:property>
<beans:property name="cookieMaxAge" value="3600"></beans:property>
</beans:bean>
<interceptors>
<beans:bean
class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<beans:property name="paramName" value="locale" />
</beans:bean>
</interceptors>
<context:component-scan base-package="com.journaldev.spring" />
</beans:beans>
-
התגית annotation-driven מאפשרת את דגם התכנות של הקונטרולר, בלעדיה Spring לא תזהה את HomeController שלנו ככיתה לטיפול בבקשות של הלקוח.
-
הגדרת סריקה של מרכיבי התצורה מספקת את החבילה שבה תחפש Spring את הרכיבים המסומנים ותרשום אותם באופן אוטומטי כ-Bean של Spring.
-
messageSource הבון מוגדר כך שיופעל i18n עבור היישום שלנו. בעליות ה- basename משמשים לספק את מיקום אוספי המשאבים.
classpath:messages
אומר שאוספי המשאבים נמצאים בנתיב המחלקה ועוקבים אחר תבנית השם כמוmessages_{locale}.properties
. מאפיין defaultEncoding משמש להגדיר את הקידוד המשמש עבור ההודעות. -
מתאים לשדה
org.springframework.web.servlet.i18n.CookieLocaleResolver
, המוגדר כסוג של פולטה מיקום מקומית, משמש להגדרת עוגייה בבקשת הלקוח כך שבקשות נוספות יכולות לזהות בקלות את מקום המגורים של המשתמש. לדוגמה, נוכל לבקש מהמשתמש לבחור את מקום המגורים בעת הפעלת היישום האינטרנטי לראשונה, ועם שימוש בעוגייה, נוכל לזהות את מקום המגורים של המשתמש ולשלוח באופן אוטומטי תגובה ספציפית למקום המגורים. נוכל גם לציין את מקום המגורים המוגדר כברירת מחדל, שם העוגייה וגיל העוגייה המרבי לפני שתפוג ותימחק על ידי דפדפן הלקוח. אם היישום שלך מתחזק את ההפעלות של המשתמש, אז תוכל גם להשתמש ב-org.springframework.web.servlet.i18n.SessionLocaleResolver
כפולטת מיקום מקומית כדי להשתמש במאפיין מקום המגורים בהפעלת המשתמש.<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver"> <property name="defaultLocale" value="en" /> </bean>
אם לא נרשום אף "localeResolver", AcceptHeaderLocaleResolver יופעל כברירת מחדל, אשר יפתור את מקום המגורים של המשתמש על ידי בדיקת הכותרת
accept-language
בבקשת HTTP של הלקוח. -
org.springframework.web.servlet.i18n.LocaleChangeInterceptor
אינטרספטור מוגדר להפריט בקשת המשתמש ולזהות את שפת המשתמש. שם הפרמטר ניתן להגדיר, ואנו משתמשים בשם פרמטר בקשה עבור השפה כ-"locale". בלעדיו, לא יהיה לנו אפשרות לשנות את שפת המשתמש ולשלוח תגובה המבוססת על הגדרות השפה החדשות של המשתמש. יש לכלול אותו כחלק מהרכיב interceptors, אחרת Spring לא יגדיר אותו כאינטרספטור.
אם אתה שואל על הקונפיגורציה שאומרת לספרינג פריימוורק לטעון את הקונפיגורציות של ההקשר שלנו, היא קיימת בתיאור ההפלגה של היישום MVC שלנו.
<servlet>
<servlet-name>appServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring/appServlet/servlet-context.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>appServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
אנחנו יכולים לשנות את המיקום או את שם קובץ ההקשר על ידי שינוי התצורה של web.xml. האפליקציה שלנו של Spring i18n מוכנה, רק תפריט אותה בכל מנהל Servlet. כללית, אני יצוא אותה כקובץ WAR בתיקיית webapps של שרת האינטרנט הסטנדרטי של Tomcat. הנה תמונות מסך של דף הבית של האפליקציה שלנו עם מיקומים שונים. דף הבית ברירת מחדל (מיקום en): מעבר מיקום כפרמטר (מיקום fr):
בקשות נוספות בלי מיקום:
כפי שניתן לראות בתמונה לעיל, אנו לא מעבירים מידע על מיקום בבקשת הלקוח, אך עדיין האפליקציה שלנו מזהה את מיקום המשתמש. כבר ניחשת כעת שזה בגלל ה- CookieLocaleResolver bean שהגדרנו בקובץ תצורת פולי האביב שלנו. בכל מקרה, תוכל לבדוק את נתוני העוגיות של הדפדפן שלך כדי לוודא זאת. אני משתמש ב-Chrome והתמונה למטה מציגה את נתוני העוגיות המאוחסנים על ידי האפליקציה.
שים לב שזמן תפוגת העוגייה הוא שעה אחת, כלומר 3600 שניות כפי שהוגדר על ידי נכס cookieMaxAge. אם תבדוק את יומני השרת, תראה שהמיקום מתעדף.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is en.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is fr.
INFO : com.journaldev.spring.HomeController - Welcome home! The client locale is fr.
זה הכל עבור דוגמת היישום Spring i18n, הורידו את פרויקט הדוגמה מהקישור למטה ושחקו איתו כדי ללמוד עוד.