Prefácio
A empresa transferiu o projeto do STRUTS2 para o SpringMVC. Como os negócios da empresa são serviços no exterior, a demanda por funções internacionais é muito alta. A função de internacionalização do STRUTS2 é mais perfeita que a SpringMVC, mas a grande característica da primavera é que ela é personalizável e personalizada; portanto, sua função de internacionalização é adicionada quando o projeto da empresa é transplantado para o SpringMVC. Eu compilei os registros e os melhorei aqui.
As principais funções implementadas neste artigo:
Carregue diretamente vários arquivos internacionais de uma pasta. A página do front-end de configuração em segundo plano exibe informações internacionais. O arquivo que exibe informações internacionais é definido automaticamente usando interceptores e anotações. O arquivo que exibe informações internacionais na página front-end exibe informações internacionais.
Nota: Este artigo não introduz em detalhes como configurar a internacionalização, analisador regional etc.
concluir
Inicialização do projeto internacional
Primeiro, crie um projeto básico de Informações sobre Spring-Boot+Thymeleaf+Internacionalização (Message.Properties). Se você precisar, pode baixá -lo do meu github.
Uma breve olhada no diretório e arquivos do projeto
Entre eles, o i18NApplication.java define um CookieLocaleResolver , que usa cookies para controlar idiomas internacionais. Também configurou um interceptador LocaleChangeInterceptor para interceptar mudanças nos idiomas internacionais.
@SpringbootApplication@ConfigurationPublic Classe I18NApplication {public static void main (string [] args) {springapplication.run (i18napplication.class, args); } @Bean Public LociqueRESOLVER LOCALERESOLVER () {CookielocaleResolver slr = new cookielocaleresolver (); slr.setcookiemaxage (3600); slr.setcookiename ("idioma"); // Defina o nome do cookie armazenado para o idioma retornar SLR; } @Bean public webmvcConfigurer webmvcConfigurer () {retorna new webmvcConfigurer () {// interceptor @substituir public void addinterceptores (interceptorRegistry Registry) {Registry.addintercept (new LocalechangeMorpt ()). }}; }}Vamos dar uma olhada no que está escrito em hello.html:
<! Doctype html> <html xmlns = "http://www.w3.org/1999/xhtml" xmlns: th = "http://www.thymeleaf.org"> <weef> <title> helul World! th: text = "#{i18n_page}"> </h1> <h3 th: text = "#{hello}"> </h3> </body> </html> Agora inicie o projeto e visite http://localhost:9090/hello (eu defino a porta para 9090 em application.properties).
Como o idioma padrão do navegador é chinês, ele não deve pesquisar em Message_ZH_CN.Properties e, se não, ele pesquisará em mensagens.properties para palavras internacionalizadas.
Em seguida, entramos http://localhost:9090/hello?locale=en_US no navegador e o idioma será cortado para o inglês. Da mesma forma, se o parâmetro após o URL estiver definido como locale=zh_CH , o idioma será cortado para os chineses.
Carregar vários arquivos internacionais diretamente de uma pasta
Na nossa página Hello.html, existem apenas duas informações internacionais 'i18n_page' e 'hello'. No entanto, em projetos reais, definitivamente não haverá tão pequenos quanto algumas informações internacionais, geralmente centenas deles. Em seguida, não devemos colocar tantas informações internacionais em um arquivo com messages.properties , e geralmente as informações internacionais são classificadas e armazenadas em vários arquivos. No entanto, quando o projeto se tornar maior, esses arquivos internacionais se tornarão cada vez mais. No momento, é inconveniente configurar este arquivo um por um no arquivo application.properties . Então agora implementamos uma função para carregar automaticamente todos os arquivos internacionais no diretório de formulação.
Herdar ResourceBundLemessagesource
Crie uma classe sob o projeto para herdar ResourceBundleMessageSource ou ReloadableResourceBundleMessageSource e nomeie -o MessageResourceExtension . E injete -o no feijão e nomeado messageSource , onde herdamos ResourceBundLemessagesource.
@Component ("MessageSource") Classe pública MessagerSourceExtension estende ResourceBundLemessagesource {} Observe que o nome do nosso componente deve ser 'MensagemUrce', porque, ao inicializar ApplicationContext , o feijão com o feijão chamado 'MessageRce' será procurado. Este processo está em AbstractApplicationContext.java . Vamos dar uma olhada no código -fonte
/*** Inicialize a Mensagem. if (beanfactory.containslocalbean (message_source_bean_name)) {this.messagesource = beanFactory.getBean (message_source_bean_name, messensource.class); ...}} ... Neste método de inicializar o MessageRce, o Beanfactory procura feijões injetados com o nome MESSAGE_SOURCE_BEAN_NAME(messageSource) . Se não for encontrado, ele procurará se há um feijão com o nome em sua classe pai.
Implementar carregamento de arquivos
Agora podemos começar MessageResourceExtension que acabamos de criar
O método para carregar o arquivo está gravado.
@Component ("MensagemUrce") Classe pública MessagerSourceExtension estende ResourceBundLemessagesource {private final estático logger logger = LoggerFactory.getLogger (MessagerEsourceExtension.class); / *** Diretório de arquivos de internacionalização especificado*/ @Value (value = "$ {spring.messages.basefolder: i18n}") private string base base; / *** Arquivo de internacionalização especificado pelo pai Mensagens de Mensagens*/ @value (value = "$ {spring.messages.basename: message}") private string basename; @PostConstruct public void init () {Logger.info ("init MessagerSourceExtension ..."); if (! stringUtils.isEmpty (BaseFolder)) {tente {this.SetBaseNames (getAllBaseNames (BaseFolder)); } catch (ioexception e) {logger.error (e.getMessage ()); }} // Definir mensagens paiRce ResourceBundLemessagesource Parent = new ResourceBundLemessagesource (); parent.setBaseName (nome de base); this.setParentMessagesource (pai); } / ** * Obtenha todos os nomes internacionais de arquivos na pasta * * @param FolderName Name Nome do arquivo * @return * @THOWSowSception * / private String [] getAllBaseNames (String Fastename) lança ioexception {Resource Resource = new ClassPathResource (FolderName); Arquivo de arquivo = Resource.getFile (); List <string> basenames = new ArrayList <> (); if (file.exists () && file.isdirectory ()) {this.getallfile (nomes basenos, "" "); } else {Logger.error ("OFile de base especificado não existe ou não é uma pasta"); } retorna basenames.toArray (new String [Basenames.size ()]); } / ** * Atravesse todos os arquivos * * @param basenames * @param pasta * @param caminho * / private void getAllFile (list <string> nomes basenos, pasta de arquivo, string path) {if (paster.isdirectory ()) {for (file: fileRames.listfiles ()) {this.getallfile () {for (arquivo file: listfiles ()) {this.getAllfile (thisgetNfile) {para (file: fileRames.listfiles () {this.getAllfile) File.separator); }} else {string i18name = this.geti18filename (path + pasta.getName ()); if (! Basenames.contains (i18Name)) {basenames.add (i18Name); }}} / ** * Converta nomes normais de arquivos em nomes de arquivos internacionais * * @param filename * @return * / private string geti18filename (string filename) {filename = filename.replace (". Propriedades", ""); for (int i = 0; i <2; i ++) {int index = filename.LastIndexOf ("_"); if (index! = -1) {filename = filename.substring (0, index); }} retornar o nome do arquivo; }}Explique vários métodos por sua vez.
@PostConstruct no método init() , que chamará automaticamente init() após a classe MessagerSourceExtension for instanciada. Este método obtém todos os arquivos de internacionalização no diretório baseFolder e os define como basenameSet . E defina um ParentMessageSource , que chamará o painel de Mensagens para encontrar as informações internacionais quando a informação internacional não puder ser encontrada.getAllBaseNames() obtém o caminho para baseFolder e, em seguida, chama getAllFile() para obter os nomes de arquivos de todos os arquivos internacionais no diretório.getAllFile() atravessa o diretório, se for uma pasta, continua a atravessar, se for um arquivo, ligue para getI18FileName() para converter o nome do arquivo em um nome de recurso internacional no formato 'i18n/basename/'. Então, simplesmente, depois que MessageResourceExtension for instanciada, o nome do arquivo de recursos na pasta 'i18n' é carregado em Basenames . Agora vamos ver o efeito.
Primeiro, adicionamos um spring.messages.baseFolder=i18n ao arquivo Application.properties, que atribuirá o valor 'i18n' ao baseFolder na MessageResourceExtension .
Após o início, vi que as informações iniciais foram impressas no console, indicando que o método init () anotado pelo @PostConstruct foi executado.
Em seguida, criamos dois conjuntos de arquivos de informações internacionais: 'Dashboard' e 'Merchant', que possuem apenas uma informação internacional: 'Dashboard.Hello' e 'Merchant.Hello', respectivamente.
Em seguida, modifique o arquivo hello.html e visite a página Hello.
<body> <h1> Página de internacionalização!
Você pode ver que as informações de internacionalização em 'mensagem', 'painel' e 'comerciante' são carregadas na página da web, indicando que carregamos com sucesso o arquivo na pasta 'i18n' de uma só vez.
Arquivos que exibem informações internacionais na página front-end das configurações em segundo plano
Na seção anterior, carregamos com sucesso vários arquivos de internacionalização e exibimos suas informações de internacionalização. Mas as informações de internacionalização em 'Dashboard.properties' são 'painel.hello' e 'comerciante.properties' é 'comerciante.hello'. Portanto, seria muito problemático escrever um prefixo para cada um. Agora, quero escrever apenas 'Hello' nos arquivos de internacionalização de 'Painel' e 'comerciante', mas as informações de internacionalização no 'painel' ou 'comerciante' são exibidas.
Reescreva o método resolveCodeWithoutArguments no MessageResourceExtension (reescreva resolveCode se houver necessidade de formatação de caracteres).
@Component ("MensagemUrce") Classe pública MessagerSourceExtension estende ResourceBundLemessagesource {... public static string i18n_attribute = "i18n_attribute"; @Override Protected String ResolveCoDewithouTarguments (código da String, Locale Locale) {// Obtenha o nome internacional especificado do arquivo definido na solicitação servletRequestattributes att = (servletRequestattributes) requestContextholder.CurrentRequestatrributes (); Final String I18File = (String) att.getAttribute (i18n_attribute, requestAttributes.scope_request); if (! stringUtils.isEmpty (i18file)) {// Obtenha o nome do arquivo internacional correspondente no string basenameset Basename = getBaseNameSet (). Stream () .Filter (name -> stringils.endswithignorecase (nome, i18file)) .findfirst (). orrelse (n); if (! stringUtils.isEmpty (nome da base)) {// Obtenha o recurso internacional de recursos internacionais especificado Bundle = getResourceBundle (nome de base, loce); if (pacote! = null) {return getStringornull (pacote, código); }}} // Se não houver um campo de internacionalização na pasta i18 especificada, o retorno do Null procurará o retorno nulo no parentMessagesource; } ...} No método resolveCodeWithoutArguments , reescrevemos, obtemos 'i18n_attribute' do httpServletRequest (falará sobre onde definir isso) correspondente ao nome do arquivo internacional que queremos exibir. Em seguida, procuramos o arquivo no BasenameSet , depois obtemos o recurso através getResourceBundle e, finalmente, getStringOrNull para obter as informações internacionais correspondentes.
Agora vamos adicionar dois métodos ao nosso HelloController .
@ControllerPublic Classe Hellocontroller {@GetMapping ("/hello") Public String Index (httpServletRequest Request) {request.setAttribute (messageResourceExtension.i18n_attribute, "hello"); retornar "Sistema/Hello"; } @GetMapping ("/Dashboard") Public String Dashboard (httpServletRequest request) {request.setAttribute (messageResourceExtension.i18n_attribute, "painel"); retornar "painel"; } @GetMapping ("/Merchant") public String Merchant (httpServletRequest Request) {request.setAttribute (messageResourceExtension.i18n_attribute, "comerciante"); retornar "comerciante"; }} Consulte que definimos um 'i18n_attribute' correspondente em cada método, que definirá o arquivo de internacionalização correspondente em cada solicitação e, em seguida, o obtém no MessageResourceExtension .
Neste momento, analisamos nosso arquivo de internacionalização e podemos ver que todas as palavras -chave são 'olá', mas as informações são diferentes.
Ao mesmo tempo, dois novos arquivos HTML são 'Dashboard.html' e 'Merchant.html', que possuem apenas uma informação internacional para 'Hello' e um título para distinguir.
<!-Isso é hello.html-> <body> <h1> Página de internacionalização!
<!-Esta é a Dashboard.html-> <body> <h1> Página de internacionalização (painel)!
<!-Este é o comerciante.
Neste momento, vamos iniciar o projeto e dar uma olhada.
Você pode ver que, embora a palavra de internacionalização em cada página seja 'Olá', exibimos as informações que queremos exibir na página correspondente.
Use interceptores e anotações para configurar automaticamente arquivos que exibem informações internacionais na página front-end
Embora as informações de internacionalização correspondentes possam ser especificadas, é muito problemático definir o arquivo de internacionalização no HTTPServletRequest em cada controlador, então agora implementamos julgamento automático para exibir o arquivo correspondente.
Primeiro, criamos uma anotação, que pode ser colocada em uma classe ou método.
@Target ({elementType.type, elementType.method}) @retention (retentionpolicy.runtime) public @interface i18n { / *** nome do arquivo internacionalizado* / string valor ();} Em seguida, colocamos a anotação I18n criada no método do controlador agora. Para exibir seu efeito, criamos um ShopController e UserController e também criamos os arquivos internacionais de 'loja' e 'usuário' correspondentes, e o conteúdo também é um 'hello'.
@ControllerPublic Classe Hellocontroller {@getMapping ("/hello") public String Index () {return "System/hello"; } @I18n ("painel") @getMapping ("/painel") public String Dashboard () {return "Dashboard"; } @I18n ("comerciante") @getMapping ("/comerciante") public string merchant () {return "comerciante"; }} @I18n ("shop") @controlerpublic class shopcontroller {@getMapping ("shop") public string shop () {return "shop"; }} @ControllerPublic Classe UserController {@GetMapping ("User") public String User () {return "User"; }} Colocamos I18n sob dashboard e merchant sob HelloController e na classe ShopController , respectivamente. E a declaração que define 'i18n_attribute' no dashboard original e nos métodos merchant é removida.
Os preparativos estão todos feitos, agora veja como especificar automaticamente os arquivos de internacionalização com base nessas anotações.
public class MessagerSourceIntercept implementa HandlerInterceptor {@Override public void PostHandle (httpServletRequest req, httpServletResponse Rep, manipulador de objetos, modelAndView)) {// defina o caminho I18 no método se (null! } Handlermethod método = (handlermethod) manipulador; // anotação no método i18 i18n i18nmethod = method.getMethodannotation (i18n.class); if (null! = i18nmethod) {req.setAttribute (messageResourceExtension.i18n_attribute, i18nmethod.value ()); retornar; } // anotação no controlador i18n i18nController = method.getBeanType (). getAnnotation (i18n.class); if (null! = i18nController) {req.setattribute (messageResourceExtension.i18n_attribute, i18nController.value ()); retornar; } // set i18 string controller = métod.getBeanType (). GetName (); int index = controller.lastIndexOf ("."); if (index! = -1) {controller = controller.substring (index + 1, controller.length ()); } index = controller.TOUPCASE (). IndexOf ("Controller"); if (index! = -1) {controller = controller.substring (index + 1, controller.length ()); } index = controller.TOUPCASE (). IndexOf ("Controller"); if (index! = -1) {controller = controller.substring (0, index); } req.setAttribute (MessagerSourceExtension.i18n_attribute, controlador); } @Override Public Boolean Prehandle (httpServletRequest Req, httpServletResponse Rep, manipulador de objetos) {// Ao pular para esse método, primeiro limpe as informações de internacionalização no solicitação req.removeattribute (MessagerEsoursextension.i18n_attribute); retornar true; }}Deixe -me explicar brevemente este interceptador.
Primeiro, se já houver 'i18n_attribute' na solicitação, isso significa que as configurações são especificadas no método do controlador e não é mais julgamento.
Em seguida, determine se existe uma anotação I18n no método para entrar no interceptador. Se houver, defina 'i18n_attribute' na solicitação e saia do interceptador. Se não houver, continue.
Em seguida, determine se há uma anotação I18n na classe que entrou na interceptação. Se houver, defina 'i18n_attribute' na solicitação e saia do interceptador. Se não houver, continue.
Finalmente, se não houver anotação I18n no método e na classe, podemos definir automaticamente o arquivo de internacionalização especificado de acordo com o nome do controlador. Por exemplo, 'UserController', procuraremos o arquivo de internacionalização do 'usuário'.
Agora, vamos executá -lo novamente para ver o efeito e ver o conteúdo em suas informações internacionais correspondentes exibidas em cada link.
afinal
Acabei de concluir as funções básicas de todo o nosso aprimoramento da internacionalização. Por fim, resolvi todo o código e o Bootstrap Integrated para mostrar o efeito de implementação da função.
Para um código detalhado, consulte o código de primavera-boot-i18n-pro no github
O exposto acima é todo o conteúdo deste artigo. Espero que seja útil para o aprendizado de todos e espero que todos apoiem mais o wulin.com.