مقدمة
قامت الشركة بنقل المشروع من Struts2 إلى SpringMVC. نظرًا لأن أعمال الشركة هي الخدمات الخارجية ، فإن الطلب على الوظائف الدولية مرتفع للغاية. تعد وظيفة التدويل لـ Struts2 أكثر مثالية من SpringMVC ، ولكن الميزة الكبيرة في الربيع هي أنها قابلة للتخصيص وتخصيصها ، لذلك تتم إضافة وظيفة التدويل عندما يتم زرع مشروع الشركة إلى SpringMVC. لقد قمت بتجميع السجلات وتحسينها هنا.
الوظائف الرئيسية المنفذة في هذه المقالة:
قم بتحميل ملفات دولية متعددة مباشرة من مجلد. تعرض الصفحة الأمامية لضبط الخلفية المعلومات الدولية. يتم تعيين الملف الذي يعرض المعلومات الدولية تلقائيًا باستخدام المقاطعات والتعليقات التوضيحية. يعرض الملف الذي يعرض المعلومات الدولية على الصفحة الأمامية المعلومات الدولية.
ملاحظة: لا تقدم هذه المقالة بالتفصيل كيفية تكوين التدويل ، المحلل الإقليمي ، إلخ.
ينجز
تهيئة المشاريع الدولية
أولاً قم بإنشاء مشروع أساسي لـ Spring-Boot+Thymeleaf+معلومات التدويل (message.properties). إذا كنت في حاجة إليها ، يمكنك تنزيله من github الخاص بي.
نظرة قصيرة على الدليل وملفات المشروع
من بينها ، i18napplication.java يضع CookieLocaleResolver ، والذي يستخدم ملفات تعريف الارتباط للتحكم في اللغات الدولية. قم أيضًا بإعداد اعتراض LocaleChangeInterceptor لاعتراض التغييرات في اللغات الدولية.
@springbootapplication@configurationpublic class i18napplication {public static void main (string [] args) {springapplication.run (i18napplication.class ، args) ؛ } bean public localeresolver localeresolver () {cookielocaleresolver slr = new Cookielocaleresolver () ؛ SLR.SetCookieMaxage (3600) ؛ SLR.SetCookiename ("لغة") ؛ // اضبط اسم ملف تعريف الارتباط المخزن إلى اللغة المرجع SLR ؛ } bean public webmvcconfigurer webmvcconfigurer () {إرجاع webmvcconfigurer () {// interceptor @override public void additpetors (interceptorregistry registry) {registry.addinterceptor (new localechangeinterceptor () }} ؛ }}دعونا نلقي نظرة على ما هو مكتوب في Hello.html:
<! doctype html> <html xmlns = "http://www.w3.org/1999/xhtml" TH: text = "#{i18n_page}"> </h1> <h3 th: text = "#{hello}"> </h3> </body> </html> الآن ابدأ المشروع وزيارة http://localhost:9090/hello (قمت بتعيين المنفذ على 9090 في application.properties).
نظرًا لأن اللغة الافتراضية للمتصفح صينية ، فسيتم البحث في الرسائل في الرسائل _zh_cn.properties ، وإذا لم يكن الأمر كذلك ، فسيبحث في الرسائل.
ثم ندخل http://localhost:9090/hello?locale=en_US في المتصفح وسيتم قطع اللغة إلى اللغة الإنجليزية. وبالمثل ، إذا تم ضبط المعلمة بعد عنوان URL على locale=zh_CH ، فسيتم قطع اللغة إلى الصينية.
قم بتحميل ملفات دولية متعددة مباشرة من مجلد
في صفحة Hello.html الخاصة بنا ، لا يوجد سوى معلومات دولية "I18N_PAGE" و "Hello". ومع ذلك ، في المشاريع الفعلية ، لن يكون هناك بالتأكيد صغير مثل بعض المعلومات الدولية ، وعادة ما تكون مئات منها. ثم يجب ألا نضع الكثير من المعلومات الدولية في ملف مع messages.properties . ومع ذلك ، عندما يصبح المشروع أكبر ، ستصبح هذه الملفات الدولية أكثر وأكثر. في هذا الوقت ، من غير المريح تكوين هذا الملف واحد تلو الآخر في ملف application.properties . حتى الآن نقوم بتنفيذ وظيفة لتحميل جميع الملفات الدولية تلقائيًا في دليل الصياغة.
ورث ResourceBundleMessagesource
قم بإنشاء فصل ضمن المشروع لروث ResourceBundleMessageSource أو ReloadableResourceBundleMessageSource واسمه MessageResourceExtension . وحقنها في الفاصوليا messageSource ، حيث نرث ResourceBundleMessagesource.
component ("messagesource") الطبقة العامة messageresourceextension تمتد ResourceBundleMessagesource {} لاحظ أن اسم المكون الخاص بنا يجب أن يكون "MessageSource" ، لأنه عند تهيئة ApplicationContext ، سيتم البحث عن الفول مع الفاصوليا المسمى "Messagesource". هذه العملية في AbstractApplicationContext.java . لنلقي نظرة على رمز المصدر
/*** تهيئة الرسائل.*استخدم الوالدين إذا لم يتم تعريف أي منها في هذا السياق.*/void void initmessagesource () {configurableListableBeanfactory beanfactory = getBeanfactory () ؛ if (beanfactory.containslocalbean (message_source_bean_name)) {this.messagesource = beanfactory.getBean (message_source_bean_name ، messagesource.class) ؛ ...}} ... في هذه الطريقة لتهيئة MessageSource ، يبحث Beanfactory عن الفاصوليا التي تم حقنها باستخدام MESSAGE_SOURCE_BEAN_NAME(messageSource) . إذا لم يتم العثور عليها ، فسيبحث عن ما إذا كان هناك حبة تحمل الاسم في فئة الأم.
تنفيذ تحميل الملف
الآن يمكننا أن نبدأ MessageResourceExtension التي أنشأناها للتو
تتم كتابة طريقة تحميل الملف.
component ("Messagesource") الفئة العامة messageresourceExtense تمتد ResourceBundleMessagesource {private Final Static Logger = loggerfactory.getLogger (messageresourceextension.class) ؛ / *** دليل ملف التدويل المحدد*/ value (value = "$ {spring.messages.basefolder: i18n}") private string basefolder ؛ / *** ملف التدويل المحدد بواسطة Parent MessageSource*/ value (value = "$ {spring.messages.basename: message}") private string basename ؛ postconstruct public void init () {logger.info ("init messageresourceextension ...") ؛ if (! stringUtils.isempty (basefolder)) {try {this.setBasenames (getAllBasenames (basefolder)) ؛ } catch (ioException e) {logger.error (e.getMessage ()) ؛ }} // تعيين messagesourceource resourceBundleMessagesource Parent = جديد ResourceBundLemessagesource () ؛ parent.setBasename (الاسم الأساسي) ؛ this.setParentMessagesource (الوالد) ؛ } / ** * احصل على جميع أسماء الملفات الدولية في المجلد * * param voldername اسم ملف * regurn * throws ioException * / private string [] getAllBasenames (foldername) remows ioException {Resource Resource = new ClassPathResource (folderName) ؛ ملف الملف = Resource.getFile () ؛ قائمة <Tring> basenames = new ArrayList <> () ؛ if (file.exists () && file.isdirectory ()) {this.getAllFile (basenames ، file ، "") ؛ } آخر {logger.error ("لا توجد قاعدة basefile المحددة أو ليست مجلد") ؛ } إرجاع basenames.toarray (سلسلة جديدة [basenames.size ()]) ؛ } / ** * اجتياز جميع الملفات * * param basenames * param folder * param path * / private void getAllfile (list <string> basenames ، file folder ، مسار السلسلة) {if (folder.isdirectory ()) {file file: folder.listfiles () }} آخر {String i18name = this.geti18filename (path + folder.getName ()) ؛ if (! basenames.contains (i18name)) {basenames.add (i18name) ؛ }}} / ** * تحويل أسماء الملفات العادية إلى أسماء الملفات الدولية * * param filename * @regurn * / private string geti18filename (اسم ملف السلسلة) {filename = fileName.replace (". الخصائص" ، "") ؛ لـ (int i = 0 ؛ i <2 ؛ i ++) {int index = filename.lastindexof ("_") ؛ if (index! = -1) {filename = filename.subString (0 ، index) ؛ }} اسم الملف الإرجاع ؛ }}اشرح عدة طرق بدورها.
@PostConstruct على طريقة init() ، والتي ستستدعي تلقائيًا طريقة init() بعد إنشاء فئة messageresourceextension. تحصل هذه الطريقة على جميع ملفات التدويل في دليل baseFolder وتعيينها على basenameSet . وتعيين ParentMessageSource ، والتي ستدعو الرسائل الوالدية للعثور على المعلومات الدولية عندما لا يمكن العثور على المعلومات الدولية.getAllBaseNames() على المسار إلى baseFolder ، ثم تستدعي طريقة getAllFile() للحصول على أسماء الملفات لجميع الملفات الدولية في الدليل.getAllFile() الدليل ، إذا كان مجلد ، يستمر في اجتيازه ، إذا كان ملفًا ، اتصل بـ getI18FileName() لتحويل اسم الملف إلى اسم مورد دولي بتنسيق 'I18N/basename/'. لذا ، ببساطة ، بعد MessageResourceExtension ، يتم تحميل اسم ملف المورد تحت مجلد "I18N" إلى Basenames . الآن دعونا نرى التأثير.
أولاً ، نضيف spring.messages.baseFolder=i18n إلى ملف Application.properties ، والذي سيعين القيمة "i18n" إلى baseFolder في MessageResourceExtension .
بعد البدء ، رأيت أنه تمت طباعة معلومات init في وحدة التحكم ، مما يشير إلى أنه تم تنفيذ طريقة init () المشروحة بواسطة @PostConstruct .
ثم نقوم بإنشاء مجموعتين من ملفات المعلومات الدولية: "لوحة القيادة" و "Merchant" ، والتي لديها معلومات دولية واحدة فقط: "Dashboard.hello" و "Merchant.hello" على التوالي.
ثم قم بتعديل ملف Hello.html ثم تفضل بزيارة صفحة Hello.
... <Undy> <h1> صفحة التدويل! </h1> <p th: text = "#{hello}"> </p> <p th: text = "#{merchant.hello}"> </p> <p th: text = "#{dashboard.hello}"> </p> </body> ...يمكنك أن ترى أن معلومات التدويل في "Message" و "Dashboard" و "Merchant" يتم تحميلها في صفحة الويب ، مما يشير إلى أننا قمنا بتحميل الملف بنجاح ضمن مجلد "I18N" في وقت واحد.
الملفات التي تعرض المعلومات الدولية على الصفحة الأمامية من إعدادات الخلفية
في القسم السابق ، نجحنا في تحميل ملفات تدويل متعددة وعرضنا معلومات التدويل الخاصة بهم. لكن معلومات التدويل في "لوحة القيادة. لذلك سيكون من المزعج للغاية كتابة بادئة لكل منها. الآن أريد أن أكتب فقط "Hello" في ملفات التدويل لـ "لوحة القيادة" و "Merchant" ، ولكن يتم عرض معلومات التدويل في "لوحة القيادة" أو "Merchant".
إعادة كتابة طريقة resolveCodeWithoutArguments في MessageResourceExtension (إعادة كتابة resolveCode إذا كانت هناك حاجة لتنسيق الأحرف).
component ("messagesource") الفئة العامة messageresourceExtension تمتد ResourceBundLemessagesource {... السلسلة الثابتة العامة i18n_attribute = "i18n_attribute" ؛ OverRide String ServEcolveCodewithoutArguments (رمز السلسلة ، موضعية موضعية) {// احصل على اسم الملف الدولي المحدد المحدد في servletrequestattributes attrid = (servletRequestatTributes) requestContexthOlder.currentRentRequestAttRibutes () ؛ السلسلة النهائية i18file = (string) attr.getAttribute (i18n_attribute ، requestAtTributes.Scope_Request) ؛ if (! stringUtils.isempty (i18file)) {// احصل على اسم الملف الدولي المطابق في basenameet string basename = getBasenameset (). Stream () .filter (name -> StringUtils.endswithInsignoreCase (name ، i18file)) .findfirst (). if (! stringUtils.isempty (basename)) {// الحصول على ملف Resource ResourceBundle الدولي المحدد = getResourceBundle (basename ، locale) ؛ if (bundle! = null) {return getTringorNull (حزمة ، رمز) ؛ }}} // إذا لم يكن هناك حقل تدويل في مجلد I18 المحدد ، فسيبحث Return Null عن عودة NULL في ParentMessagesource ؛ } ...} في طريقة resolveCodeWithoutArguments التي أعدناها ، احصل على "i18n_attribute" من httpservletrequest (سيتحدث عن مكان تعيين هذا) المقابل لاسم الملف الدولي الذي نريد عرضه. ثم نبحث عن الملف في BasenameSet ، ثم نحصل على المورد من خلال getResourceBundle ، وأخيراً getStringOrNull للحصول على المعلومات الدولية المقابلة.
الآن دعنا نضيف طريقتين إلى HelloController .
controllerpublic class hellocontroller {getMapping ("/hello") فهرس السلسلة العامة (طلب httpservletrequest) {request.setAttribute (messageresourceextension.i18n_attribute ، "Hello") ؛ إرجاع "النظام/مرحبا" ؛ } getMapping ("/dashboard") سلسلة معلومات السلسلة العامة (طلب httpservletrequest) {request.setattribute (messageresourceextension.i18n_attribute ، "Dashboard") ؛ إرجاع "لوحة القيادة" ؛ } getMapping ("/Merchant") Merchant السلسلة العامة (طلب httpservletrequest) {request.setAttribute (messageresourceextension.i18n_attribute ، "Merchant") ؛ إرجاع "التاجر" ؛ }} راجع أننا قمنا بتعيين "i18n_attribute" المقابل في كل طريقة ، والتي ستعمل على تعيين ملف التدويل المقابل في كل طلب ثم الحصول عليه في MessageResourceExtension .
في هذا الوقت ، ننظر إلى ملف التدويل الخاص بنا ويمكننا أن نرى أن جميع الكلمات الرئيسية "مرحبًا" ، لكن المعلومات مختلفة.
في الوقت نفسه ، هما ملفان HTML جديدان هما "Dashboard.html" و "Merchant.html" ، والتي تحتوي فقط على معلومات دولية لـ "Hello" وعنوان التمييز.
<!-هذا هو hello.html-> <body> <h1> صفحة التدويل! </h1> <p th: text = "#{hello}"> </p> </body> <!-هذه هي لوحة dashboard.html-> <body> <h1> التدويل (لوحة القيادة)! </h1> <p th: text = "#{hello}"> </p> </body> <!-هذا هو merchant.html-> <body> <h1> صفحة تدويل (تاجر)! </h1> <p th: text = "#{hello}"> </p> </body>في هذا الوقت ، لنبدأ المشروع ونلقي نظرة.
يمكنك أن ترى أنه على الرغم من أن كلمة التدويل في كل صفحة هي "مرحبًا" ، إلا أننا نعرض المعلومات التي نريد عرضها على الصفحة المقابلة.
استخدم المقاطعات والتعليقات التوضيحية لإعداد الملفات تلقائيًا تعرض المعلومات الدولية على الصفحة الأمامية
على الرغم من أنه يمكن تحديد معلومات التدويل المقابلة ، إلا أنه من المزعج للغاية تعيين ملف التدويل في httpservletrequest في كل وحدة تحكم ، لذلك نحن الآن ننفذ الحكم التلقائي لعرض الملف المقابل.
أولاً نقوم بإنشاء تعليق توضيحي ، يمكن وضعه على فصل أو طريقة.
target ({elementType.type ، elementType.method}) @entry (enthypolicy.runtime) public interface i18n { / *** اسم الملف الدولي* / string value () ؛} ثم وضعنا التعليق التوضيحي I18n الذي تم إنشاؤه في طريقة وحدة التحكم الآن. من أجل عرض تأثيره ، نقوم بإنشاء ملفات ShopController و UserController ، وكذلك إنشاء ملفات "المتجر" و "المستخدم" المقابلة ، والمحتوى هو أيضًا "مرحبًا".
controllerpublic class hellocontroller {getMapping ("/hello") public string index () {return "system/hello" ؛ } @i18n ("Dashboard") getMapping ("/dashboard") السلسلة العامة لرسام Dashboard () {return "dashboard" ؛ } @i18n ("Merchant") getMapping ("/merchant") السلسلة العامة Merchant () {return "merchant" ؛ }} @i18n ("Shop") @controlpublic class ShopController {getMapping ("Shop") Public String Shop () {return "Shop" ؛ }} controllerpublic class UserController {getMapping ("user") public string user () {return "user" ؛ }} نضع التعليق التوضيحي I18n تحت dashboard merchant تحت HelloController ، وعلى فئة ShopController على التوالي. ويتم إزالة البيان الذي يضبط "i18n_attribute" تحت dashboard الأصلية وطرق merchant .
يتم إجراء جميع الاستعدادات ، الآن ، انظر كيفية تحديد ملفات التدويل تلقائيًا بناءً على هذه التعليقات التوضيحية.
الطبقة العامة messageresourceInterceptor تنفذ المعالج {Override public void postthandle (httpservletrequest req ، httpservletresponse rep ، معالج الكائن ، modelandviewsewiew) {// اضبط مسار i18 بالطريقة إذا (null! } طريقة HandlerMethod = (Handlermethod) معالج ؛ // التعليق التوضيحي على الطريقة i18 i18n i18nmethod = method.getMethodannotation (i18n.class) ؛ if (null! = i18nmethod) {req.setattribute (messageresourceextension.i18n_attribute ، i18nmethod.value ()) ؛ يعود؛ } // التعليق التوضيحي على وحدة التحكم i18n i18ncontroller = method.getBeantype (). getAnnotation (i18n.class) ؛ if (null! = i18ncontroller) {req.setattribute (messageresourceextension.i18n_attribute ، i18ncontroller.value ()) ؛ يعود؛ } // set i18 string controller = method.getBeanType (). getName () ؛ int index = controller.lastindexof (".") ؛ if (index! = -1) {controller = controller.subString (index + 1 ، controller.length ()) ؛ } index = controller.toupperCase (). indexof ("controller") ؛ if (index! = -1) {controller = controller.subString (index + 1 ، controller.length ()) ؛ } index = controller.toupperCase (). indexof ("controller") ؛ if (index! = -1) {controller = controller.subString (0 ، index) ؛ } req.setattribute (messageresourceextension.i18n_attribute ، وحدة تحكم) ؛ } Override Public Boolean prehandle (httpservletrequest req ، httpservletresponse rep ، معالج الكائن) {// عند القفز إلى هذه الطريقة ، قم أولاً بمسح معلومات التدويل في طلب req.removeAttribute (messageresourceextension.i18n_attribute) ؛ العودة صحيح. }}اسمحوا لي أن أشرح باختصار هذا التقاطع.
أولاً ، إذا كان هناك بالفعل "i18n_attribute" في الطلب ، فهذا يعني أن الإعدادات محددة في طريقة وحدة التحكم ، ولم يعد يتم إصدار الحكم.
ثم حدد ما إذا كان هناك تعليق توضيحي I18n على طريقة إدخال المعترض. إذا كان هناك ، قم بتعيين "i18n_attribute" في الطلب والخروج من المعترض. إذا كان لا ، تابع.
ثم حدد ما إذا كان هناك تعليق توضيحي I18n على الفصل الذي دخل إلى التقاطع. إذا كان هناك ، قم بتعيين "i18n_attribute" في الطلب والخروج من المعترض. إذا كان لا ، تابع.
أخيرًا ، إذا لم يكن هناك أي توضيح I18n على الطريقة والفئة ، فيمكننا تعيين ملف التدويل المحدد تلقائيًا وفقًا لاسم وحدة التحكم. على سبيل المثال ، "UserController" ، ثم سنبحث عن ملف التدويل "المستخدم".
الآن دعنا نديرها مرة أخرى لرؤية التأثير ونرى المحتوى في المعلومات الدولية المقابلة المعروضة على كل رابط.
في النهاية
لقد أكملت للتو الوظائف الأساسية لتعزيز التدويل بأكمله. أخيرًا ، قمت بتصنيف جميع التعليمات البرمجية و bootstrap4 المتكاملة لإظهار تأثير تنفيذ الوظيفة.
للحصول على رمز مفصل ، يرجى الاطلاع على رمز الربيع-boot-i18n-pro على github
ما سبق هو كل محتوى هذه المقالة. آمل أن يكون ذلك مفيدًا لتعلم الجميع وآمل أن يدعم الجميع wulin.com أكثر.