Najlepsza praktyka przy użyciu NSLocalizedString


140

Używam (jak wszyscy inni) NSLocalizedStringdo lokalizowania mojej aplikacji.

Niestety, istnieje kilka „wad” (niekoniecznie wina samego NSLocalizedString), w tym

  • Brak autouzupełniania dla ciągów w Xcode. To sprawia, że ​​praca jest nie tylko podatna na błędy, ale także męcząca.
  • Może się zdarzyć, że zmienisz definicję ciągu tylko dlatego, że nie wiedziałeś, że istnieje już równoważny ciąg (np. „Wprowadź hasło” zamiast „Najpierw wprowadź hasło”)
  • Podobnie jak w przypadku problemu z autouzupełnianiem, musisz "zapamiętać" / skopiować ciągi komentarzy, w przeciwnym razie genstringskończy się wiele komentarzy dla jednego ciągu
  • Jeśli chcesz używać genstringpo zlokalizowaniu niektórych ciągów, musisz uważać, aby nie stracić starych lokalizacji.
  • Te same struny są rozrzucone po całym projekcie. Na przykład był używany NSLocalizedString(@"Abort", @"Cancel action")wszędzie, a następnie Code Review prosi o zmianę nazwy ciągu NSLocalizedString(@"Cancel", @"Cancel action")na, aby kod był bardziej spójny.

To, co robię (i po kilku poszukiwaniach na SO, doszedłem do wniosku, że wiele osób to robi), to posiadanie oddzielnego strings.hpliku, w którym mam #definecały kod lokalizacji. Na przykład

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

Zasadniczo zapewnia to uzupełnianie kodu, pojedyncze miejsce do zmiany nazw zmiennych (więc nie ma już potrzeby tworzenia genstringów) oraz unikalne słowo kluczowe do automatycznej refaktoryzacji. Jednak odbywa się to kosztem zakończenia całej masy #defineinstrukcji, które nie są z natury uporządkowane (np. Jak LocString.Common.Cancel lub coś w tym rodzaju).

Tak więc, chociaż działa to dość dobrze, zastanawiałem się, jak robicie to w swoich projektach. Czy istnieją inne metody upraszczania korzystania z NSLocalizedString? Czy może w ogóle istnieje struktura, która ją zawiera?


Po prostu robię to prawie tak samo jak ty. Ale używam makro NSLocalizedStringWithDefaultValue do tworzenia różnych plików ciągów dla różnych problemów lokalizacyjnych (takich jak kontrolery, modele itp.) I do tworzenia początkowej wartości domyślnej.
anka

Wygląda na to, że funkcja Eksportuj do lokalizacji xcode6 nie przechwytuje ciągów, które są zdefiniowane jako makra w pliku nagłówkowym. Czy ktoś może potwierdzić lub powiedzieć, czego mi brakuje? Dzięki...!
Juddster

@Juddster, może potwierdzić, nawet z nowym edytorem funduszu-> Export for Localization nie jest zapisywany w pliku nagłówkowym
Red

Odpowiedzi:


100

NSLocalizedStringma kilka ograniczeń, ale jest tak centralny dla Cocoa, że ​​pisanie własnego kodu do obsługi lokalizacji jest nierozsądne, co oznacza, że ​​będziesz musiał go użyć. To powiedziawszy, trochę narzędzi może pomóc, oto jak postępuję:

Aktualizacja pliku ciągów

genstringsnadpisuje twoje pliki łańcuchowe, odrzucając wszystkie poprzednie tłumaczenia. Napisałem update_strings.py, aby przeanalizować stary plik ciągów, uruchomić genstringsi wypełnić puste miejsca, aby nie trzeba było ręcznie przywracać istniejących tłumaczeń. Skrypt próbuje jak najdokładniej dopasować istniejące pliki łańcuchowe, aby uniknąć zbyt dużego różnicowania podczas ich aktualizacji.

Nazywanie twoich strun

Jeśli używasz NSLocalizedStringzgodnie z reklamą:

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

Może się zdarzyć, że zdefiniujesz ten sam ciąg w innej części kodu, co może być sprzeczne, ponieważ ten sam angielski termin może mieć różne znaczenie w różnych kontekstach ( OKi Cancelprzyjdzie Ci do głowy). Dlatego zawsze używam nic nie znaczącego, składającego się z wielkich liter z przedrostkiem specyficznym dla modułu i bardzo dokładnym opisem:

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

Używanie tego samego ciągu w różnych miejscach

Jeśli używasz tego samego ciągu wiele razy, możesz użyć makra tak jak wcześniej lub buforować je jako zmienną wystąpienia w kontrolerze widoku lub źródle danych. W ten sposób nie będziesz musiał powtarzać opisu, który może stać się nieaktualny i niespójny między instancjami tej samej lokalizacji, co zawsze jest mylące. Ponieważ zmienne instancji są symbolami, będziesz mógł używać autouzupełniania w tych najpopularniejszych tłumaczeniach i używać ciągów "ręcznych" dla konkretnych, co i tak wystąpiłoby tylko raz.

Mam nadzieję, że dzięki tym wskazówkom będziesz bardziej produktywny z lokalizacją Cocoa!


Dziękuję za odpowiedź, na pewno przyjrzę się Twojemu plikowi Pythona. Zgadzam się z twoimi konwencjami nazewnictwa. Niedawno rozmawiałem z kilkoma innymi programistami iOS i zalecili oni użycie statycznych ciągów znaków zamiast makr, co ma sens. Głosowałem za twoją odpowiedzią, ale poczekam trochę, zanim ją przyjmuję, ponieważ rozwiązanie jest nadal trochę niezdarne. Może przyjdzie coś lepszego. Dzięki jeszcze raz!
JiaYow,

Nie ma za co. Lokalizacja to żmudny proces, a posiadanie odpowiednich narzędzi i przepływu pracy robi ogromną różnicę.
ndfred

17
Nigdy nie rozumiałem, dlaczego funkcje lokalizacyjne w stylu gettext używają jednego z tłumaczeń jako klucza. Co się stanie, jeśli oryginalny tekst się zmieni? Twoje kluczowe zmiany i wszystkie zlokalizowane pliki używają starego tekstu jako klucza. To nigdy nie miało dla mnie sensu. Zawsze używałem klawiszy typu „home_button_text”, więc są one niepowtarzalne i nigdy się nie zmieniają. Napisałem również skrypt bash, aby przeanalizować wszystkie moje pliki Localizable.strings i wygenerować plik klasy za pomocą metod statycznych, które załadują odpowiedni ciąg. To daje mi dokończenie kodu. Pewnego dnia mogę to otworzyć.
Mike Weller

2
Myślę, że masz na myśli genstringsnie gestring.
Hiroshi

1
Czas kompilacji @ndfred sprawdza, czy nie wpisałeś łańcucha źle, jest największą wygraną. I tak jest trochę więcej kodu do dodania. Również w przypadku refaktoryzacji, analizy statycznej, posiadanie symbolu ułatwi sprawę.
Allen Zeng,


24

Zgadzam się z ndfred, ale chciałbym to dodać:

Drugi parametr może być użyty jako ... wartość domyślna !!

(NSLocalizedStringWithDefaultValue nie działa poprawnie z genstringiem, dlatego zaproponowałem to rozwiązanie)

Oto moja implementacja niestandardowa, która używa NSLocalizedString, która używa komentarza jako wartości domyślnej:

1. We wstępnie skompilowanym nagłówku (pliku .pch) przedefiniuj makro „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. utworzyć klasę do implementacji modułu obsługi lokalizacji

#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. Użyj tego!

Upewnij się, że dodałeś skrypt uruchamiania w fazach tworzenia aplikacji, aby plik Localizable.strings był aktualizowany przy każdej kompilacji, tj. Nowy zlokalizowany ciąg zostanie dodany do pliku Localized.strings:

Mój skrypt fazy budowania to skrypt powłoki:

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

Więc kiedy dodasz ten nowy wiersz w swoim kodzie:

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

Następnie wykonaj kompilację, twój plik ./Localizable.scripts będzie zawierał tę nową linię:

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

A ponieważ klucz == wartość dla „view_settings_title”, niestandardowy LocalizedStringHandler zwróci komentarz, tj. „Ustawienia”

Voilà :-)


Pobieranie błędów ARC, brak znanej metody instancji dla selektora 'localizedString: comment: :(
Mangesh

Przypuszczam, że dzieje się tak, ponieważ brakuje LocalizationHandlerUtil.h. Nie mogę znaleźć kodu z powrotem ... Po prostu spróbuj utworzyć plik nagłówkowy LocalizationHandlerUtil.h i powinno być OK
Pascal

Utworzyłem pliki. Myślę, że jest to spowodowane problemem ze ścieżką folderu.
Mangesh

3

W Swift używam np. Przycisku „Tak” w tym przypadku:

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

Zwróć uwagę na użycie value:jako domyślnej wartości tekstowej. Pierwszy parametr służy jako identyfikator tłumaczenia. Zaletą używania value:parametru jest to, że domyślny tekst można zmienić później, ale identyfikator tłumaczenia pozostaje taki sam. Plik Localizable.strings będzie zawierał"btn_yes" = "Yes";

Jeśli value:parametr nie został użyty, to pierwszy parametr byłby używany zarówno dla identyfikatora tłumaczenia, jak i dla domyślnej wartości tekstowej. Plik Localizable.strings będzie zawierał "Yes" = "Yes";. Ten sposób zarządzania plikami lokalizacyjnymi wydaje się dziwny. Zwłaszcza jeśli przetłumaczony tekst jest długi, identyfikator również jest długi. Za każdym razem, gdy zmienia się dowolny znak domyślnej wartości tekstowej, zmienia się również identyfikator tłumaczenia. Prowadzi to do problemów, gdy używane są zewnętrzne systemy tłumaczeniowe. Zmiana identyfikatora tłumaczenia jest rozumiana jako dodanie nowego tekstu tłumaczenia, co nie zawsze jest pożądane.


2

Napisałem skrypt, aby pomóc w utrzymaniu Localizable.strings w wielu językach. Chociaż nie pomaga to w autouzupełnianiu, pomaga scalić pliki .strings za pomocą polecenia:

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

Aby uzyskać więcej informacji, zobacz: https://github.com/hiroshi/merge_strings

Mam nadzieję, że niektórzy z was uznają to za przydatne.


2

Jeśli ktoś szuka szybkiego rozwiązania. Możesz sprawdzić moje rozwiązanie, które stworzyłem tutaj: SwiftyLocalization

Dzięki kilku krokom konfiguracji uzyskasz bardzo elastyczną lokalizację w arkuszu kalkulacyjnym Google (komentarz, niestandardowy kolor, wyróżnienie, czcionka, wiele arkuszy i więcej).

Krótko mówiąc, kroki to: Arkusz kalkulacyjny Google -> Pliki CSV -> Localizable.strings

Co więcej, generuje również Localizables.swift, strukturę, która działa jak interfejsy do pobierania i dekodowania klucza (chociaż musisz ręcznie określić sposób dekodowania ciągu znaków z klucza).

Dlaczego to jest świetne?

  1. Nie potrzebujesz już klucza w postaci zwykłego sznurka w każdym miejscu.
  2. W czasie kompilacji wykryto nieprawidłowe klucze.
  3. Xcode może wykonywać autouzupełnianie.

Chociaż istnieją narzędzia, które mogą automatycznie uzupełniać lokalizowalny klucz. Odniesienie do rzeczywistej zmiennej zapewni, że jest to zawsze prawidłowy klucz, w przeciwnym razie nie będzie się kompilować.

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

Projekt wykorzystuje Google App Script do konwersji Arkuszy -> CSV i skryptu Python do konwersji plików CSV -> Localizable.strings. Możesz rzucić okiem na ten przykładowy arkusz, aby dowiedzieć się, co jest możliwe.


1

w iOS 7 i Xcode 5 należy unikać stosowania metody „Localization.strings” i używać nowej metody „podstawowej lokalizacji”. Istnieje kilka samouczków, jeśli wyszukujesz w Google `` lokalizację podstawową ''

Dokument Apple: lokalizacja podstawowa


tak Steve, zgadza się. Ponadto nadal potrzebujesz metody pliku .strings dla każdego dynamicznie generowanego ciągu. Ale tylko w tych przypadkach preferowaną metodą Apple jest lokalizacja bazowa.
Ronny Webers

Link do nowej metody?
Hiperbola

1
Imo podstawowa metoda lokalizacji jest bezwartościowa. Nadal musisz zachować inne pliki lokalizacji dla ciągów dynamicznych, a to sprawia, że ​​ciągi są rozłożone na wiele plików. Ciągi znaków wewnątrz Nibs / Storyboardów mogą być lokalizowane automatycznie do kluczy w Localizable.strings w przypadku niektórych bibliotek,
Rafael Nobre

0
#define PBLocalizedString(key, val) \

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

0

Ja sam często daję się ponieść kodowaniu, zapominając o umieszczaniu wpisów w plikach .strings. W ten sposób mam skrypty pomocnicze, aby dowiedzieć się, co powinienem umieścić z powrotem w plikach .strings i przetłumaczyć.

Ponieważ używam własnego makra zamiast NSLocalizedString, proszę przejrzeć i zaktualizować skrypt przed użyciem, jak założyłem dla uproszczenia, że nil jest używany jako drugi parametr NSLocalizedString. Część, którą chciałbyś zmienić, to

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

Po prostu zastąp go czymś, co pasuje do makra i użycia NSLocalizedString.

Oto skrypt, naprawdę potrzebujesz tylko części 3. Reszta to łatwiejsze zobaczenie, skąd to wszystko się bierze:

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

Plik wyjściowy zawiera klucze znalezione w kodzie, ale nie w pliku Localizable.strings. Oto próbka:

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

Z pewnością można go bardziej polerować, ale pomyślałem, że podzielę się.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.