Best Practice mit NSLocalizedString

140

Ich verwende (wie alle anderen) NSLocalizedString, um meine App zu lokalisieren.

Leider gibt es einige "Nachteile" (nicht unbedingt die Schuld von NSLocalizedString selbst), einschließlich

  • Keine automatische Vervollständigung für Zeichenfolgen in Xcode. Dies macht das Arbeiten nicht nur fehleranfällig, sondern auch mühsam.
  • Möglicherweise definieren Sie eine Zeichenfolge neu, nur weil Sie nicht wussten, dass bereits eine entsprechende Zeichenfolge vorhanden ist (z. B. "Bitte Passwort eingeben" vs. "Passwort zuerst eingeben").
  • Ähnlich wie beim Autocompletion-Problem müssen Sie sich die Kommentarzeichenfolgen "merken" / kopieren, sonst erhalten Sie genstringmehrere Kommentare für eine Zeichenfolge
  • Wenn Sie verwenden möchten, genstringnachdem Sie bereits einige Zeichenfolgen lokalisiert haben, müssen Sie darauf achten, dass Ihre alten Lokalisierungen nicht verloren gehen.
  • Dieselben Zeichenfolgen sind über das gesamte Projekt verteilt. Beispielsweise haben Sie NSLocalizedString(@"Abort", @"Cancel action")überall verwendet, und dann werden Sie von Code Review aufgefordert, die Zeichenfolge umzubenennen NSLocalizedString(@"Cancel", @"Cancel action"), um den Code konsistenter zu machen.

Was ich tue (und nach einigen Suchen auf SO habe ich gedacht, dass viele Leute dies tun), ist eine separate strings.hDatei, in der ich den #definegesamten Lokalisierungscode habe. Beispielsweise

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

Dies bietet im Wesentlichen Code-Vervollständigung, einen einzigen Ort zum Ändern von Variablennamen (sodass keine Genzeichenfolge mehr erforderlich ist) und ein eindeutiges Schlüsselwort für die automatische Umgestaltung. Dies geht jedoch zu #defineLasten einer ganzen Reihe von Anweisungen, die nicht von Natur aus strukturiert sind (z. B. LocString.Common.Cancel oder ähnliches).

Obwohl dies etwas gut funktioniert, habe ich mich gefragt, wie ihr es in euren Projekten macht. Gibt es andere Ansätze, um die Verwendung von NSLocalizedString zu vereinfachen? Gibt es vielleicht sogar ein Framework, das es einschließt?

JiaYow
quelle
Ich mache es genauso wie du. Ich verwende jedoch das Makro NSLocalizedStringWithDefaultValue, um verschiedene Zeichenfolgendateien für verschiedene Lokalisierungsprobleme (wie Controller, Modelle usw.) zu erstellen und einen anfänglichen Standardwert zu erstellen.
Anka
Es scheint, dass der Export von xcode6 in die Lokalisierung die Zeichenfolgen, die als Makros in einer Header-Datei definiert sind, nicht abfängt. Kann mir jemand bestätigen oder sagen, was mir fehlen könnte? Vielen Dank...!
Juddster
@Juddster, kann bestätigen, auch mit dem neuen Fonds Editor-> Export for Localization wird es nicht in der Header-Datei aufgenommen
Red

Antworten:

100

NSLocalizedStringEs gibt einige Einschränkungen, aber es ist für Cocoa so zentral, dass es nicht zumutbar ist, benutzerdefinierten Code für die Lokalisierung zu schreiben, was bedeutet, dass Sie ihn verwenden müssen. Trotzdem kann ein wenig Werkzeug helfen. So gehe ich vor:

Aktualisieren der Zeichenfolgendatei

genstringsÜberschreibt Ihre String-Dateien und verwirft alle Ihre vorherigen Übersetzungen. Ich habe update_strings.py geschrieben , um die alte Zeichenfolgendatei zu analysieren, auszuführen genstringsund die Lücken auszufüllen, damit Sie Ihre vorhandenen Übersetzungen nicht manuell wiederherstellen müssen. Das Skript versucht, die vorhandenen Zeichenfolgendateien so genau wie möglich abzugleichen, um zu vermeiden, dass beim Aktualisieren ein zu großer Unterschied auftritt.

Benennen Sie Ihre Saiten

Wenn Sie NSLocalizedStringwie angegeben verwenden:

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

Möglicherweise definieren Sie dieselbe Zeichenfolge in einem anderen Teil Ihres Codes, was zu Konflikten führen kann, da derselbe englische Begriff in verschiedenen Kontexten unterschiedliche Bedeutungen haben kann ( OKund Cancelin den Sinn kommt). Deshalb verwende ich immer einen bedeutungslosen All-Caps-String mit einem modulspezifischen Präfix und einer sehr genauen Beschreibung:

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

Verwenden derselben Zeichenfolge an verschiedenen Stellen

Wenn Sie dieselbe Zeichenfolge mehrmals verwenden, können Sie entweder ein Makro wie zuvor verwenden oder es als Instanzvariable in Ihrem View Controller oder Ihrer Datenquelle zwischenspeichern. Auf diese Weise müssen Sie die Beschreibung nicht wiederholen, die möglicherweise veraltet und zwischen Instanzen derselben Lokalisierung inkonsistent wird, was immer verwirrend ist. Da Instanzvariablen Symbole sind, können Sie die automatische Vervollständigung für diese am häufigsten verwendeten Übersetzungen verwenden und "manuelle" Zeichenfolgen für die spezifischen Übersetzungen verwenden, die ohnehin nur einmal vorkommen würden.

Ich hoffe, dass Sie mit diesen Tipps produktiver mit der Kakaolokalisierung umgehen können!

ndfred
quelle
Vielen Dank für Ihre Antwort, ich werde auf jeden Fall einen Blick auf Ihre Python-Datei werfen. Ich stimme Ihren Namenskonventionen zu. Ich habe kürzlich mit einigen anderen iOS-Entwicklern gesprochen, und sie haben die Verwendung statischer Zeichenfolgen anstelle von Makros empfohlen, was sinnvoll ist. Ich habe Ihre Antwort positiv bewertet, werde aber etwas warten, bevor ich sie akzeptiere, da die Lösung immer noch etwas ungeschickt ist. Vielleicht kommt etwas Besseres. Danke noch einmal!
JiaYow
Gern geschehen. Die Lokalisierung ist ein langwieriger Prozess. Die richtigen Tools und Arbeitsabläufe machen einen großen Unterschied.
ndfred
17
Ich habe nie verstanden, warum Lokalisierungsfunktionen im gettext-Stil eine der Übersetzungen als Schlüssel verwenden. Was passiert, wenn sich Ihr Originaltext ändert? Ihre Schlüsseländerungen und alle Ihre lokalisierten Dateien verwenden den alten Text für ihren Schlüssel. Es hat für mich nie Sinn gemacht. Ich habe immer Schlüssel wie "home_button_text" verwendet, damit sie eindeutig sind und sich nie ändern. Ich habe auch ein Bash-Skript geschrieben, um alle meine Localizable.strings-Dateien zu analysieren und eine Klassendatei mit statischen Methoden zu generieren, die die entsprechende Zeichenfolge laden. Dies gibt mir Code-Vervollständigung. Eines Tages könnte ich dies Open Source.
Mike Weller
2
Ich denke du meinst genstringsnicht gestring.
Hiroshi
1
@ndfred Die Kompilierungszeit überprüft, ob Sie die Zeichenfolge nicht falsch eingegeben haben. Dies ist der größte Gewinn. Es ist sowieso etwas mehr Code hinzuzufügen. Auch im Fall von Refactoring und statischer Analyse wird das Vorhandensein eines Symbols das Warten erleichtern.
Allen Zeng
31

Für die automatische Vervollständigung von Zeichenfolgen in Xcode können Sie https://github.com/questbeat/Lin verwenden .

Hiroshi
quelle
3
Das ist eigentlich ziemlich erstaunlich. Es müssen keine Makros erstellt werden.
Beau Nouvelle
1
Seite nicht gefunden_
Juanmi
1
@ Juanmi Danke, dass du den toten Link erwähnt hast. Ich habe den Link durch die Github-URL ersetzt.
Hiroshi
24

Stimmen Sie mit ndfred überein, aber ich möchte Folgendes hinzufügen:

Der zweite Parameter kann als ... Standardwert verwendet werden !!

(NSLocalizedStringWithDefaultValue funktioniert nicht richtig mit genstring, deshalb habe ich diese Lösung vorgeschlagen)

Hier ist meine benutzerdefinierte Implementierung, die NSLocalizedString verwendet und einen Kommentar als Standardwert verwendet:

1. Definieren Sie in Ihrem vorkompilierten Header (.pch-Datei) das Makro 'NSLocalizedString' neu:

// 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. Erstellen Sie eine Klasse, um den Lokalisierungshandler zu implementieren

#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. Verwenden Sie es!

Stellen Sie sicher, dass Sie in Ihren App-Erstellungsphasen ein Ausführungsskript hinzufügen, damit Ihre Localizable.strings-Datei bei jedem Build aktualisiert wird. Das heißt, eine neue lokalisierte Zeichenfolge wird in Ihre Localized.strings-Datei eingefügt:

Mein Build-Phase-Skript ist ein Shell-Skript:

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

Wenn Sie also diese neue Zeile in Ihren Code einfügen:

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

Führen Sie dann einen Build durch. Ihre ./Localizable.scripts-Datei enthält diese neue Zeile:

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

Und da key == value für 'view_settings_title' ist, gibt der benutzerdefinierte LocalizedStringHandler den Kommentar zurück, dh 'Settings ".

Voilà :-)

Pascal
quelle
ARC-Fehler erhalten, Keine bekannte Instanzmethode für den Selektor 'localizedString: comment
::
Ich nehme an, es liegt daran, dass LocalizationHandlerUtil.h fehlt. Ich kann den Code nicht zurückfinden ... Versuchen Sie einfach, die Header-Datei LocalizationHandlerUtil.h zu erstellen, und es sollte in Ordnung sein
Pascal
Ich habe die Dateien erstellt. Ich denke, es liegt an einem Problem mit dem Ordnerpfad.
Mangesh
3

In Swift verwende ich Folgendes, z. B. für die Schaltfläche "Ja" in diesem Fall:

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

Beachten Sie die Verwendung von value:für den Standardtextwert. Der erste Parameter dient als Übersetzungs-ID. Der Vorteil der Verwendung des value:Parameters besteht darin, dass der Standardtext später geändert werden kann, die Übersetzungs-ID jedoch gleich bleibt. Die Datei Localizable.strings enthält"btn_yes" = "Yes";

Wenn der value:Parameter nicht verwendet wurde, wird der erste Parameter sowohl für die Übersetzungs-ID als auch für den Standardtextwert verwendet. Die Datei Localizable.strings würde enthalten "Yes" = "Yes";. Diese Art der Verwaltung von Lokalisierungsdateien scheint seltsam zu sein. Insbesondere wenn der übersetzte Text lang ist, ist auch die ID lang. Wenn ein Zeichen des Standardtextwerts geändert wird, wird auch die Übersetzungs-ID geändert. Dies führt zu Problemen bei der Verwendung externer Übersetzungssysteme. Unter Ändern der Übersetzungs-ID wird das Hinzufügen eines neuen Übersetzungstextes verstanden, was möglicherweise nicht immer erwünscht ist.

petrsyn
quelle
2

Ich habe ein Skript geschrieben, um Localizable.strings in mehreren Sprachen zu verwalten. Während es bei der automatischen Vervollständigung nicht hilft, hilft es, .strings-Dateien mit dem folgenden Befehl zusammenzuführen:

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

Weitere Informationen finden Sie unter: https://github.com/hiroshi/merge_strings

Einige von Ihnen finden es nützlich, hoffe ich.

Hiroshi
quelle
2

Wenn jemand nach einer Swift-Lösung sucht. Vielleicht möchten Sie sich meine Lösung ansehen, die ich hier zusammengestellt habe: SwiftyLocalization

Mit wenigen Schritten zum Einrichten haben Sie eine sehr flexible Lokalisierung in Google Spreadsheet (Kommentar, benutzerdefinierte Farbe, Hervorhebung, Schriftart, mehrere Blätter und mehr).

Kurz gesagt, sind die Schritte: Google Spreadsheet -> CSV-Dateien -> Localizable.strings

Darüber hinaus wird Localizables.swift generiert, eine Struktur, die für Sie als Schnittstelle zum Abrufen und Dekodieren von Schlüsseln fungiert (Sie müssen jedoch manuell angeben, wie ein String vom Schlüssel dekodiert werden soll).

Warum ist das toll?

  1. Sie müssen nicht mehr überall einen Schlüssel als einfache Zeichenfolge haben.
  2. Bei der Kompilierung werden falsche Schlüssel erkannt.
  3. Xcode kann automatisch vervollständigen.

Zwar gibt es Tools, mit denen Sie Ihren lokalisierbaren Schlüssel automatisch vervollständigen können. Durch die Bezugnahme auf eine echte Variable wird sichergestellt, dass es sich immer um einen gültigen Schlüssel handelt, andernfalls wird er nicht kompiliert.

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

Das Projekt verwendet Google App Script zum Konvertieren von Blättern -> CSV und Python-Skript zum Konvertieren von CSV-Dateien -> Localizable.strings. In diesem Beispielblatt können Sie einen kurzen Blick darauf werfen, was möglich ist.

aunnnn
quelle
1

Unter iOS 7 und Xcode 5 sollten Sie die Methode 'Localization.strings' vermeiden und die neue Methode 'base localization' verwenden. Es gibt einige Tutorials, wenn Sie nach "Basislokalisierung" googeln.

Apple-Dokument: Basislokalisierung

Ronny Webers
quelle
ja Steve das ist richtig. Außerdem benötigen Sie weiterhin die Dateimethode .strings für jede dynamisch generierte Zeichenfolge. Aber nur für diese ist Apples bevorzugte Methode die Basislokalisierung.
Ronny Webers
Link zur neuen Methode?
Übertreibung
1
Imo ist die Basislokalisierungsmethode wertlos. Sie müssen noch andere Speicherortdateien für dynamische Zeichenfolgen aufbewahren, und Ihre Zeichenfolgen werden über viele Dateien verteilt. Strings in Nibs / Storyboards können automatisch auf Schlüssel in Localizable.strings mit einigen Libs wie github.com/AliSoftware/OHAutoNIBi18n
Rafael Nobre
0
#define PBLocalizedString(key, val) \

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

Ich selbst werde oft von der Codierung mitgerissen und vergesse, die Einträge in .strings-Dateien zu speichern. Daher habe ich Hilfsskripte, um herauszufinden, was ich schulde, um sie wieder in .strings-Dateien zu speichern und zu übersetzen.

Da ich mein eigenes Makro über NSLocalizedString verwende, überprüfen und aktualisieren Sie das Skript, bevor Sie es verwenden, da ich der Einfachheit halber angenommen habe, dass nil als zweiter Parameter für NSLocalizedString verwendet wird. Der Teil, den Sie ändern möchten, ist

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

Ersetzen Sie es einfach durch etwas, das Ihrer Makro- und NSLocalizedString-Verwendung entspricht.

Hier kommt das Skript, Sie brauchen in der Tat nur Teil 3. Der Rest ist einfacher zu sehen, woher alles kommt:

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

Die Ausgabedatei enthält Schlüssel, die im Code gefunden wurden, jedoch nicht in der Datei Localizable.strings. Hier ist ein Beispiel:

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

Sicher kann mehr poliert werden, dachte aber ich würde teilen.

Stanislav Dvoychenko
quelle