Best practice utilizzando NSLocalizedString


140

Sto (come tutti gli altri) usando NSLocalizedStringper localizzare la mia app.

Sfortunatamente, ci sono diversi "svantaggi" (non necessariamente colpa di NSLocalizedString stesso), incluso

  • Nessuna compilazione automatica per le stringhe in Xcode. Ciò rende il lavoro non solo soggetto a errori ma anche noioso.
  • Potresti finire per ridefinire una stringa semplicemente perché non sapevi che esistesse già una stringa equivalente (es. "Inserisci la password" anziché "Inserisci prima la password")
  • Analogamente al problema del completamento automatico, è necessario "ricordare" / copiare le stringhe di commento, altrimenti genstringfiniranno con più commenti per una stringa
  • Se si desidera utilizzare genstringdopo aver già localizzato alcune stringhe, è necessario fare attenzione a non perdere le vecchie localizzazioni.
  • Le stesse stringhe sono sparse per l'intero progetto. Ad esempio, hai usato NSLocalizedString(@"Abort", @"Cancel action")ovunque, quindi Code Review ti chiede di rinominare la stringa NSLocalizedString(@"Cancel", @"Cancel action")per rendere il codice più coerente.

Quello che faccio (e dopo alcune ricerche su SO ho immaginato che molte persone lo facciano) è avere un strings.hfile separato in cui ho #definetutto il codice localizzato. Per esempio

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

Ciò fornisce essenzialmente il completamento del codice, un unico posto per cambiare i nomi delle variabili (quindi non c'è più bisogno di genstring) e una parola chiave unica per il refactoring automatico. Tuttavia, questo ha il costo di finire con un mucchio di #defineaffermazioni che non sono intrinsecamente strutturate (cioè come LocString.Common.Cancel o qualcosa del genere).

Quindi, mentre funziona un po 'bene, mi chiedevo come lo facciate nei vostri progetti. Esistono altri approcci per semplificare l'uso di NSLocalizedString? C'è forse anche un framework che lo incapsula?


Lo faccio quasi come te. Ma sto usando il makro NSLocalizedStringWithDefaultValue per creare diversi file di stringhe per diversi problemi di localizzazione (come controller, modelli, ecc.) E per creare un valore predefinito iniziale.
anka,

Sembra che l'esportazione verso la localizzazione di xcode6 non catturi le stringhe definite come macro in un file di intestazione. Qualcuno può confermare o dirmi cosa potrei perdere? Grazie...!
Juddster,

@Juddster, posso confermare, anche con il nuovo fondo Editor-> Esporta per localizzazione non viene raccolto nel file di intestazione
Red

Risposte:


100

NSLocalizedStringha alcune limitazioni, ma è così centrale per Cocoa che è irragionevole scrivere codice personalizzato per gestire la localizzazione, il che significa che dovrai usarlo. Detto questo, un piccolo attrezzo può aiutare, ecco come procedere:

Aggiornamento del file di stringhe

genstringssovrascrive i tuoi file di stringa, scartando tutte le tue traduzioni precedenti. Ho scritto update_strings.py per analizzare il vecchio file di stringhe, eseguire genstringse riempire gli spazi vuoti in modo da non dover ripristinare manualmente le traduzioni esistenti. Lo script tenta di abbinare i file di stringa esistenti il ​​più vicino possibile per evitare di avere una differenza troppo grande durante l'aggiornamento.

Dai un nome alle tue corde

Se usi NSLocalizedStringcome pubblicizzato:

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

Potresti finire per definire la stessa stringa in un'altra parte del tuo codice, che potrebbe essere in conflitto poiché lo stesso termine inglese potrebbe avere un significato diverso in contesti diversi ( OKe Cancelvenire in mente). Ecco perché uso sempre una stringa senza maiuscole senza significato con un prefisso specifico del modulo e una descrizione molto precisa:

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

Utilizzando la stessa stringa in luoghi diversi

Se si utilizza più volte la stessa stringa, è possibile utilizzare una macro come in precedenza oppure memorizzarla nella cache come variabile di istanza nel controller di visualizzazione o nell'origine dati. In questo modo non dovrai ripetere la descrizione che potrebbe diventare stantia e incoerente tra le istanze della stessa localizzazione, il che è sempre fonte di confusione. Poiché le variabili di istanza sono simboli, sarai in grado di utilizzare il completamento automatico su queste traduzioni più comuni e di utilizzare stringhe "manuali" per quelle specifiche, che si verificherebbero comunque solo una volta.

Spero che sarai più produttivo con la localizzazione del cacao con questi suggerimenti!


Grazie per la tua risposta, darò sicuramente un'occhiata al tuo file Python. Sono d'accordo con le tue convenzioni di denominazione. Di recente ho parlato con altri sviluppatori iOS e mi hanno consigliato l'uso di stringhe statiche anziché di macro, il che ha senso. Ho votato a favore della tua risposta, ma aspetterò un po 'prima di accettarla, perché la soluzione è ancora un po' goffa. Forse arriva qualcosa di meglio. Grazie ancora!
JiaYow,

Prego. La localizzazione è un processo noioso, avere gli strumenti e il flusso di lavoro giusti fa la differenza.
ndfred

17
Non ho mai capito perché le funzioni di localizzazione in stile gettext usano una delle traduzioni come chiave. Cosa succede se il testo originale cambia? Le tue chiavi cambiano e tutti i tuoi file localizzati utilizzano il vecchio testo per la loro chiave. Non ha mai avuto senso per me. Ho sempre usato chiavi come "home_button_text", quindi sono uniche e non cambiano mai. Ho anche scritto uno script bash per analizzare tutti i miei file Localizable.strings e generare un file di classe con metodi statici che caricherà la stringa appropriata. Questo mi dà il completamento del codice. Un giorno potrei open source questo.
Mike Weller,

2
Penso che genstringsnon intendi gestring.
Hiroshi,

1
La verifica del tempo di compilazione di @ndfred che non hai digitato la stringa in modo errato è la vincita più grande. È comunque leggermente più codice da aggiungere. Anche nel caso del refactoring, l'analisi statica, avere un simbolo renderà le cose più facili.
Allen Zeng,


24

Concordo con ndfred, ma vorrei aggiungere questo:

Il secondo parametro può essere utilizzato come ... valore predefinito !!

(NSLocalizedStringWithDefaultValue non funziona correttamente con genstring, ecco perché ho proposto questa soluzione)

Ecco la mia implementazione personalizzata che utilizza NSLocalizedString che utilizza il commento come valore predefinito:

1 Nell'intestazione precompilata (file .pch), ridefinisci la 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. creare una classe per implementare il gestore di localizzazione

#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. Usalo!

Assicurati di aggiungere uno script Esegui nelle fasi di build dell'app in modo che il file Localizable.strings venga aggiornato ad ogni build, ovvero, una nuova stringa localizzata verrà aggiunta nel file Localized.strings:

La mia fase di compilazione Script è uno script di shell:

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

Quindi quando aggiungi questa nuova riga nel tuo codice:

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

Quindi esegui una build, il tuo file ./Localizable.scripts conterrà questa nuova riga:

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

E poiché key == valore per "view_settings_title", LocalizedStringHandler personalizzato restituirà il commento, ovvero "Impostazioni"

Ecco :-)


Ottenere errori ARC, nessun metodo di istanza noto per il selettore 'localizedString: commento: :(
Mangesh

Suppongo sia perché manca LocalizationHandlerUtil.h. Non riesco a trovare il codice indietro ... Prova a creare il file di intestazione LocalizationHandlerUtil.h e dovrebbe essere OK
Pascal

Ho creato i file. Penso che sia dovuto al problema del percorso della cartella.
Mangesh,

3

In Swift sto usando quanto segue, ad esempio per il pulsante "Sì" in questo caso:

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

Nota l'uso di value:per il valore di testo predefinito. Il primo parametro funge da ID di traduzione. Il vantaggio dell'utilizzo del value:parametro è che il testo predefinito può essere modificato in un secondo momento, ma l'ID di traduzione rimane lo stesso. Il file Localizable.strings conterrà"btn_yes" = "Yes";

Se il value:parametro non fosse utilizzato, il primo parametro verrebbe utilizzato sia per l'ID della traduzione sia per il valore di testo predefinito. Il file Localizable.strings conterrà "Yes" = "Yes";. Questo tipo di gestione dei file di localizzazione sembra essere strano. Soprattutto se il testo tradotto è lungo, anche l'ID è lungo. Ogni volta che viene modificato qualsiasi carattere del valore di testo predefinito, viene modificato anche l'ID di traduzione. Ciò porta a problemi quando si utilizzano sistemi di traduzione esterni. La modifica dell'ID di traduzione si intende come l'aggiunta di un nuovo testo di traduzione, che potrebbe non essere sempre desiderato.


2

Ho scritto una sceneggiatura per aiutare a mantenere Localizable.strings in più lingue. Anche se non aiuta nel completamento automatico, aiuta a unire i file .strings usando il comando:

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

Per maggiori informazioni, consultare: https://github.com/hiroshi/merge_strings

Alcuni di voi lo trovano utile, spero.


2

Se qualcuno è alla ricerca di una soluzione Swift. Potresti voler dare un'occhiata alla mia soluzione che ho messo qui: SwiftyLocalization

Con pochi passaggi per l'installazione, avrai una localizzazione molto flessibile in Google Spreadsheet (commento, colore personalizzato, evidenziazione, font, più fogli e altro).

In breve, i passaggi sono: Foglio di calcolo di Google -> File CSV -> Localizable.strings

Inoltre, genera anche Localizables.swift, una struttura che funge da interfaccia per il recupero e la decodifica delle chiavi per te (devi specificare manualmente un modo per decodificare String dalla chiave).

Perché è fantastico?

  1. Non è più necessario disporre di una chiave come semplice stringa in tutti i punti.
  2. Le chiavi errate vengono rilevate al momento della compilazione.
  3. Xcode può eseguire il completamento automatico.

Mentre ci sono strumenti che possono completare automaticamente la chiave localizzabile. Il riferimento a una variabile reale garantirà che sia sempre una chiave valida, altrimenti non verrà compilata.

// 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)

Il progetto utilizza Google App Script per convertire Fogli -> CSV e Python script per convertire file CSV -> Localizable.strings Puoi dare una rapida occhiata a questo foglio di esempio per sapere cosa è possibile.


1

con iOS 7 e Xcode 5, dovresti evitare di utilizzare il metodo "Localization.strings" e utilizzare il nuovo metodo "localizzazione di base". Ci sono alcuni tutorial in giro se cerchi Google per 'localizzazione di base'

Documento Apple: localizzazione di base


si Steve è corretto. Inoltre, è ancora necessario il metodo del file .strings per qualsiasi stringa generata dinamicamente. Ma solo per questi, il metodo preferito di Apple è la localizzazione di base.
Ronny Webers,

Link al nuovo metodo?
Iperbole,

1
Imo il metodo di localizzazione di base è inutile. Devi ancora conservare altri file di posizione per stringhe dinamiche e mantiene le tue stringhe distribuite su molti file. Le stringhe all'interno di pennini / storyboard possono essere localizzate automaticamente in chiavi in ​​Localizable.strings con alcune librerie come github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre,

0
#define PBLocalizedString(key, val) \

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

0

Io stesso sono spesso portato via con la codifica, dimenticando di inserire le voci nei file .strings. Quindi ho degli script di aiuto per trovare cosa devo reinserire nei file .strings e tradurre.

Mentre utilizzo la mia macro su NSLocalizedString, ti preghiamo di rivedere e aggiornare lo script prima di utilizzare, come ho ipotizzato per semplicità, che zero viene utilizzato come secondo parametro di NSLocalizedString. La parte che vorresti cambiare è

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

Sostituiscilo semplicemente con qualcosa che corrisponda alla tua macro e all'utilizzo di NSLocalizedString.

Ecco la sceneggiatura, in effetti hai solo bisogno della terza parte. Il resto è vedere più facilmente da dove proviene tutto:

// 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

Il file di output contiene le chiavi trovate nel codice, ma non nel file Localizable.strings. Ecco un esempio:

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

Certamente può essere lucidato di più, ma ho pensato di condividere.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.