Implementando herança em arquivos de localização iOS





Saudações, queridos hackers!



Hoje quero compartilhar uma experiência interessante na solução do problema de localização. No iOS, a localização é organizada de forma bastante conveniente do ponto de vista de um ou vários destinos nos quais as chaves em localizable.strings não são muito repetidas. Mas tudo se torna mais complicado quando você tem uma dúzia de alvos, nos quais mais da metade das chaves são repetidas, mas ao mesmo tempo têm significados diferentes, e também há um conjunto de chaves que são exclusivas para um determinado alvo.



Para aqueles que ainda não encontraram isso, explicarei o problema em mais detalhes com um exemplo.



Digamos que temos um grande projeto no qual 90% do código geral e 3 alvos : MyApp1 , MyApp2 , MyApp3 , que possuem uma série de telas específicas, bem como cada uma tem seu próprio nome e textos. Em essência, um destino é um aplicativo independente. Cada um deles deve ser traduzido para 10 idiomas. No entanto, NÃO queremos adicionar chaves de localização como app1_localizable_key1 , app2_localizable_key1 , etc. Queremos que tudo seja bonito no código e localizado em uma linha



NSLocalizedString(@"localizable_key1", nil)


Sem nenhum if e ifdef , de forma que ao adicionar um novo destino, não tenhamos que procurar lugares com NSLocalizedString em todo o código de um projeto enorme e registrar novas chaves lá. Também queremos que algumas das chaves sejam vinculadas a telas de destino específicas, ou seja, havia as chaves app2_screen1_key , app3_screen2_key .



Agora você pode fazer o seguinte usando ferramentas padrão do Xcode:



  • Copie a parte comum localizable.strings para cada destino e obteremos 3 cópias desses arquivos.
  • Adicione chaves específicas de destino ao localizable.strings correspondente.


Que problemas temos:



  • É muito caro adicionar uma nova chave pública a um projeto. O número de assentos é igual ao número de alvos multiplicado pelo número de idiomas. Em nosso exemplo, são 30 lugares.
  • Existe a possibilidade de um erro quando adicionamos uma linha a 1-2 alvos atuais com os quais estamos trabalhando ativamente e, um ano depois, eles decidiram ressuscitar um ou mais alvos. Você terá que sincronizar manualmente as localizações entre si ou escrever um script para isso. E se algum desleixo foi mostrado ao adicionar ou mesclar branches, e as chaves gerais são misturadas com as específicas, então haverá uma verdadeira busca.
  • O volume de arquivos de localização. Estão todos em constante crescimento, o que torna difícil trabalhar com eles e aumenta as chances de conflito na fusão de ramos.


O que eu gostaria:



  • Para manter todas as chaves públicas em um arquivo separado.
  • Para cada destino, havia um arquivo no qual eram armazenadas apenas as chaves específicas a ele, bem como chaves gerais com valores para este destino.


Para nosso exemplo, ter um arquivo comum localizable.strings com strings



"shared_localizable_key1" = "MyApp title"
"shared_localizable_key2" = "MyApp description"
"shared_localizable_key3" = "Shared text1"
"shared_localizable_key4" = "Shared text2"


Eu gostaria de ter um arquivo localizable_app2.strings com as chaves



"shared_localizable_key1" = "MyApp2 another title"
"shared_localizable_key2" = "MyApp2 another description"
"app2_screen1_key" = "Profile screen title"


Essa. organizar o princípio de herança em arquivos de localização .



Infelizmente, o Xcode não é feito para isso, então tive que reinventar minha própria "bicicleta", que não queria ir há muito tempo devido ao fato de que o Xcode aqui e ali colocava paus nas rodas.



Temos um projeto com 18 destinos e 12 idiomas. E isso não é brincadeira, o projeto é muito grande e muitos alvos são necessários lá. Cada vez que precisamos adicionar uma nova chave pública para tradução, estamos lidando com 216 arquivos de localização. Isso leva muito tempo. E adicionar um novo alvo leva ao fato de que você precisa copiar mais 12 localizable.strings nele . Em geral, em algum momento percebemos que não era mais possível viver assim e tivemos que buscar uma solução.



Não vou falar por muito tempo sobre todos os métodos que consegui testar no processo, irei direto para a solução de trabalho.



Portanto, primeiro precisamos encontrar todas as chaves compartilhadas. Isso pode ser feito usando um script, não vou entrar em detalhes, é uma tarefa bastante trivial.



Além disso, quando recebemos um arquivo de localização comum (base), ou melhor, 12 arquivos físicos, bem como um conjunto de arquivos para cada destino, vá para o Xcode e adicione todos os arquivos lá. Ao mesmo tempo, não anexamos arquivos a nenhum destino, ou seja, o painel direito em Associação de destino deve estar desmarcado.







Vamos colocar essas marcas apenas para o arquivo, que será o resultado do trabalho do script de montagem dos arquivos.



Em seguida, a mesma "bicicleta" começa:



  • Localization, build_localization.py.
  • Localizable. localizable.strings.
  • Localizable .






Só precisamos dele para adicionar corretamente um link para os arquivos do projeto, e para que o Xcode os reconheça corretamente. Caso contrário, ele não os usará para encontrar as chaves. Por exemplo, se você criar uma pasta Localizable com os arquivos localizable.strings organizados corretamente e adicioná-la ao projeto como referências de pasta , não importa o que aconteça, o Xcode não entenderá que demos a ela as chaves de localização. Portanto, pegue a pasta Localizável , arraste-a como um grupo ( criar grupo ) e desmarque a caixa de seleção de copiar itens, se necessário, para obtê-la como na imagem abaixo.







Exclua a pasta localizávele adicione-o às exceções do gita. Como não precisamos do resultado do script no gita, ele mudará para cada destino e obstruirá os commits.



Agora precisamos adicionar o script à fase de construção. Para fazer isso, em Build Phases, clique em New Run Script Phase e escreva nosso script com parâmetros. 




python3 ${SRCROOT}/Localization/build_localization.py -b “${SRCROOT}/BaseLocalization" -s "${SRCROOT}/Target1Localization" -d "${SRCROOT}/Localization/Localizable"


b é a pasta com a localização base, s é a localização do destino atual, d é a pasta de resultados.



Mova a nova fase para cima, ela não deve ser inferior à fase Copiar recursos do pacote . Essa. primeiro, o script gera arquivos e só então eles são levados para o pacote.







Agora é importante informar ao Xcode que durante a execução do script, os arquivos são alterados, caso contrário, ao compilar, irá gerar um erro de que não foi possível encontrar os arquivos. Além disso, o erro estará apenas em uma montagem limpa e não ficará imediatamente claro qual é o problema. Na fase de construção, adicione todos os arquivos de localização aos arquivos de saída







Isso precisa ser feito para cada alvo. A maneira mais fácil de fazer isso é abrindo o projeto com um editor de texto, porque o Xcode não será capaz de copiar / colar uma fase entre os destinos. Da mesma forma, o parâmetro de script -s para cada destino será diferente.



Agora, com cada construção, o script pegará o arquivo de localização base, fará o roll over das alterações do arquivo de destino (adicionar, sobrescrever as chaves) e gerar a localização na pasta Localizable , que o iOS usará para encontrar as chaves.



Em geral, obtivemos o que foi planejado ao implementar o mecanismo de herança:



  • As chaves compartilhadas residem em um arquivo e não interferem em outros. O tempo para o processo de inserção de novas chaves foi reduzido em 18! Tempo.
  • As chaves relacionadas a um destino específico estão no arquivo correspondente.
  • O tamanho do arquivo caiu significativamente. Nós nos livramos da confusão de falas repetidas.
  • O processo de adição de um novo idioma a um projeto também é bastante simplificado.
  • Ao criar um novo destino, você não precisa copiar a localização com um monte de linhas desnecessárias. Crie um novo arquivo chamado localizable.strings e adicione apenas o que é necessário para este destino.
  • Se você decidir reviver o destino antigo, não precisará fazer nada com as linhas, tudo será retirado do arquivo base.
  • O script não suja o git, o resultado do trabalho permanece localmente e pode ser removido sem dor.


→ O script concluído pode ser obtido aqui



. Não pretendo ser um script perfeito, solicitações de pull são bem-vindas.



All Articles