คำนำ
บริษัท ได้โอนโครงการจาก Struts2 ไปยัง SpringMVC เนื่องจากธุรกิจของ บริษัท เป็นบริการในต่างประเทศความต้องการฟังก์ชั่นระหว่างประเทศจึงสูงมาก ฟังก์ชั่นการทำให้เป็นสากลของ Struts2 นั้นสมบูรณ์แบบกว่า SpringMVC แต่คุณสมบัติที่สำคัญของฤดูใบไม้ผลิคือมันสามารถปรับแต่งและปรับแต่งได้ดังนั้นฟังก์ชั่นการทำให้เป็นสากลจะถูกเพิ่มเข้ามาเมื่อโครงการของ บริษัท ถูกปลูกถ่ายไปยัง SpringMVC ฉันได้รวบรวมบันทึกและปรับปรุงที่นี่
ฟังก์ชั่นหลักที่ใช้ในบทความนี้:
โหลดไฟล์ต่างประเทศหลายไฟล์โดยตรงจากโฟลเดอร์ หน้าการตั้งค่าพื้นหลังส่วนหน้าแสดงข้อมูลระหว่างประเทศ ไฟล์ที่แสดงข้อมูลระหว่างประเทศจะถูกตั้งค่าโดยอัตโนมัติโดยใช้ interceptors และคำอธิบายประกอบ ไฟล์ที่แสดงข้อมูลระหว่างประเทศในหน้าส่วนหน้าแสดงข้อมูลระหว่างประเทศ
หมายเหตุ: บทความนี้ไม่ได้แนะนำรายละเอียดเกี่ยวกับวิธีการกำหนดค่าความเป็นสากลตัวแยกวิเคราะห์ภูมิภาค ฯลฯ
ทำให้สำเร็จ
การเริ่มต้นโครงการระหว่างประเทศ
ก่อนอื่นสร้างข้อมูลพื้นฐาน Spring-Boot+Thymeleaf+Internationalization (Message.properties) โครงการ หากคุณต้องการคุณสามารถดาวน์โหลดได้จาก GitHub ของฉัน
ดูสั้น ๆ ที่ไดเรกทอรีและไฟล์ของโครงการ
ในหมู่พวกเขา i18napplication.java ตั้งค่า CookieLocaleResolver ซึ่งใช้คุกกี้เพื่อควบคุมภาษาระหว่างประเทศ นอกจากนี้ยังตั้งค่าตัวดักจับ LocaleChangeInterceptor เพื่อสกัดกั้นการเปลี่ยนแปลงในภาษาระหว่างประเทศ
@springbootapplication@configurationpublic คลาส i18napplication {โมฆะคงที่สาธารณะหลัก (สตริง [] args) {springapplication.run (i18napplication.class, args); } @Bean Public Localesolver Localesolver () {cookielocaleresolver slr = ใหม่ cookielocaleresolver (); SLR.SetCookieMaxage (3600); slr.setCookiename ("ภาษา"); // ตั้งชื่อคุกกี้ที่เก็บไว้เป็นภาษากลับ SLR; } @Bean Public WebMVCCONFigurer WebMVCCONFigurer () {ส่งคืน WebMVCCONFigurer ใหม่ () {// interceptor @Override Public Void AddInterceptors (InterceptorRegistry Registry) {registry.addinterceptor (ใหม่ - -มาดูสิ่งที่เขียนใน hello.html:
<! doctype html> <html xmlns = "http://www.w3.org/1999/xhtml" xmlns: th = "http://www.thymeleaf.org"> <head> <title> th: text = "#{i18n_page}"> </h1> <h3 th: text = "#{hello}"> </h3> </body> </html> ตอนนี้เริ่มโครงการและเยี่ยมชม http://localhost:9090/hello (ฉันตั้งค่าพอร์ตเป็น 9090 ใน application.properties)
เนื่องจากภาษาเริ่มต้นของเบราว์เซอร์เป็นภาษาจีนจึงเป็นค่าเริ่มต้นในการค้นหาใน messages_zh_cn.properties และถ้าไม่มันจะค้นหาในข้อความ properties สำหรับคำสากล
จากนั้นเราป้อน http://localhost:9090/hello?locale=en_US ในเบราว์เซอร์และภาษาจะถูกตัดเป็นภาษาอังกฤษ ในทำนองเดียวกันหากพารามิเตอร์หลังจาก URL ถูกตั้งค่าเป็น locale=zh_CH ภาษาจะถูกตัดเป็นภาษาจีน
โหลดไฟล์ต่างประเทศหลายไฟล์โดยตรงจากโฟลเดอร์
ในหน้า hello.html ของเรามีเพียงสองข้อมูลระหว่างประเทศ 'i18n_page' และ 'สวัสดี' อย่างไรก็ตามในโครงการจริงจะไม่มีข้อมูลเล็ก ๆ น้อย ๆ เพียงไม่กี่อย่างที่มักจะมีหลายร้อยรายการ จากนั้นเราจะต้องไม่ใส่ข้อมูลระหว่างประเทศจำนวนมากในไฟล์ที่มี messages.properties และโดยปกติแล้วข้อมูลระหว่างประเทศจะถูกจัดประเภทและจัดเก็บในหลายไฟล์ อย่างไรก็ตามเมื่อโครงการมีขนาดใหญ่ขึ้นไฟล์ต่างประเทศเหล่านี้จะมากขึ้นเรื่อย ๆ ในเวลานี้มันไม่สะดวกที่จะกำหนดค่าไฟล์นี้ทีละหนึ่งในไฟล์ application.properties ดังนั้นตอนนี้เราใช้ฟังก์ชั่นเพื่อโหลดไฟล์ต่างประเทศทั้งหมดในไดเรกทอรีสูตรโดยอัตโนมัติ
สืบทอด ResourceBundleMessagesource
สร้างชั้นเรียนภายใต้โครงการเพื่อสืบทอด ResourceBundleMessageSource หรือ ReloadableResourceBundleMessageSource และตั้งชื่อมัน MessageResourceExtension และฉีดเข้าไปในถั่วและชื่อ messageSource ซึ่งเราได้รับมรดก ResourceBundleMessagesource
@Component ("MessagesOrce") Public Class MessagerEsourceExtension ขยาย ResourceBundleMessagesource {} โปรดทราบว่าชื่อส่วนประกอบของเราจะต้องเป็น 'MessageSource' เพราะเมื่อเริ่มต้น ApplicationContext ถั่วที่มีถั่วชื่อ 'MessageSource' จะถูกค้นหา กระบวนการนี้อยู่ใน AbstractApplicationContext.java มาดูซอร์สโค้ดกันเถอะ
/*** เริ่มต้น MessagesOrce.*ใช้ Parent's หากไม่มีการกำหนดไว้ในบริบทนี้*/Void InitMessageSource () {configurableListableBeanFactory beanfactory = getBeanFactory (); if (beanfactory.containslocalBean (message_source_bean_name)) {this.messagesource = beanfactory.getBean (message_source_bean_name, messagesource.class); - ในวิธีการเริ่มต้น MessagesOrce นี้ BeanFactory มองหาถั่วที่ฉีดด้วยชื่อ MESSAGE_SOURCE_BEAN_NAME(messageSource) หากไม่พบมันจะมองหาว่ามีถั่วที่มีชื่ออยู่ในชั้นเรียนหลักหรือไม่
ใช้การโหลดไฟล์
ตอนนี้เราสามารถเริ่มต้น MessageResourceExtension ที่เราเพิ่งสร้างขึ้น
วิธีการโหลดไฟล์ถูกเขียน
@Component ("MessagesOrce") คลาสสาธารณะ MessagerEsourceExtension ขยาย ResourceBundleMessageSource {ส่วนตัว logger logger คงสุดท้ายสุดท้าย = loggerFactory.getLogger (MessagerEsourceExtension.class); / *** ไดเรกทอรีไฟล์สากลที่ระบุ*/ @value (value = "$ {spring.messages.basefolder: i18n}") สตริงส่วนตัว basefolder; / *** ไฟล์สากลที่ระบุโดย Parent MessagesOrce*/ @Value (value = "$ {spring.messages.baseName: ข้อความ}") สตริงส่วนตัว basename; @PostConstruct โมฆะสาธารณะ init () {logger.info ("init messageresourceExtension ... "); if (! stringutils.isempty (basefolder)) {ลอง {this.setBasenames (getallbasenames (basefolder)); } catch (ioexception e) {logger.error (e.getMessage ()); }} // ตั้งค่า MessageSource ResourceBundLeMessageOrce Parent = ใหม่ ResourceBundLeMessageSource (); parent.setBasename (basename); this.setParentMessagesource (ผู้ปกครอง); } / ** * รับชื่อไฟล์นานาชาติทั้งหมดในโฟลเดอร์ * * @param ชื่อไฟล์ folderName * @return * @throws ioexception * / สตริงส่วนตัว [] getallbasenames (สตริง folderName) โยน ioexception {ทรัพยากรทรัพยากร = ใหม่ classPathResource (folderName); ไฟล์ไฟล์ = resource.getFile (); รายการ <String> basenames = new ArrayList <> (); if (file.exists () && file.isdirectory ()) {this.getAllFile (basenames, ไฟล์, ""); } else {logger.error ("basefile ที่ระบุไม่มีอยู่หรือไม่ใช่โฟลเดอร์"); } return basenames.toarray (สตริงใหม่ [basenames.size ()]); } / ** * traverse ไฟล์ทั้งหมด * * @param basenames * @param โฟลเดอร์ * @param พา ธ * / โมฆะส่วนตัว getallfile (รายการ <string> basenames, โฟลเดอร์ไฟล์, เส้นทางสตริง) {ถ้า (folder.isdirectory file.Sparator); }} else {String i18Name = this.geti18filename (path + folder.getName ()); if (! basenames.contains (i18Name)) {basenames.add (i18Name); }}} / ** * แปลงชื่อไฟล์ปกติเป็นชื่อไฟล์นานาชาติ * * @param filename * @return * / สตริงส่วนตัว geti18fileName (ชื่อไฟล์สตริง) {filename = filename.replace (". Properties", ""); สำหรับ (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 ซึ่งจะเรียก MessageSource ผู้ปกครองเพื่อค้นหาข้อมูลระหว่างประเทศเมื่อไม่พบข้อมูลระหว่างประเทศgetAllBaseNames() ได้รับเส้นทางไปยัง baseFolder จากนั้นเรียกใช้เมธอด getAllFile() เพื่อรับชื่อไฟล์ของไฟล์ต่างประเทศทั้งหมดในไดเรกทอรีgetAllFile() สำรวจไดเรกทอรีถ้าเป็นโฟลเดอร์จะยังคงสำรวจอย่างต่อเนื่องหากเป็นไฟล์โทร getI18FileName() เพื่อแปลงชื่อไฟล์เป็นชื่อทรัพยากรระหว่างประเทศในรูปแบบ 'i18n/basename/' ดังนั้นเพียงแค่พูดง่ายๆหลังจากที่ MessageResourceExtension ถูกอินสแตนซ์ชื่อของไฟล์ทรัพยากรภายใต้โฟลเดอร์ 'I18N' จะถูกโหลดลงใน Basenames ตอนนี้มาดูเอฟเฟกต์
ก่อนอื่นเราเพิ่ม spring.messages.baseFolder=i18n ไปยังไฟล์แอปพลิเคชัน Properties ซึ่งจะกำหนดค่า 'i18n' ให้กับ baseFolder ใน MessageResourceExtension
หลังจากเริ่มต้นฉันเห็นว่าข้อมูลเริ่มต้นถูกพิมพ์ในคอนโซลซึ่งบ่งชี้ว่าวิธีการ init () มีคำอธิบายประกอบโดย @PostConstruct ได้ถูกดำเนินการแล้ว
จากนั้นเราสร้างไฟล์ข้อมูลนานาชาติสองชุด: 'Dashboard' และ 'Merchant' ซึ่งมีข้อมูลระหว่างประเทศเพียงข้อมูลเดียว: 'Dashboard.hello' และ 'Merchant.hello' ตามลำดับ
จากนั้นแก้ไขไฟล์ hello.html จากนั้นไปที่หน้าสวัสดี
... <body> <h1> หน้าสากล! </h1> <p th: text = "#{hello}"> </p> <p th: text = "#{merchant.hello}"> </p> <p th: text = "#{dashboard.hello}"> </p> </body>คุณจะเห็นว่าข้อมูลการทำให้เป็นสากลใน 'ข้อความ', 'แดชบอร์ด' และ 'ผู้ค้า' ถูกโหลดในหน้าเว็บโดยระบุว่าเราได้โหลดไฟล์ภายใต้โฟลเดอร์ 'I18N' สำเร็จในคราวเดียว
ไฟล์ที่แสดงข้อมูลระหว่างประเทศในหน้าส่วนหน้าของการตั้งค่าพื้นหลัง
ในส่วนก่อนหน้าเราประสบความสำเร็จในการโหลดไฟล์สากลหลายไฟล์และแสดงข้อมูลสากล แต่ข้อมูลสากลใน 'Dashboard.properties' คือ 'Dashboard.hello' และ 'Merchant.properties' คือ 'Merchant.hello' ดังนั้นจึงเป็นเรื่องยากมากที่จะเขียนคำนำหน้าสำหรับแต่ละ ตอนนี้ฉันต้องการเขียน 'Hello' ในไฟล์สากลของ 'Dashboard' และ 'Merchant' เท่านั้น แต่ข้อมูลการทำให้เป็นสากลใน 'Dashboard' หรือ 'Merchant' จะปรากฏขึ้น
วิธีการเขียน resolveCodeWithoutArguments ใน MessageResourceExtension (REWRITE resolveCode หากมีความจำเป็นในการจัดรูปแบบอักขระ)
@Component ("MessagesOrce") Public Class MessagerEsourceExtension ขยาย ResourceBundleMessageSource {... สตริงคงที่สาธารณะ i18n_attribute = "i18n_attribute"; @Override String redentresidewithoToUtArguments (รหัสสตริง, locale locale) {// รับชื่อไฟล์นานาชาติที่ระบุไว้ในการร้องขอ servletRequestatTributes attr = (servletRequestatTributes) requestcontextholder.currentrequestattributes (); สตริงสุดท้าย i18file = (สตริง) attr.getAttribute (i18n_attribute, requestattributes.scope_request); if (! stringUtils.isEmpty (i18file)) {// รับชื่อไฟล์ระหว่างประเทศที่จับคู่ในสตริง basenameset basename = getBasenameset (). สตรีม () .filter (ชื่อ -> stringUtils.endswithIgnorecase (ชื่อ i18file) .findfirst () if (! stringUtils.isEmpty (basename)) {// รับทรัพยากรทรัพยากรไฟล์ระหว่างประเทศที่ระบุที่ระบุ Bundle = getResourceBundle (basename, locale); if (bundle! = null) {return getStringOrnull (ชุด, รหัส); }}} // หากไม่มีฟิลด์สากลในโฟลเดอร์ I18 ที่ระบุการส่งคืน NULL จะมองหาการส่งคืนค่า NULL ใน ParentMessagesource; - ในเมธอด resolveCodeWithoutArguments ที่เราเขียนใหม่รับ 'i18n_attribute' จาก httpservletRequest (จะพูดถึงสถานที่ที่จะตั้งค่า) ที่สอดคล้องกับชื่อไฟล์ระหว่างประเทศที่เราต้องการแสดง จากนั้นเราค้นหาไฟล์ใน BasenameSet จากนั้นรับทรัพยากรผ่าน getResourceBundle และในที่สุดก็ getStringOrNull เพื่อรับข้อมูลระหว่างประเทศที่เกี่ยวข้อง
ตอนนี้เรามาเพิ่มสองวิธีใน HelloController ของเรา
@ControllerPublic คลาส Hellocontroller {@getMapping ("/hello") ดัชนีสตริงสาธารณะ (คำขอ httpservletrequest) {request.setAttribute (MessagerEsourceExtension.i18n_attribute, "Hello"); กลับ "System/Hello"; } @getMapping ("/dashboard") Dashboard สตริงสาธารณะ (คำขอ httpservletRequest) {request.setAttribute (MessagerEsourceExtension.i18n_attribute, "Dashboard"); กลับ "แดชบอร์ด"; } @getMapping ("/Merchant") ผู้ค้าสตริงสาธารณะ (คำขอ httpservletRequest) {request.setAttribute (MessagerEsourceExtension.i18n_attribute, "Merchant"); กลับ "พ่อค้า"; - ดูว่าเราตั้งค่า 'i18n_attribute' ที่สอดคล้องกันในแต่ละวิธีซึ่งจะตั้งค่าไฟล์สากลที่เกี่ยวข้องในแต่ละคำขอแล้วรับใน MessageResourceExtension
ในเวลานี้เราดูไฟล์สากลของเราและเราจะเห็นว่าคำหลักทั้งหมดเป็น 'สวัสดี' แต่ข้อมูลแตกต่างกัน
ในเวลาเดียวกันไฟล์ HTML ใหม่สองไฟล์คือ 'Dashboard.html' และ 'Merchant.html' ซึ่งมีข้อมูลระดับนานาชาติสำหรับ 'สวัสดี' และชื่อเรื่องสำหรับการแยกแยะ
<!-นี่คือ 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>ในเวลานี้มาเริ่มโครงการและดู
คุณจะเห็นว่าแม้ว่าคำสากลในแต่ละหน้าคือ 'สวัสดี' แต่เราแสดงข้อมูลที่เราต้องการแสดงในหน้าเว็บที่เกี่ยวข้อง
ใช้ interceptors และคำอธิบายประกอบเพื่อตั้งค่าไฟล์ที่แสดงข้อมูลระหว่างประเทศโดยอัตโนมัติในหน้าส่วนหน้า
แม้ว่าข้อมูลการทำให้เป็นสากลที่เกี่ยวข้องสามารถระบุได้ แต่ก็มีปัญหาเกินกว่าที่จะตั้งค่าไฟล์สากลใน httpservletRequest ในแต่ละคอนโทรลเลอร์ดังนั้นตอนนี้เราใช้การตัดสินอัตโนมัติเพื่อแสดงไฟล์ที่เกี่ยวข้อง
ก่อนอื่นเราสร้างคำอธิบายประกอบซึ่งสามารถวางไว้ในชั้นเรียนหรือวิธีการ
@Target ({ElementType.type, ElementType.method}) @retention (retentionPolicy.runtime) สาธารณะ @Interface i18n { / *** ชื่อไฟล์สากล* / สตริงค่า ();};}; จากนั้นเราใส่คำอธิบายประกอบ I18n ที่สร้างขึ้นในวิธีการควบคุมตอนนี้ เพื่อแสดงเอฟเฟกต์เราสร้าง ShopController และ UserController และยังสร้างไฟล์สากล 'ร้านค้า' และ 'ผู้ใช้' ที่สอดคล้องกันและเนื้อหายังเป็น 'สวัสดี'
@ControllerPublic คลาส Hellocontroller {@getMapping ("/hello") ดัชนีสตริงสาธารณะ () {return "System/Hello"; } @i18n ("Dashboard") @getMapping ("/dashboard") Public String Dashboard () {return "Dashboard"; } @i18n ("ผู้ค้า") @getMapping ("/ผู้ค้า") สตริงสาธารณะ () {return "ผู้ค้า"; - @i18n ("shop") @controllerpublic คลาส Shopcontroller {@getMapping ("Shop") Public String Shop () {return "Shop"; - @ControllerPublic คลาส userController {@getMapping ("ผู้ใช้") ผู้ใช้สตริงสาธารณะ () {return "ผู้ใช้"; - เราวางคำอธิบายประกอบ I18n ไว้ใต้ dashboard และวิธี merchant ภายใต้ HelloController และในชั้นเรียน ShopController ตามลำดับ และคำสั่งที่ตั้งค่า 'i18n_attribute' ภายใต้ dashboard ดั้งเดิมและวิธี merchant จะถูกลบออก
การเตรียมการทั้งหมดเสร็จสิ้นตอนนี้ดูวิธีการระบุไฟล์สากลโดยอัตโนมัติตามคำอธิบายประกอบเหล่านี้
Public Class MessagerEsourceInterceptor ใช้ HandlerInterceptor {@Override โมฆะสาธารณะ Postthandle (HTTPSERVLETREQUEST REQ, HTTPSERVLETRESSESPONSE Rep, Handler Object, ModelandView ModelandView) {// Set เส้นทาง i18) } วิธี handlermethod = (handlermethod) handler; // คำอธิบายประกอบเกี่ยวกับวิธีการ 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 (ดัชนี + 1, controller.length ()); }} index = controller.touppercase (). indexof ("คอนโทรลเลอร์"); if (index! = -1) {controller = controller.substring (ดัชนี + 1, controller.length ()); }} index = controller.touppercase (). indexof ("คอนโทรลเลอร์"); if (index! = -1) {controller = controller.substring (0, index); } req.setAttribute (MessagerEsourceExtension.i18n_attribute, คอนโทรลเลอร์); } @Override บูลีนสาธารณะ prehandle (httpservletrequest req, httpservletResponse ตัวแทน, ตัวจัดการวัตถุ) {// เมื่อกระโดดไปที่วิธีนี้ก่อนอื่นให้ล้างข้อมูลสากลในคำขอ req.removeattribute (MessageresourceExtension.i18n_attribute) กลับมาจริง; -ให้ฉันอธิบายการสกัดกั้นนี้สั้น ๆ
ก่อนอื่นหากมี 'i18n_attribute' อยู่แล้วในคำขอนั้นหมายความว่ามีการระบุการตั้งค่าในวิธีการควบคุมและไม่มีการตัดสินอีกต่อไป
จากนั้นตรวจสอบว่ามีคำอธิบายประกอบ I18n เกี่ยวกับวิธีการป้อน interceptor หรือไม่ หากมีให้ตั้งค่า 'i18n_attribute' ลงในคำขอและออกจาก interceptor หากไม่มีให้ดำเนินการต่อ
จากนั้นตรวจสอบว่ามีคำอธิบายประกอบ I18n ในชั้นเรียนที่เข้าสู่การสกัดกั้นหรือไม่ หากมีให้ตั้งค่า 'i18n_attribute' ลงในคำขอและออกจาก interceptor หากไม่มีให้ดำเนินการต่อ
ในที่สุดหากไม่มีคำอธิบายประกอบ I18n เกี่ยวกับวิธีการและคลาสเราสามารถตั้งค่าไฟล์สากลที่ระบุโดยอัตโนมัติตามชื่อคอนโทรลเลอร์ ตัวอย่างเช่น 'UserController' จากนั้นเราจะค้นหาไฟล์ 'ผู้ใช้' สากล '
ตอนนี้ให้เรียกใช้อีกครั้งเพื่อดูเอฟเฟกต์และดูเนื้อหาในข้อมูลระหว่างประเทศที่เกี่ยวข้องที่แสดงในแต่ละลิงก์
ในที่สุด
ฉันเพิ่งเสร็จสิ้นฟังก์ชั่นพื้นฐานของการปรับปรุงความเป็นสากลทั้งหมดของเรา ในที่สุดฉันก็จัดเรียงรหัสทั้งหมดและบูตสแตรป 4 เพื่อแสดงเอฟเฟกต์การใช้งานของฟังก์ชั่น
สำหรับรหัสโดยละเอียดโปรดดูรหัสของ Spring-Boot-I18N-PRO บน GitHub
ข้างต้นเป็นเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการเรียนรู้ของทุกคนและฉันหวังว่าทุกคนจะสนับสนุน wulin.com มากขึ้น