Kata pengantar
Perusahaan telah mentransfer proyek dari Struts2 ke SpringMVC. Karena bisnis perusahaan adalah layanan di luar negeri, permintaan untuk fungsi internasional sangat tinggi. Fungsi internasionalisasi Struts2 lebih sempurna daripada SpringMVC, tetapi fitur besar Spring adalah bahwa ia dapat disesuaikan dan disesuaikan, sehingga fungsi internasionalisasi ditambahkan ketika proyek perusahaan ditransplantasikan ke SpringMVC. Saya telah menyusun catatan dan memperbaikinya di sini.
Fungsi utama yang diimplementasikan dalam artikel ini:
Langsung memuat beberapa file internasional dari folder. Latar belakang pengaturan halaman front-end menampilkan informasi internasional. File yang menampilkan informasi internasional diatur secara otomatis menggunakan pencegat dan anotasi. File yang menampilkan informasi internasional di halaman front-end menampilkan informasi internasional.
Catatan: Artikel ini tidak memperkenalkan secara rinci cara mengkonfigurasi internasionalisasi, parser regional, dll.
menyelesaikan
Inisialisasi Proyek Internasional
Pertama-tama buat proyek Spring-Boot+Thymeleaf+Internasionalisasi (Pesan.PROPERTIES). Jika Anda membutuhkannya, Anda dapat mengunduhnya dari github saya.
Pandangan singkat pada direktori dan file proyek
Di antara mereka, i18napplication.java menetapkan CookieLocaleResolver , yang menggunakan cookie untuk mengontrol bahasa internasional. Juga mengatur pencegat LocaleChangeInterceptor untuk mencegat perubahan dalam bahasa internasional.
@SpringbootApplication@configurationpublic kelas i18napplication {public static void main (string [] args) {springApplication.run (i18napplication.class, args); } @Bean Public LocalEresolver localeResolver () {cookielocaleresolver slr = cookielocaleresolver baru (); slr.setcookiemaxage (3600); slr.setCookiename ("bahasa"); // Atur nama cookie yang disimpan ke SLR Return Bahasa; } @Bean public webmvcconfigurer webmvcconfigurer () {return baru webmvcconfigurer () {// interceptor @override public void addInterceptors (interceptorregistry registry) {registry.addInterceptor (new localechangerceptor ()). AddPathPatterns (""); }}; }}Mari kita lihat apa yang tertulis di Hello.html:
<! Doctype html> <html xmlns = "http://www.w3.org/1999/xhtml" xmlns: th = "http://www.thymeleaf.org"> < -head> <itement> Hello world! </Title> </head> TH: text = "#{i18n_page}"> </h1> <h3 th: text = "#{hello}"> </h3> </body> </html> Sekarang mulailah proyek dan kunjungi http://localhost:9090/hello (saya mengatur port ke 9090 di application.properties).
Karena bahasa default browser adalah bahasa Cina, akan default untuk mencari di pesan_zh_cn.properties, dan jika tidak, itu akan mencari di pesan. Properti untuk kata -kata internasional.
Lalu kita memasukkan http://localhost:9090/hello?locale=en_US di browser dan bahasa akan dipotong menjadi bahasa Inggris. Demikian pula, jika parameter setelah URL diatur ke locale=zh_CH , bahasa akan dipotong menjadi Cina.
Muat beberapa file internasional langsung dari folder
Di halaman hello.html kami, hanya ada dua informasi internasional 'i18n_page' dan 'halo'. Namun, dalam proyek aktual, pasti tidak akan ada sekecil beberapa informasi internasional, biasanya ratusan dari mereka. Maka kita tidak boleh memasukkan begitu banyak informasi internasional dalam file dengan messages.properties , dan biasanya informasi internasional diklasifikasikan dan disimpan dalam beberapa file. Namun, ketika proyek menjadi lebih besar, file internasional ini akan menjadi lebih dan lebih. Pada saat ini, tidak nyaman untuk mengkonfigurasi file ini satu per satu di file application.properties . Jadi sekarang kami menerapkan fungsi untuk secara otomatis memuat semua file internasional di direktori formulasi.
Mewarisi sumber dayalemessageSource
Buat kelas di bawah proyek untuk mewarisi ResourceBundleMessageSource atau ReloadableResourceBundleMessageSource dan beri nama MessageResourceExtension . Dan menyuntikkannya ke dalam kacang dan bernama messageSource , di mana kami mewarisi ResourceBundlemessageSource.
@Component ("MessageSource") kelas publik MessagereSourceExtension memperluas sumber daya ResourceBundLeMessageSource {} Perhatikan bahwa nama komponen kami harus 'pesan pesan', karena ketika menginisialisasi ApplicationContext , kacang dengan kacang bernama 'PesanSource' akan dicari. Proses ini dalam AbstractApplicationContext.java . Mari kita lihat kode sumbernya
/*** Inisialisasi sumber pesan.*Gunakan orang tua jika tidak ada yang didefinisikan dalam konteks ini.*/Protected void initmessageSource () {configurableListableBeanFactory beanfactory = getBeanFactory (); if (beanfactory.containslocalbean (message_source_bean_name)) {this.messageSource = beanfactory.getbean (message_source_bean_name, messesource.class); ...}} ... Dalam metode menginisialisasi pesan sumber ini, BeanFactory mencari kacang yang disuntikkan dengan nama MESSAGE_SOURCE_BEAN_NAME(messageSource) . Jika tidak ditemukan, itu akan mencari apakah ada kacang dengan nama di kelas induknya.
Implementasikan pemuatan file
Sekarang kita dapat memulai MessageResourceExtension yang baru saja kita buat
Metode untuk memuat file ditulis.
@Component ("MessageSource") kelas publik MessagereSourceExtension memperluas sumber daya SumberCeBundLeMessageSource {private final static Logger Logger = LoggerFactory.getLogger (MessagereSourceExtension.class); / *** direktori file internasionalisasi yang ditentukan*/ @value (value = "$ {spring.messages.baseFolder: i18n}") private string basefolder; / *** File internasionalisasi yang ditentukan oleh Parent MessageSource*/ @Value (value = "$ {spring.messages.baseName: pesan}") basa string pribadi; @PostConstruct public void init () {logger.info ("init messageresourceextension ..."); if (! Stringutils.isempty (BaseFolder)) {coba {this.setBasEnames (getAllBasEnames (BaseFolder)); } catch (ioException e) {logger.error (e.getMessage ()); }} // atur Pesan induk sumber daya sumber daya PartEBundLeMessageSource = new ResourceBundLeMessageSource (); Parent.setBasename (Basename); this.setParentMessageSource (Parent); } / ** * Dapatkan semua nama file internasional di folder * * @param FolderName Nama file * @return * @throws ioException * / private string [] getAllBasEnames (string folderName) melempar ioException {Resource Resource = new classpathResource (foldername); File file = resource.getFile (); Daftar <String> BASENAMES = NEW ARRAYLIST <> (); if (file.exists () && file.isDirectory ()) {this.getAllFile (Basenames, File, ""); } else {logger.error ("Basefile yang ditentukan tidak ada atau bukan folder"); } return BaseNames.toArray (string baru [baseNames.size ()]); } / ** * Traverse semua file * * @param BaseNames * @param folder * @param path * / private void getAllFile (Daftar <String> BASENAMES, Folder File, Path String) {if (folder.isdirectory ()) {for File: folder.listFiles ()) {ini. File.separator); }} else {string i18name = this.geti18fileName (path + folder.getName ()); if (! BaseNames.Contains (i18Name)) {BaseNames.Add (i18Name); }}} / ** * Konversi nama file normal ke nama file internasional * * @param filename * @return * / private string geti18fileName (string filename) {filename = filename.replace (". Properties", ""); untuk (int i = 0; i <2; i ++) {int index = filename.LastIndexOf ("_"); if (index! = -1) {filename = filename.substring (0, index); }} kembalikan nama file; }}Jelaskan beberapa metode pada gilirannya.
@PostConstruct pada metode init() , yang secara otomatis akan memanggil metode init() setelah kelas MessagereSourceExtension dipakai. Metode ini mendapatkan semua file internasionalisasi di Direktori baseFolder dan mengaturnya ke basenameSet . Dan menetapkan ParentMessageSource , yang akan menghubungi orang tua pesan sumber untuk menemukan informasi internasional ketika informasi internasional tidak dapat ditemukan.getAllBaseNames() mendapatkan jalur ke baseFolder , dan kemudian memanggil metode getAllFile() untuk mendapatkan nama file dari semua file internasional di direktori.getAllFile() melintasi direktori, jika itu adalah folder, terus melintasi, jika itu adalah file, hubungi getI18FileName() untuk mengonversi nama file menjadi nama sumber daya internasional dalam format 'i18n/basa/'. Jadi sederhananya, setelah MessageResourceExtension dipakai, nama file sumber daya di bawah folder 'i18n' dimuat ke dalam Basenames . Sekarang mari kita lihat efeknya.
Pertama, kami menambahkan spring.messages.baseFolder=i18n ke file application.properties, yang akan menetapkan nilai 'i18n' ke baseFolder di MessageResourceExtension .
Setelah memulai, saya melihat bahwa informasi init dicetak di konsol, menunjukkan bahwa metode init () yang dianotasi oleh @PostConstruct telah dieksekusi.
Kemudian kami membuat dua set file informasi internasional: 'Dashboard' dan 'Merchant', yang hanya memiliki satu informasi internasional: 'Dashboard.hello' dan 'pedagang.hello' masing -masing.
Kemudian ubah file hello.html lalu kunjungi halaman Hello.
... <hody> <h1> Halaman internasionalisasi! </h1> <p th: text = "#{hello}"> </p> <p th: text = "#{Merchant.hello}"> </p> <p th: text = "#{dashboard.hello}"> </p> </pred> ...Anda dapat melihat bahwa informasi internasionalisasi dalam 'pesan', 'dasbor' dan 'pedagang' dimuat di halaman web, menunjukkan bahwa kami telah berhasil memuat file di bawah folder 'i18n' pada satu waktu.
File yang menampilkan informasi internasional di halaman front-end pengaturan latar belakang
Pada bagian sebelumnya, kami berhasil memuat beberapa file internasionalisasi dan menampilkan informasi internasionalisasi mereka. Tetapi informasi internasionalisasi di 'dashboard.properties' adalah 'dashboard.hello' dan 'pedagang.properties' adalah 'pedagang.hello'. Jadi akan sangat merepotkan untuk menulis awalan untuk masing -masing. Sekarang saya hanya ingin menulis 'halo' di file internasionalisasi 'dasbor' dan 'pedagang', tetapi informasi internasionalisasi di 'dasbor' atau 'pedagang' ditampilkan.
Tulis ulang metode resolveCodeWithoutArguments di MessageResourceExtension (tulis ulang resolveCode jika ada kebutuhan untuk pemformatan karakter).
@Component ("MessageSource") kelas publik MessagereSourceExtension memperluas sumber daya SumberCeBundLeMessageSource {... string statis public i18n_attribute = "i18n_attribute"; @Override Protected String ResolVecodeWithOutArguments (kode string, locale locale) {// Dapatkan nama file internasional yang ditentukan dalam permintaan servletRequestAttributes attr = (servletRequestAttributes) requestContextholder.currentrequestattributes (); string final i18file = (string) attr.getAttribute (i18n_attribute, requestAttributes.scope_request); if (! Stringutils.isempty (i18file)) {// Dapatkan nama file internasional yang cocok di BaseNameset String Basename = getBasEnameset (). Stream () .filter (name -> Stringutils.endswithignoIgneCase (name, i18file)) .findFirst () orelseCase (name, i18file)) .findFirst (). if (! Stringutils.isempty (Basename)) {// Dapatkan bundel sumber daya sumber daya internasional yang ditentukan bundel = getResourceBundle (Basename, lokal); if (bundle! = null) {return getstringornull (bundel, kode); }}} // Jika tidak ada bidang internasionalisasi di folder i18 yang ditentukan, pengembalian nol akan mencari pengembalian nol di parentMessageSource; } ...} Dalam metode resolveCodeWithoutArguments yang kami tulis ulang, dapatkan 'i18n_attribute' dari httpservletrequest (akan berbicara tentang di mana harus mengatur ini) sesuai dengan nama file internasional yang ingin kami tampilkan. Kemudian kami mencari file di BasenameSet , lalu mendapatkan sumber daya melalui getResourceBundle , dan akhirnya getStringOrNull untuk mendapatkan informasi internasional yang sesuai.
Sekarang mari kita tambahkan dua metode ke HelloController kami.
@ControllerPublic kelas HelloController {@getMapping ("/hello") Public String Index (permintaan httpservletRequest) {request.setAttribute (MessagereSourceExtension.i18n_attribute, "hello"); mengembalikan "System/Hello"; } @GetMapping ("/Dashboard") Public String Dashboard (permintaan httpservletRequest) {request.setAttribute (MessagereSourceExtension.i18n_attribute, "Dashboard"); mengembalikan "dasbor"; } @GetMapping ("/Merchant") Public String Merchant (permintaan httpservletRequest) {request.setAttribute (MessagereSourceExtension.i18n_attribute, "Merchant"); mengembalikan "pedagang"; }} Lihat bahwa kami menetapkan 'i18n_attribute' yang sesuai di setiap metode, yang akan mengatur file internasionalisasi yang sesuai di setiap permintaan dan kemudian mendapatkannya di MessageResourceExtension .
Pada saat ini, kami melihat file internasionalisasi kami dan kami dapat melihat bahwa semua kata kunci adalah 'halo', tetapi informasinya berbeda.
Pada saat yang sama, dua file HTML baru adalah 'Dashboard.html' dan 'Merchant.html', yang hanya memiliki informasi internasional untuk 'Hello' dan judul untuk membedakan.
<!-Ini hello.html-> <hody> <h1> halaman internasionalisasi! </h1> <p th: text = "#{hello}"> </p> </body> <!-Ini adalah dasbor.html-> <hody> <h1> halaman internasionalisasi (dasbor)! </h1> <p th: text = "#{hello}"> </p> </body> <!-Ini adalah Merchant.html-> <hody> <h1> halaman internasionalisasi (pedagang)! </h1> <p th: text = "#{hello}"> </p> </body>Saat ini, mari kita mulai proyek dan lihat.
Anda dapat melihat bahwa meskipun kata internasionalisasi pada setiap halaman adalah 'halo', kami menampilkan informasi yang ingin kami tampilkan di halaman yang sesuai.
Gunakan pencegat dan anotasi untuk secara otomatis mengatur file yang menampilkan informasi internasional di halaman front-end
Meskipun informasi internasionalisasi yang sesuai dapat ditentukan, terlalu merepotkan untuk mengatur file internasionalisasi di httpservletRequest di setiap pengontrol, jadi sekarang kami menerapkan penilaian otomatis untuk menampilkan file yang sesuai.
Pertama, kami membuat anotasi, yang dapat ditempatkan pada kelas atau metode.
@Target ({elementType.type, elementType.method}) @retensi (retentionpolicy.runtime) public @interface i18n { / *** Nama file internasional* / string value ();} Kemudian kami menempatkan anotasi I18n yang dibuat dalam metode pengontrol sekarang. Untuk menampilkan efeknya, kami membuat ShopController dan UserController , dan juga membuat file internasional 'toko' dan 'pengguna' yang sesuai, dan kontennya juga merupakan 'halo'.
@ControllerPublic kelas HelloController {@getMapping ("/hello") Public String Index () {return "System/Hello"; } @I18n ("Dashboard") @getMapping ("/Dashboard") Public String Dashboard () {return "Dashboard"; } @I18n ("Merchant") @getMapping ("/Merchant") Public String Merchant () {return "Merchant"; }} @I18n ("shop") @controllerpublic class shopController {@getmapping ("shop") public string shop () {return "shop"; }} @ControllerPublic kelas UserController {@getMapping ("User") Public String User () {return "user"; }} Kami menempatkan anotasi I18n di bawah metode dashboard dan merchant di bawah HelloController , dan di kelas ShopController masing -masing. Dan pernyataan yang menetapkan 'i18n_attribute' di bawah dashboard asli dan metode merchant dihapus.
Semua persiapan dilakukan, sekarang lihat bagaimana secara otomatis menentukan file internasionalisasi berdasarkan anotasi ini.
public class MessageResourceInterceptor implements HandlerInterceptor { @Override public void postHandle(HttpServletRequest req, HttpServletResponse rep, Object handler, ModelAndView modelAndView) { // Set the i18 path in the method if (null != req.getAttribute(MessageResourceExtension.I18N_ATTRIBUTE)) { return; } Metode Handlermethod = (Handlermethod) Handler; // anotasi pada metode i18 i18n i18nmethod = method.getMethodannotation (i18n.class); if (null! = i18nmethod) {req.setAttribute (messageresourceextension.i18n_attribute, i18nmethod.value ()); kembali; } // anotasi pada controller i18n i18ncontroller = method.getBeantype (). getAnnotation (i18n.class); if (null! = i18nController) {req.setAttribute (messageresourceextension.i18n_attribute, i18nController.value ()); kembali; } // 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, controller); } @Override Public boolean prehandle (httpservletrequest req, httpservletResponse rep, pawang objek) {// Saat melompat ke metode ini, pertama -tama hapus informasi internasionalisasi dalam permintaan req.removeattribute (messagereSourExtension.i18n_attribute); Kembali Benar; }}Izinkan saya menjelaskan secara singkat pencegat ini.
Pertama, jika sudah ada 'i18n_attribute' dalam permintaan, itu berarti bahwa pengaturan ditentukan dalam metode pengontrol, dan tidak lagi penilaian dibuat.
Kemudian tentukan apakah ada anotasi I18n pada metode untuk memasuki interseptor. Jika ada, atur 'i18n_attribute' ke dalam permintaan dan keluar dari pencegat. Jika tidak ada, lanjutkan.
Kemudian tentukan apakah ada anotasi I18n pada kelas yang memasuki intersep. Jika ada, atur 'i18n_attribute' ke dalam permintaan dan keluar dari pencegat. Jika tidak ada, lanjutkan.
Akhirnya, jika tidak ada anotasi I18n pada metode dan kelas, kami dapat secara otomatis mengatur file internasionalisasi yang ditentukan sesuai dengan nama pengontrol. Misalnya, 'UserController', maka kami akan mencari file internasionalisasi 'pengguna'.
Sekarang mari kita jalankan lagi untuk melihat efeknya dan melihat konten dalam informasi internasional yang sesuai ditampilkan pada setiap tautan.
akhirnya
Saya baru saja menyelesaikan fungsi dasar dari seluruh peningkatan internasionalisasi kami. Akhirnya, saya memilah semua kode dan Bootstrap4 terintegrasi untuk menunjukkan efek implementasi fungsi.
Untuk kode terperinci, silakan lihat Kode Spring-Boot-I18n-Pro di GitHub
Di atas adalah semua konten artikel ini. Saya berharap ini akan membantu untuk pembelajaran semua orang dan saya harap semua orang akan lebih mendukung wulin.com.