تدويل Spring MVC (i18n) وتعريب (L10n) – مثال

مرحبًا بكم في دليل التدويل العالمي لربيع. أي تطبيق ويب يستخدمه مستخدمون من جميع أنحاء العالم، التدويل (i18n) أو التعريب (L10n) مهم جدًا لتحسين تفاعل المستخدمين. معظم إطارات تطبيقات الويب توفر طرقًا سهلة لتعريب التطبيق بناءً على إعدادات لغة المستخدم. يتبع ربيع نفس النمط ويوفر دعمًا شاملاً للتدويل العالمي (i18n) من خلال استخدام معاملات ربيع، حلول تحديد الموقع، ومجموعات الموارد للغات المختلفة. بعض المقالات السابقة حول التدويل في جافا.

تدويل ربيع (i18n)

لنقم بإنشاء مشروع بسيط في 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

للبساطة، دعونا نفترض أن تطبيقنا يدعم فقط لغتين – en و fr. إذا لم يتم تحديد لغة المستخدم، سنستخدم الإنجليزية كلغة افتراضية. دعونا نقوم بإنشاء حزم الموارد في Spring لهاتين اللغتين والتي ستُستخدم في صفحة 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 للأحرف الخاصة في حزم الموارد باللغة الفرنسية، حتى يتم تفسيرها بشكل صحيح في HTML المرسلة إلى طلبات العميل. نقطة هامة أخرى هي أن حزم الموارد لهما نفس النمط في فهرس التطبيق واسمهما يتبع النمط “messages_{locale}.properties”. سنرى لاحقًا لماذا تكون هذه مهمة.

فئة تحكم Spring i18n

فئتنا تحكم بسيطة جداً، حيث تسجل لغة المستخدم وتعيد صفحة 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 Internationalization i18n – Bean Configuration File

ملف تكوين Spring Bean هو المكان الذي يحدث فيه كل السحر. هذه هي جمالية إطار الربيع حيث يساعدنا على التركيز أكثر على منطق الأعمال بدلاً من البرمجة للمهام التافهة. دعونا نرى كيف يبدو ملف تكوين بين Spring Bean وسننظر في كل الفولفولات واحدة تلو الأخرى. رمز ملف تكوين 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>
  1. annotation-driven يمكن أن يمكن نموذج التحكم في الواجهة، بدونه لن يتعرف Spring على HomeController كمعالج لطلبات العملاء.

  2. context:component-scan يوفر الحزمة التي ستبحث فيها Spring عن المكونات المحددة تلقائيًا وتسجلها تلقائيًا كفولفولات Spring.

  3. messageSource يتم تكوين الفاصل لتمكين i18n لتطبيقنا. يتم استخدام خاصية basename لتوفير موقع حزم الموارد. classpath: messages يعني أن حزم الموارد موجودة في مسار الفئة وتتبع نمط الاسم كـ messages_{locale}.properties. تُستخدم خاصية defaultEncoding لتعريف الترميز المستخدم للرسائل.

  4. يُستخدم جاذب الـorg.springframework.web.servlet.i18n.CookieLocaleResolver بنوع localeResolver لتعيين ملف تعريف (cookie) في طلب العميل بحيث يمكن للطلبات اللاحقة التعرف بسهولة على لغة المستخدم. على سبيل المثال، يمكننا أن نطلب من المستخدم اختيار اللغة عندما يطلق تطبيق الويب لأول مرة، وباستخدام ملف التعريف، يمكننا التعرف تلقائيًا على لغة المستخدم وإرسال استجابة خاصة باللغة. يمكننا أيضًا تحديد اللغة الافتراضية، واسم الملف التعريفي، والفترة الزمنية القصوى للملف قبل أن يتم انتهاؤه وحذفه بواسطة متصفح العميل. إذا كان التطبيق الخاص بك يحتفظ بجلسات المستخدم، يمكنك أيضًا استخدام org.springframework.web.servlet.i18n.SessionLocaleResolver كـ localeResolver لاستخدام سمة اللغة في جلسة المستخدم. التكوين مشابه لـ CookieLocaleResolver.

    <bean id="localeResolver"
    	class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    	<property name="defaultLocale" value="en" />
    </bean>
    

    إذا لم نقم بتسجيل أي “localeResolver”، سيتم استخدام AcceptHeaderLocaleResolver تلقائيًا، الذي يقوم بحل لغة المستخدم عن طريق فحص رأس accept-language في طلب HTTP الخاص بالعميل.

  5. org.springframework.web.servlet.i18n.LocaleChangeInterceptor مكون التصفية مكون لالتقاط طلب المستخدم وتحديد لغته. اسم المعلمة قابل للتكوين ونحن نستخدم اسم معلمة الطلب للغة باسم “locale”. بدون هذا المكون، لن نتمكن من تغيير لغة المستخدم وإرسال الاستجابة استنادًا إلى إعدادات اللغة الجديدة للمستخدم. يجب أن يكون جزءًا من عناصر التصفية وإلا لن يقوم Spring بتكوينه كمكون تصفية.

إذا كنت تتساءل عن التكوين الذي يخبر إطار 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 جاهز، قم بنشره في أي حاوية خدمة. عادةً ما أقوم بتصديره كملف WAR في دليل تطبيقات خادم الويب tomcat المستقل. فيما يلي لقطات شاشة لصفحة التطبيق الرئيسية مع لغات مختلفة. الصفحة الرئيسية الافتراضية (لغة en): تمرير اللغة كمعلمة (لغة fr): طلبات إضافية بدون لغة: كما يمكنك رؤية في الصورة أعلاه، لا نقوم بتمرير معلومات اللغة في طلب العميل ومع ذلك، يتعرف تطبيقنا على لغة المستخدم. لقد تخمنت الآن أن هذا بسبب حاصل البنفسج اللغوي (CookieLocaleResolver) الذي قمنا بتكوينه في ملف تكوين الفاصل الزمني لفاصل الربيع الخاص بنا. ومع ذلك، يمكنك التحقق من بيانات ملفات تعريف الارتباط في متصفحك للتأكيد. أنا استخدم متصفح 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، قم بتنزيل مشروع المثال من الرابط أدناه وتفاعل معه لتتعلم المزيد.

تنزيل مشروع Spring i18n

Source:
https://www.digitalocean.com/community/tutorials/spring-mvc-internationalization-i18n-and-localization-l10n-example