Prática recomendada usando NSLocalizedString

140

Estou (como todos os outros) usando NSLocalizedStringpara localizar meu aplicativo.

Infelizmente, existem várias "desvantagens" (não necessariamente a culpa do próprio NSLocalizedString), incluindo

  • Não há preenchimento automático para seqüências de caracteres no Xcode. Isso torna o trabalho não apenas propenso a erros, mas também cansativo.
  • Você pode acabar redefinindo uma string simplesmente porque não sabia que uma string equivalente já existia (por exemplo, "Digite a senha" vs. "Digite a senha primeiro")
  • Da mesma forma que o problema de preenchimento automático, você precisa "lembrar" / copipaste as sequências de comentários, ou então genstringacabará com vários comentários para uma sequência
  • Se você deseja usar genstringdepois de localizar algumas seqüências, tenha cuidado para não perder suas localizações antigas.
  • As mesmas strings estão espalhadas por todo o projeto. Por exemplo, você usou em NSLocalizedString(@"Abort", @"Cancel action")todos os lugares e, em seguida, a Revisão de código solicita que você renomeie a sequência NSLocalizedString(@"Cancel", @"Cancel action")para tornar o código mais consistente.

O que eu faço (e depois de algumas pesquisas no SO, achei que muitas pessoas fazem isso) é ter um strings.harquivo separado, onde eu #definelocalizo todo o código. Por exemplo

// In strings.h
#define NSLS_COMMON_CANCEL NSLocalizedString(@"Cancel", nil)
// Somewhere else
NSLog(@"%@", NSLS_COMMON_CANCEL);

Isso basicamente fornece o preenchimento de código, um único local para alterar os nomes de variáveis ​​(portanto, não é mais necessário usar genstring) e uma palavra-chave exclusiva para refatorar automaticamente. No entanto, isso tem o custo de acabar com um monte de #defineinstruções que não são inerentemente estruturadas (como LocString.Common.Cancel ou algo assim).

Então, enquanto isso funciona um pouco bem, eu queria saber como vocês fazem isso em seus projetos. Existem outras abordagens para simplificar o uso do NSLocalizedString? Existe talvez até uma estrutura que a encapsule?

JiaYow
fonte
Eu faço quase o mesmo que você. Mas estou usando o NSLocalizedStringWithDefaultValue para criar arquivos de strings diferentes para diferentes problemas de localização (como controladores, modelos etc.) e para criar um valor padrão inicial.
Anka 16/04
Parece que Exportar para localização do xcode6 não captura as seqüências definidas como macros em um arquivo de cabeçalho. Alguém pode confirmar ou me dizer o que posso estar perdendo? Obrigado...!
Juddster 30/09
@Juddster, pode confirmar, mesmo com o novo editor de fundo-> Exportar para Localização, ele não é captado no arquivo de cabeçalho
Red

Respostas:

100

NSLocalizedStringpossui algumas limitações, mas é tão central para o Cocoa que não é razoável escrever código personalizado para lidar com a localização, o que significa que você precisará usá-lo. Dito isto, um pouco de ferramentas pode ajudar, eis como eu procedo:

Atualizando o arquivo de strings

genstringssobrescreve seus arquivos de string, descartando todas as suas traduções anteriores. Eu escrevi update_strings.py para analisar o arquivo de strings antigo, executar genstringse preencher os espaços em branco para que você não precise restaurar manualmente suas traduções existentes. O script tenta corresponder os arquivos de cadeia existentes o mais próximo possível para evitar diferenças muito grandes ao atualizá-los.

Nomeando suas strings

Se você usar NSLocalizedStringcomo anunciado:

NSLocalizedString(@"Cancel or continue?", @"Cancel notice message when a download takes too long to proceed");

Você pode acabar definindo a mesma string em outra parte do seu código, que pode entrar em conflito, pois o mesmo termo em inglês pode ter significado diferente em contextos diferentes ( OKe Cancellembre-se). É por isso que eu sempre uso uma string all-caps sem sentido com um prefixo específico do módulo e uma descrição muito precisa:

NSLocalizedString(@"DOWNLOAD_CANCEL_OR_CONTINUE", @"Cancel notice window title when a download takes too long to proceed");

Usando a mesma string em lugares diferentes

Se você usar a mesma sequência várias vezes, poderá usar uma macro como você fez ou armazená-la em cache como uma variável de instância no seu controlador de exibição ou na sua fonte de dados. Dessa forma, você não precisará repetir a descrição, que pode ficar obsoleta e inconsistente entre instâncias da mesma localização, o que é sempre confuso. Como as variáveis ​​de instância são símbolos, você poderá usar o preenchimento automático nessas traduções mais comuns e usar seqüências "manuais" para as específicas, que ocorreriam apenas uma vez.

Espero que você seja mais produtivo com a localização do cacau com essas dicas!

ndfred
fonte
Obrigado pela sua resposta, definitivamente darei uma olhada no seu arquivo python. Eu concordo com suas convenções de nomenclatura. Conversei recentemente com outros desenvolvedores do iOS e eles recomendaram o uso de strings estáticos em vez de macros, o que faz sentido. Promovi sua resposta, mas esperarei um pouco antes de aceitá-la, porque a solução ainda é um pouco desajeitada. Talvez algo melhor aconteça. Obrigado novamente!
JiaYow
Você é muito bem-vindo. A localização é um processo tedioso, ter as ferramentas e o fluxo de trabalho certos faz um mundo de diferença.
Ndfred
17
Eu nunca entendi por que as funções de localização no estilo gettext usam uma das traduções como chave. O que acontece se o texto original for alterado? Sua chave muda e todos os arquivos localizados estão usando o texto antigo para a chave. Isso nunca fez sentido para mim. Eu sempre usei chaves como "home_button_text" para que sejam únicas e nunca mudem. Também escrevi um script bash para analisar todos os meus arquivos Localizable.strings e gerar um arquivo de classe com métodos estáticos que carregarão a sequência apropriada. Isso me dá a conclusão do código. Um dia eu poderia abrir esse código.
Mike Weller
2
Eu acho que você genstringsnão quis dizer gestring.
Hiroshi
1
O tempo de compilação @ndfred verifica se você não digitou a sequência errada, é a maior vitória. É marginalmente mais código para adicionar de qualquer maneira. Também no caso da refatoração, a análise estática, ter um símbolo facilitará as coisas.
Allen Zeng
31

Quanto ao preenchimento automático para seqüências de caracteres no Xcode, você pode tentar https://github.com/questbeat/Lin .

hiroshi
fonte
3
Isso é realmente incrível. Não há necessidade de criar macros.
Beau Nouvelle
1
Página não encontrada_
Juanmi
1
@ Juanmi Obrigado por mencionar o link morto. Substituí o link pelo URL do github.
hiroshi 07/06
24

Concordo com o ndfred, mas gostaria de adicionar isso:

O segundo parâmetro pode ser usado como ... valor padrão !!

(NSLocalizedStringWithDefaultValue não funciona corretamente com genstring, por isso propus esta solução)

Aqui está minha implementação personalizada que usa NSLocalizedString que usa comentário como valor padrão:

1 No cabeçalho pré-compilado (arquivo .pch), redefina a macro 'NSLocalizedString':

// cutom NSLocalizedString that use macro comment as default value
#import "LocalizationHandlerUtil.h"

#undef NSLocalizedString
#define NSLocalizedString(key,_comment) [[LocalizationHandlerUtil singleton] localizedString:key  comment:_comment]

2. crie uma classe para implementar o manipulador de localização

#import "LocalizationHandlerUtil.h"

@implementation LocalizationHandlerUtil

static LocalizationHandlerUtil * singleton = nil;

+ (LocalizationHandlerUtil *)singleton
{
    return singleton;
}

__attribute__((constructor))
static void staticInit_singleton()
{
    singleton = [[LocalizationHandlerUtil alloc] init];
}

- (NSString *)localizedString:(NSString *)key comment:(NSString *)comment
{
    // default localized string loading
    NSString * localizedString = [[NSBundle mainBundle] localizedStringForKey:key value:key table:nil];

    // if (value == key) and comment is not nil -> returns comment
    if([localizedString isEqualToString:key] && comment !=nil)
        return comment;

    return localizedString;
}

@end

3. Use-o!

Certifique-se de adicionar um script Executar em suas fases de compilação do aplicativo para que o arquivo Localizable.strings seja atualizado a cada compilação, ou seja, uma nova cadeia localizada será adicionada ao seu arquivo Localized.strings:

Meu script de fase de construção é um script de shell:

Shell: /bin/sh
Shell script content: find . -name \*.m | xargs genstrings -o MyClassesFolder

Então, quando você adiciona esta nova linha ao seu código:

self.title = NSLocalizedString(@"view_settings_title", @"Settings");

Em seguida, execute uma compilação, seu arquivo ./Localizable.scripts conterá esta nova linha:

/* Settings */
"view_settings_title" = "view_settings_title";

E como o valor da chave == para 'view_settings_title', o LocalizedStringHandler personalizado retornará o comentário, ou seja, 'Configurações "

Voilà :-)

Pascal
fonte
Obtendo erros do ARC, nenhum método de instância conhecido para o seletor 'localizedString: comment: :(
Mangesh
Suponho que seja porque está faltando LocalizationHandlerUtil.h. Não consigo encontrar a parte de trás do código ... Basta tentar criar o arquivo de cabeçalho LocalizationHandlerUtil.h e deve ser OK
Pascal
Eu criei os arquivos. Eu acho que é devido ao problema do caminho da pasta.
Mangesh
3

No Swift, estou usando o seguinte, por exemplo, para o botão "Sim" neste caso:

NSLocalizedString("btn_yes", value: "Yes", comment: "Yes button")

Observe o uso do value:valor de texto padrão. O primeiro parâmetro serve como o ID da tradução. A vantagem de usar o value:parâmetro é que o texto padrão pode ser alterado posteriormente, mas o ID da tradução permanece o mesmo. O arquivo Localizable.strings conterá"btn_yes" = "Yes";

Se o value:parâmetro não foi usado, o primeiro parâmetro seria usado para ambos: para o ID da tradução e também para o valor de texto padrão. O arquivo Localizable.strings conteria "Yes" = "Yes";. Esse tipo de gerenciamento de arquivos de localização parece estranho. Especialmente se o texto traduzido for longo, o ID também será longo. Sempre que qualquer caractere do valor de texto padrão for alterado, o ID da tradução também será alterado. Isso leva a problemas quando sistemas de tradução externos são usados. A alteração do ID da tradução é entendida como a adição de novo texto de tradução, o que nem sempre é desejado.

petrsyn
fonte
2

Eu escrevi um script para ajudar a manter Localizable.strings em vários idiomas. Embora não ajude no preenchimento automático, ajuda a mesclar arquivos .strings usando o comando:

merge_strings.rb ja.lproj/Localizable.strings en.lproj/Localizable.strings

Para mais informações, consulte: https://github.com/hiroshi/merge_strings

Alguns de vocês acham útil, espero.

hiroshi
fonte
2

Se alguém procura uma solução Swift. Você pode conferir minha solução que eu montei aqui: SwiftyLocalization

Com algumas etapas de configuração, você terá uma localização muito flexível na planilha do Google (comentário, cor personalizada, destaque, fonte, várias folhas e muito mais).

Em resumo, as etapas são: Google Spreadsheet -> arquivos CSV -> Localizable.strings

Além disso, ele também gera Localizables.swift, uma estrutura que age como uma interface para a recuperação e decodificação de chaves (você precisa especificar manualmente uma maneira de decodificar String da chave).

Por que isso é ótimo?

  1. Você não precisa mais ter uma chave como uma sequência simples em todos os lugares.
  2. Chaves erradas são detectadas em tempo de compilação.
  3. O Xcode pode executar o preenchimento automático.

Embora existam ferramentas que podem preencher automaticamente sua chave localizável. A referência a uma variável real garantirá que ela seja sempre uma chave válida, caso contrário, não será compilada.

// It's defined as computed static var, so it's up-to-date every time you call. 
// You can also have your custom retrieval method there.

button.setTitle(Localizables.login.button_title_login, forState: .Normal)

O projeto usa o Google App Script para converter o Sheets -> CSV e o script Python para converter arquivos CSV -> Localizable.strings. Você pode dar uma rápida olhada nesta planilha de exemplo para saber o que é possível.

aunnnn
fonte
1

com o iOS 7 e o Xcode 5, evite usar o método 'Localization.strings' e use o novo método de 'localização básica'. Existem alguns tutoriais, se você pesquisar no Google por 'localização básica'

Documento da Apple: localização básica

Ronny Webers
fonte
Sim Steve, isso está correto. Além disso, você ainda precisa do método de arquivo .strings para qualquer sequência gerada dinamicamente. Mas apenas para estes, o método preferido da Apple é a localização básica.
22914 Ronny Webers
Link para o novo método?
precisa
1
Imo, o método de localização base, é inútil. Você ainda precisa manter outros arquivos de localização para cadeias dinâmicas, e isso mantém suas cadeias espalhadas por muitos arquivos. Cordas dentro Tico / Storyboards pode ser localizada automaticamente as chaves em Localizable.strings com alguns liberais, como github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre
0
#define PBLocalizedString(key, val) \

[[NSBundle mainBundle] localizedStringForKey:(key) value:(val) table:nil]
baozhifei
fonte
0

Eu mesmo, muitas vezes me empolgo com a codificação, esquecendo de colocar as entradas nos arquivos .strings. Portanto, tenho scripts auxiliares para descobrir o que devo colocar novamente nos arquivos .strings e traduzir.

Como eu uso minha própria macro sobre o NSLocalizedString, revise e atualize o script antes de usar, pois eu assumi por simplicidade que nil é usado como um segundo parâmetro para o NSLocalizedString. A parte que você deseja alterar é

NSLocalizedString\(@(".*?")\s*,\s*nil\) 

Basta substituí-lo por algo que corresponda à sua macro e ao uso do NSLocalizedString.

Aí vem o script, você só precisa da Parte 3. O resto é ver mais facilmente de onde tudo vem:

// Part 1. Get keys from one of the Localizable.strings
perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings

// Part 2. Get keys from the source code
grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/'

// Part 3. Get Part 1 and 2 together.

comm -2 -3 <(grep -n -h -Eo -r  'NSLocalizedString\(@(".*?")\s*,\s*nil\)' ./ | perl -ne 'print "$1\n" if /NSLocalizedString\(@(".+")\s*,\s*nil\)/' | sort | uniq) <(perl -ne 'print "$1\n" if /^\s*(".+")\s*=/' myapp/fr.lproj/Localizable.strings | sort) | uniq >> fr-localization-delta.txt

O arquivo de saída contém chaves encontradas no código, mas não no arquivo Localizable.strings. Aqui está uma amostra:

"MPH"
"Map Direction"
"Max duration of a detailed recording, hours"
"Moving ..."
"My Track"
"New Trip"

Certamente pode ser mais polido, mas pensei em compartilhar.

Stanislav Dvoychenko
fonte