Co to jest odpowiednik NSLocalizedString w Swift?

228

Czy istnieje odpowiednik Swift NSLocalizedString(...)? W Objective-Czwykle używamy:

NSString *string = NSLocalizedString(@"key", @"comment");

Jak mogę to samo osiągnąć w Swift? Znalazłem funkcję:

func NSLocalizedString(
    key: String,
    tableName: String? = default,
    bundle: NSBundle = default,
    value: String = default,
    #comment: String) -> String

Jest jednak bardzo długi i wcale nie jest wygodny.

RaffAl
źródło
2
Najlepiej jest utworzyć krótszą wersję fragmentu kodu: NSLocalizedString („”, komentarz: „”) ... Podobało mi się rozszerzenie, ale problem polega na tym, że ciągi znaków nie przechwytują tych ciągów do pliku tłumaczenia.
Matej Ukmar,
3
W Swift 3 możesz po prostu NSLocalizedString("Cancel", comment: "Cancel button title")skorzystać z domyślnych wartości. Myślę, że to wygodne.
LShi
To jest bardzo dobry artykuł na temat lokalizacji (rozszerzenie ciągów, różne tabele ciągów, a nawet pluralizacja): medium.com/@marcosantadev/…
LightMan
To bardzo dobry artykuł na temat lokalizacji w Swift dla solidnej architektury medium.com/@mendibarouk/…
Mendy

Odpowiedzi:

373

Używam następnego rozwiązania:

1) utwórz rozszerzenie:

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

2) w pliku Localizable.strings :

"Hi" = "Привет";

3) przykład zastosowania:

myLabel.text = "Hi".localized

cieszyć się! ;)

--upd: -

w przypadku komentarzy można użyć tego rozwiązania:

1) Rozszerzenie:

extension String {
    func localized(withComment:String) -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: withComment)
    }
}

2) w pliku .strings:

/* with !!! */
"Hi" = "Привет!!!";

3) przy użyciu:

myLabel.text = "Hi".localized(withComment: "with !!!")
dr OX
źródło
92
Jedynym problemem jest to, że nie będzie można używać tego genstringsnarzędzia do generowania plików .strings.
Ned
9
To bardzo dobry pomysł! Uczyniłem to również trochę mądrzejszym, zmieniając na, func localized(comment: String = "") -> Stringaby stał się mniejszy i z opcjonalnymi komentarzami :)
Gui Moura
2
Masz pomysł, jak genstringsz tym korzystać?
Chris,
48
Wszyscy są bardzo podekscytowani tą odpowiedzią, ale BIG problemem (dla każdego poważnego projektu z kilkoma językami) jest to, że całkowicie przeszkadza to w zarządzaniu przetłumaczonymi wiadomościami, ponieważ genstringsdziała tylko na dosłowne ciągi przekazane do NSLocalizedString. Dzięki temu sprytnemu obejściu tracisz możliwość aktualizowania plików .strings za pomocą tego genstringsnarzędzia, a przynajmniej dla mnie oznacza to, że nie będę w stanie korzystać z tego uproszczonego podejścia.
Erik van der Neut
14
Znalazłem to świetne rozwiązanie zaimplementowane w github.com/marmelroy/Localize-Swift . Problem generowania łańcuchów rozwiązuje również niestandardowy skrypt Pythona zawarty przez autora. Nie jestem autorem.
Tomek Cejner
279

NSLocalizedStringIstnieje również w światowej Swifta.

func NSLocalizedString(
    key: String,
    tableName: String? = default,
    bundle: NSBundle = default,
    value: String = default,
    #comment: String) -> String

tableName, bundleI valueparametry są zaznaczone defaultsłowo kluczowe, które oznacza, że możemy pominąć te parametry podczas wywoływania funkcji. W takim przypadku zostaną użyte ich wartości domyślne.

Prowadzi to do wniosku, że wywołanie metody można uprościć w celu:

NSLocalizedString("key", comment: "comment")

Swift 5 - bez zmian, nadal tak działa.

RaffAl
źródło
44
jedyną różnicą jest to, że komentarz nie może być zerowy, a autouzupełnianie nie jest intuicyjne w przypadku krótkich wersji.
Marcin
1
to już nie działa. Otrzymuję błąd informujący, że nie użyto wystarczającej liczby argumentów.
Aplikacje 4 U
2
Nie to, że powyższe jest poprawne w Xcode 6.3, Swift 1.2 z konkretną zmianą w stosunku do celu-c, komentarz (jak stwierdził Marcin) nie może być zerowy, ale może być „” (pusty).
Neil,
2
Komentarz zerowy / pusty utrudnia przeniesienie łańcucha później w pliku łańcucha; jeśli nic więcej nie dodaje nazwy klasy / pliku, gdzie jest on używany jako komentarz.
Johan
To jest poprawna odpowiedź. Gdy Apple dokona aktualizacji dla Swift, Xcode będzie mógł po prostu automatycznie przekonwertować ten interfejs API na nowy interfejs Swift API i nic innego się nie zepsuje. Nawet obecnie w menu Refraktora Xcode (wer. 11.4.1) dostępna jest Wrap in NSLocalizedStringopcja, która bardzo ułatwia, zaznaczając tekst, klikając prawym przyciskiem myszy i wybierając element menu.
Ethan Allen
28

Odmiana istniejących odpowiedzi:

Swift 5.1:

extension String {

    func localized(withComment comment: String? = nil) -> String {
        return NSLocalizedString(self, comment: comment ?? "")
    }

}

Następnie możesz po prostu użyć go z komentarzem lub bez:

"Goodbye".localized()
"Hello".localized(withComment: "Simple greeting")

Pamiętaj, że genstringsto nie będzie działać z tym rozwiązaniem.

José
źródło
14

W ten sposób można utworzyć inną implementację dla różnych typów (tj. Int lub niestandardowe klasy, takie jak CurrencyUnit, ...). Możliwe jest również skanowanie tej metody przy użyciu narzędzia genstrings. Po prostu dodaj flagę procedury do polecenia

genstrings MyCoolApp/Views/SomeView.swift -s localize -o .

rozbudowa:

import UIKit

extension String {
    public static func localize(key: String, comment: String) -> String {
        return NSLocalizedString(key, comment: comment)
    }
}

stosowanie:

String.localize("foo.bar", comment: "Foo Bar Comment :)")
Kay
źródło
Ta odpowiedź jest niesamowita i powinna być oceniana więcej! Jest to najprostsze rozwiązanie, jakie do tej pory znalazłem, jeśli chcesz uniknąć wprowadzenia kolejnej biblioteki. To dobre rozwiązanie natywne.
cgossain
6

Wersja Swift 3:) ...

import Foundation

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}
Jan
źródło
6

W rzeczywistości możesz użyć dwóch faz, aby przetłumaczyć swoje teksty w projektach Swift:

1) Pierwsza faza polega na użyciu starego sposobu utworzenia wszystkich tłumaczonych ciągów:

NSLocalisedString("Text to translate", comment: "Comment to comment")

1.1) Następnie powinieneś użyć genstringów do wygenerowania Localizable.strings:

$ genstrings *swift

2) Następnie powinieneś użyć tej odpowiedzi .

2.1) Użyj opcji „Znajdź i zamień” XCode w oparciu o wyrażenie regularne. W podanym przykładzie (jeśli nie masz komentarzy) wyrażenie regularne będzie następujące:

NSLocalizedString\((.*)\, comment:\ \"\"\) 

i zamień na

$1.localized

lub (jeśli masz komentarze)

NSLocalizedString\((.*)\, comment:\ (.*)\)

i zamień na

$1.localizedWithComment(comment: $2)

Możesz dowolnie grać z wyrażeniami regularnymi i różnymi kombinacjami rozszerzeń. Ogólnym sposobem jest podzielenie całego procesu na dwie fazy. Mam nadzieję, że to pomaga.

GYFK
źródło
1
Przepraszam, nie rozumiem tutaj wielu odpowiedzi. Jaka jest przewaga tej metody nad użyciem NSLocalizedString("Cancel", comment: "Cancel button title")?
LShi
1
@LShi niektórzy narzekali, że NSLocalizedStringwygląda to mniej Szybciej, niż powinno. String.localizedz drugiej strony wygląda bardziej Swifty, ale nie można użyć gesntringsnarzędzia, które jest powszechnie używane do ułatwienia pracy z internacjonalizacją. Chodzi mi o to, że dość łatwo jest połączyć oba podejścia. Jest to głównie kwestia czytelności.
GYFK,
Co się stanie, jeśli będziesz musiał zrobić kolejną rundę genstrings? Czy zamienić z powrotem wszystko .localizedby NSLocalizedString?
Cristik
5

Utworzono metodę małego pomocnika dla przypadków, w których „komentarz” jest zawsze ignorowany. Mniej kodu jest łatwiejsze do odczytania:

public func NSLocalizedString(key: String) -> String {
    return NSLocalizedString(key, comment: "")
}

Wystarczy umieścić go w dowolnym miejscu (poza klasą), a Xcode znajdzie tę globalną metodę.

JOM
źródło
12
To zła praktyka. Komentarze są zalecane i pomocne, chyba że sam wykonujesz całe tłumaczenie.
Jeremiasz
Nawet jeśli sam tłumaczysz, komentarze byłyby pomocne, szczególnie w dużym projekcie.
shim
4

Prawdopodobnie najlepszym sposobem jest ten tutaj .

fileprivate func NSLocalizedString(_ key: String) -> String {
    return NSLocalizedString(key, comment: "")
}

i

import Foundation
extension String {
    static let Hello = NSLocalizedString("Hello")
    static let ThisApplicationIsCreated = NSLocalizedString("This application is created by the swifting.io team")
    static let OpsNoFeature = NSLocalizedString("Ops! It looks like this feature haven't been implemented yet :(!")
}

możesz użyć tego w ten sposób

let message: String = .ThisApplicationIsCreated
print(message)

dla mnie to najlepsze, ponieważ

  • Zakodowane ciągi znajdują się w jednym określonym pliku, więc dzień, w którym chcesz go zmienić, jest naprawdę łatwy
  • Łatwiejszy w użyciu niż ręczne wpisywanie ciągów w pliku za każdym razem
  • łańcuchy nadal będą działać
  • możesz dodać więcej rozszerzeń, na przykład jeden na kontroler widoku, aby zachować porządek
Robin Dorpe
źródło
3
Należy zauważyć, że ciągi zdefiniowane w opisany sposób są ciągami statycznymi. Aplikacja powinna zostać ponownie uruchomiona po zmianie języka w aplikacji Ustawienia iOS. Jeśli nie, uruchom go ponownie samodzielnie, aby zobaczyć zmiany. Może również mieć narzut pamięci, ponieważ inicjujemy wszystkie ciągi naraz, a nie w momencie, gdy są potrzebne.
iDevAmit
2
Myślę, że lepiej jest tutaj użyć obliczonych właściwości, takich jak tastatic var Hello: String = { return NSLocalizedString("Hello") }
sztuka marzeń
Zagłosowano, ponieważ nie jest zgodny ze wskazówkami
Cristik
3

Podczas opracowywania zestawu SDK. Potrzebujesz dodatkowej operacji.

1) Utwórz Localizable.strings jak zwykle w YourLocalizeDemoSDK.

2) utwórz te same Localizable.strings w YourLocalizeDemo.

3) znajdź ścieżkę pakietu YourLocalizeDemoSDK.

Swift4 :

// if you use NSLocalizeString in NSObject, you can use it like this
let value = NSLocalizedString("key", tableName: nil, bundle: Bundle(for: type(of: self)), value: "", comment: "")

Bundle(for: type(of: self))pomaga znaleźć pakiet w YourLocalizeDemoSDK. Jeśli użyjesz Bundle.mainzamiast tego, otrzymasz niewłaściwą wartość (w rzeczywistości będzie to ten sam ciąg z kluczem).

Ale jeśli chcesz użyć rozszerzenia String wspomnianego przez dr OX . Musisz zrobić coś więcej. Rozszerzenie pochodzenia wygląda następująco.

extension String {
    var localized: String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }
}

Jak wiemy, opracowujemy pakiet SDK, Bundle.mainotrzymamy pakiet pakietu YourLocalizeDemo. Nie tego chcemy. Potrzebujemy pakietu w YourLocalizeDemoSDK. Jest to sztuczka, aby szybko ją znaleźć.

Uruchom poniższy kod w instancji NSObject w YourLocalizeDemoSDK. Otrzymasz adres URL YourLocalizeDemoSDK.

let bundleURLOfSDK = Bundle(for: type(of: self)).bundleURL
let mainBundleURL = Bundle.main.bundleURL

Wydrukuj oba dwa adresy URL, a przekonasz się, że możemy zbudować pakiet bundleURLofSDK na mainBundleURL. W takim przypadku będzie to:

let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main

A rozszerzenie String będzie:

extension String {
    var localized: String {
        let bundle = Bundle(url: Bundle.main.bundleURL.appendingPathComponent("Frameworks").appendingPathComponent("YourLocalizeDemoSDK.framework")) ?? Bundle.main
        return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
    }
}

Mam nadzieję, że to pomoże.

Liam
źródło
2

Stworzyłem własne narzędzie do generowania ciągów znaków do wyodrębniania ciągów za pomocą niestandardowej funkcji tłumaczenia

extension String {

    func localizedWith(comment:String) -> String {
        return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: comment)
    }

}

https://gist.github.com/Maxdw/e9e89af731ae6c6b8d85f5fa60ba848c

Parsuje wszystkie szybkie pliki i eksportuje ciągi i komentarze w kodzie do pliku .strings.

Prawdopodobnie nie jest to najłatwiejszy sposób, ale jest to możliwe.

Max
źródło
1

Chociaż nie rozwiązuje to problemu skrócenia, ale pomogło mi to uporządkować komunikaty, stworzyłem strukturę komunikatów o błędach, takich jak poniżej

struct Constants {
    // Error Messages
    struct ErrorMessages {
        static let unKnownError = NSLocalizedString("Unknown Error", comment: "Unknown Error Occured")
        static let downloadError = NSLocalizedString("Error in Download", comment: "Error in Download")
    }
}

let error = Constants.ErrorMessages.unKnownError

W ten sposób możesz uporządkować wiadomości i sprawić, by generowanie łańcuchów działało.

I to jest użyte polecenie genstrings

find ./ -name \*.swift -print0 | xargs -0 genstrings -o .en.lproj
anoop4real
źródło
1

Przydatny do użycia w testach jednostkowych:

Jest to prosta wersja, którą można rozszerzyć na różne przypadki użycia (np. Za pomocą tableNames).

public func NSLocalizedString(key: String, referenceClass: AnyClass, comment: String = "") -> String 
{
    let bundle = NSBundle(forClass: referenceClass)
    return NSLocalizedString(key, tableName:nil, bundle: bundle, comment: comment)
}

Użyj tego w ten sposób:

NSLocalizedString("YOUR-KEY", referenceClass: self)

Lub tak z komentarzem:

NSLocalizedString("YOUR-KEY", referenceClass: self, comment: "usage description")
GatoCurioso
źródło
1
Złą praktyką jest pomijanie komentarzy.
José
@ José Dziękuję za komentarz. Kod miał być pomysłem, a nie szablonem do kopiowania i wklejania. Ale dodałem opcję dodawania komentarzy, jeśli chcesz;)
GatoCurioso,
1

Jest to ulepszenie w podejściu „.localized”. Zacznij od dodania rozszerzenia klasy, ponieważ pomoże to w ustawieniach programowych:

extension String {
    func localized (bundle: Bundle = .main, tableName: String = "Localizable") -> String {
        return NSLocalizedString(self, tableName: tableName, value: "\(self)", comment: "")
    }
}

Przykład użycia ciągów ustawianych programowo:

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

Teraz pliki tłumaczenia scenariuszy Xcode powodują, że menedżer plików jest nieporządny i nie radzi sobie dobrze z aktualizacjami scenorysu. Lepszym rozwiązaniem jest utworzenie nowej podstawowej klasy etykiet i przypisanie jej do wszystkich etykiet serii ujęć:

class BasicLabel: UILabel {
    //initWithFrame to init view from code
    override init(frame: CGRect) {
      super.init(frame: frame)
      setupView()
    }

    //initWithCode to init view from xib or storyboard
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      setupView()
    }

    //common func to init our view
    private func setupView() {
        let storyboardText = self.text
        text = storyboardText?.localized()
    }
}

Teraz każda etykieta, którą dodasz i podasz domyślną wartość domyślną w serii ujęć, zostanie automatycznie przetłumaczona, zakładając, że dostarczyłeś tłumaczenie.

Możesz zrobić to samo dla UIButton:

class BasicBtn: UIButton {
    //initWithFrame to init view from code
    override init(frame: CGRect) {
      super.init(frame: frame)
      setupView()
    }

    //initWithCode to init view from xib or storyboard
    required init?(coder aDecoder: NSCoder) {
      super.init(coder: aDecoder)
      setupView()
    }

    //common func to init our view
    private func setupView() {
        let storyboardText = self.titleLabel?.text
        let lclTxt = storyboardText?.localized()
        setTitle(lclTxt, for: .normal)
    }
}
Dave G.
źródło
0

Kiedy tłumaczysz, powiedzmy z angielskiego, gdzie fraza jest taka sama, na inny język, w którym jest inny (z powodu płci, koniugacji czasowników lub deklinacji), najprostszą formą NSString w Swift, która działa we wszystkich przypadkach, są trzy argumenty jeden . Na przykład angielska fraza „previous was” została przetłumaczona inaczej na rosyjski w przypadku „weight” („предыдущ ий был”) i „waist” („предыдущ ая был а ”).

W takim przypadku potrzebujesz dwóch różnych tłumaczeń dla jednego źródła (pod względem narzędzia XLIFF zalecanego w WWDC 2018). Nie można tego osiągnąć za pomocą dwóch argumentów NSLocalizedString, gdzie „previous was” będzie taki sam zarówno dla „klucza”, jak i tłumaczenia na angielski (tj. Dla wartości). Jedynym sposobem jest użycie postaci trzech argumentów

NSLocalizedString("previousWasFeminine", value: "previous was", comment: "previousWasFeminine")

NSLocalizedString("previousWasMasculine", value: "previous was", comment: "previousWasMasculine")

gdzie klucze („previousWasFeminine” i „previousWasMasculine”) są różne.

Wiem, że ogólna rada polega na tłumaczeniu frazy jako całości, jednak czasem jest to zbyt czasochłonne i niewygodne.

Vadim Motorine
źródło
-1

Lokalizacja z domyślnym językiem:

extension String {
func localized() -> String {
       let defaultLanguage = "en"
       let path = Bundle.main.path(forResource: defaultLanguage, ofType: "lproj")
       let bundle = Bundle(path: path!)

       return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}
badacz
źródło