Wie kann ich das Gebietsschema programmgesteuert mit Swift ändern?

71

Ich mache ios App auf XCODE 6.3 von Swift. Und meine App hat die Funktion zum Auswählen der Sprache wie im Bild unten

Geben Sie hier die Bildbeschreibung ein

Ich habe bereits ein Storyboard für meine Landessprache. Aber ich kann nicht herausfinden, wie man die Lokalisierung programmgesteuert über die Schaltfläche ändert.

Jeder weiß, wie es geht

Varis Darasirikul
quelle
oh das ist sehr hilfreich danke!
Varis Darasirikul
Wenn Sie es lösen könnten, posten Sie bitte die Antwort hier
MBH
Zum Ändern der Sprache "on the fly" können Sie cocoapods.org/pods/L10n-swift verwenden .
Adrian Bobrowski
<p> Sie können globale NSLocale.current ändern </ p> medium.com/@konradpiekos93/…
Konrad Piękoś

Antworten:

83

Hier ist eine Möglichkeit, es mit Swift im laufenden Betrieb zu ändern. Fügen Sie String eine Erweiterungsfunktion hinzu:

extension String {
func localized(lang:String) ->String {

    let path = NSBundle.mainBundle().pathForResource(lang, ofType: "lproj")
    let bundle = NSBundle(path: path!)

    return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}}

Swift 4:

extension String {
func localized(_ lang:String) ->String {

    let path = Bundle.main.path(forResource: lang, ofType: "lproj")
    let bundle = Bundle(path: path!)

    return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
}}

Angenommen, Sie haben die regulären Localizable.strings mit lang_id.lproj eingerichtet (z. B. en.lproj, de.lproj usw.), können Sie diese überall dort verwenden, wo Sie sie benötigen:

var val = "MY_LOCALIZED_STRING".localized("de")
dijipiji
quelle
5
Die schnelle 3-Version: func localized (lang: String) -> String {let path = Bundle.main.path (forResource: lang, ofType: "lproj") let bundle = Bundle (path: path!) Gibt NSLocalizedString (self, tableName: nil, bundle: bundle!, value: "", comment: "")}
Medhi
Herzlichen Glückwunsch, dies ist bei weitem die beste Lösung, die mir begegnet ist. Ich habe gerade eine Änderung vorgenommen, um die Sprache dynamisch ändern zu können. Ich werde es als Antwort posten.
Alexandre G.
4
Aber wie lade ich ein Storyboard neu, wenn Sie die Sprache ändern? Wenn ich derzeit die Sprache ändere, funktionieren die Zeichenfolgen mit .localized (), aber das Storyboard wird nicht in der ausgewählten Sprache angezeigt.
Hardik Shekhat
Hallo Hardik, in diesem Fall müssen deine Storyboard-Labels aus dem Code festgelegt werden und du solltest den Code jedes Mal ausführen, wenn sich deine Sprache ändert
dijipiji
7
Diese Antwort beantwortet nicht die Frage, bei der es sich um ein Storyboard in einer Landessprache handelte. Es ist nur ein Zucker, einen String zu übersetzen.
Rommex
30

Dies ermöglicht das Ändern der Sprache durch einfaches Aktualisieren von aUserDefaults Schlüssels .

Dies basiert auf der großartigen Antwort von @dijipiji. Dies ist eine Swift 3- Version.

extension String {
    var localized: String {
        if let _ = UserDefaults.standard.string(forKey: "i18n_language") {} else {
            // we set a default, just in case
            UserDefaults.standard.set("fr", forKey: "i18n_language")
            UserDefaults.standard.synchronize()
        }

        let lang = UserDefaults.standard.string(forKey: "i18n_language")

        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        let bundle = Bundle(path: path!)

        return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}

Verwendung

Einfach hinzufügen .localized Sie Ihren String als solchen hinzu:

"MyString".localized, MyStringein Schlüssel in derLocalizable.strings Datei.

Sprache ändern

UserDefaults.standard.set("en", forKey: "i18n_language")
Alexandre G.
quelle
7
Aber wie lade ich ein Storyboard neu, wenn Sie die Sprache ändern? Wenn ich derzeit die Sprache ändere, funktionieren die Strings mit .localized (), aber das Storyboard ändert sich überhaupt nicht.
Hardik Shekhat
Ich hatte ein ähnliches Problem. Meine Aufgabe besteht einfach darin, die Sprache in einem Ansichts-Controller namens "Einstellungen" zu ändern, diese Ansicht dann zu schließen und voila! Alle meine Beschriftungen und Schaltflächen ändern im Handumdrehen die Sprache. Ich arbeite daran, es zu optimieren, und wenn ich einen Weg finde, werde ich eine Lösung veröffentlichen.
Philip Borges
1
Yep @PhilipBorges, das ist auch das, was in meiner App passiert, obwohl ich einige andere Zeichenfolgen im View Controller habe, in denen ich die Sprache ändere. Ich rufe einfach eine updateStrings()Methode auf, bei der ich diese Zeichenfolgen zurücksetze. Hardik, ich denke nicht, dass dies die richtige Lösung ist, wenn Sie beim Ändern der Sprache ein ganz anderes Storyboard laden möchten. Warum müssen Sie aus Neugier überhaupt ein anderes Storyboard laden? Scheint wie ein Ärger.
Alexandre G.
1
Sie sollten synchronize () nicht verwenden. Apple-Dokumente besagen buchstäblich, dass überall (zumindest für iOS 10 und 11): developer.apple.com/documentation/foundation/userdefaults/…
NeverwinterMoon
14

Verwendbarer Code in Swift 4:

extension Bundle {
    private static var bundle: Bundle!

    public static func localizedBundle() -> Bundle! {
        if bundle == nil {
            let appLang = UserDefaults.standard.string(forKey: "app_lang") ?? "ru"
            let path = Bundle.main.path(forResource: appLang, ofType: "lproj")
            bundle = Bundle(path: path!)
        }

        return bundle;
    }

    public static func setLanguage(lang: String) {
        UserDefaults.standard.set(lang, forKey: "app_lang")
        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        bundle = Bundle(path: path!)
    }
}

und

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

    func localizeWithFormat(arguments: CVarArg...) -> String{
        return String(format: self.localized(), arguments: arguments)
    }
}

Anruf:

let localizedString = "enter".localized()

setze ein neues Gebietsschema neu (zum Beispiel "ru"):

Bundle.setLanguage(lang: "ru")
mr.boyfox
quelle
Ändert diese Methode die Zeichenfolgen im laufenden Betrieb, wenn die Sprache geändert wird?
Abduhafiz
Ja, wenn Sie die Sprache nur für Ihre App in Ihrer App zur Laufzeit ändern möchten, hängt dies nicht mit der Sprache des
Gerätebetriebssystems zusammen
Hallo, ich kann das nicht im laufenden Betrieb zum Laufen bringen. Der Text wird aktualisiert, wenn die Ansicht neu geladen wurde, der aktuelle Controller bleibt jedoch unverändert. Irgendwelche Vorschläge, wie man das behebt?
Dandepeched
Wenn Sie möchten, dass Ansichtstextelemente automatisch aktualisiert werden, sollten Sie die Gerätesprache ändern. Wenn Sie sie im laufenden Betrieb ändern möchten, sollten Sie sie manuell ändern (aktualisieren Sie den gesamten Text mit einem neuen Gebietsschema, verschieben Sie alle UI-Elemente, bei denen Text zur Übersetzung in eine allgemeine Funktion verwendet wird im Controller und wenn sich das Gebietsschema ändert, gehen Sie zur Controller-Benachrichtigung über und rufen Sie diese allgemeine Funktion erneut auf)
mr.boyfox
Und wie gehen Sie mit Sprachänderungen um?
Kelin
12

Swift 4.2

In meinem Fall muss ich zwei Dinge zur Laufzeit aktualisieren, wenn der Benutzer die Spracheinstellung ändert.

1. Localizable.strings

Lokalisierbare Zeichenfolgen

2. Storyboard-Lokalisierung

Storyboard-Lokalisierung

Ich mache @ John Pang Code schneller

BundleExtension.swift

import UIKit

private var bundleKey: UInt8 = 0

final class BundleExtension: Bundle {

     override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        return (objc_getAssociatedObject(self, &bundleKey) as? Bundle)?.localizedString(forKey: key, value: value, table: tableName) ?? super.localizedString(forKey: key, value: value, table: tableName)
    }
}

extension Bundle {

    static let once: Void = { object_setClass(Bundle.main, type(of: BundleExtension())) }()

    static func set(language: Language) {
        Bundle.once

        let isLanguageRTL = Locale.characterDirection(forLanguage: language.code) == .rightToLeft
        UIView.appearance().semanticContentAttribute = isLanguageRTL == true ? .forceRightToLeft : .forceLeftToRight

        UserDefaults.standard.set(isLanguageRTL,   forKey: "AppleTe  zxtDirection")
        UserDefaults.standard.set(isLanguageRTL,   forKey: "NSForceRightToLeftWritingDirection")
        UserDefaults.standard.set([language.code], forKey: "AppleLanguages")
        UserDefaults.standard.synchronize()

        guard let path = Bundle.main.path(forResource: language.code, ofType: "lproj") else {
            log(.error, "Failed to get a bundle path.")
            return
        }

        objc_setAssociatedObject(Bundle.main, &bundleKey, Bundle(path: path), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}

Language.swift

import Foundation

enum Language: Equatable {
    case english(English)
    case chinese(Chinese)
    case korean
    case japanese

    enum English {
        case us
        case uk
        case australian
        case canadian
        case indian
    }

    enum Chinese {
        case simplified
        case traditional
        case hongKong
    }
}

extension Language {

    var code: String {
        switch self {
        case .english(let english):
            switch english {
            case .us:                return "en"
            case .uk:                return "en-GB"
            case .australian:        return "en-AU"
            case .canadian:          return "en-CA"
            case .indian:            return "en-IN"
            }

        case .chinese(let chinese):
            switch chinese {
            case .simplified:       return "zh-Hans"
            case .traditional:      return "zh-Hant"
            case .hongKong:         return "zh-HK"
            }

        case .korean:               return "ko"
        case .japanese:             return "ja"
        }
    }

    var name: String {
        switch self {
        case .english(let english):
            switch english {
            case .us:                return "English"
            case .uk:                return "English (UK)"
            case .australian:        return "English (Australia)"
            case .canadian:          return "English (Canada)"
            case .indian:            return "English (India)"
            }

        case .chinese(let chinese):
            switch chinese {
            case .simplified:       return "简体中文"
            case .traditional:      return "繁體中文"
            case .hongKong:         return "繁體中文 (香港)"
            }

        case .korean:               return "한국어"
        case .japanese:             return "日本語"
        }
    }
}

extension Language {

    init?(languageCode: String?) {
        guard let languageCode = languageCode else { return nil }
        switch languageCode {
        case "en", "en-US":     self = .english(.us)
        case "en-GB":           self = .english(.uk)
        case "en-AU":           self = .english(.australian)
        case "en-CA":           self = .english(.canadian)
        case "en-IN":           self = .english(.indian)

        case "zh-Hans":         self = .chinese(.simplified)
        case "zh-Hant":         self = .chinese(.traditional)
        case "zh-HK":           self = .chinese(.hongKong)

        case "ko":              self = .korean
        case "ja":              self = .japanese
        default:                return nil
        }
    }
}

Verwenden Sie so

var language: [Language] = [.korean, .english(.us), .english(.uk), .english(.australian), .english(.canadian), .english(.indian),
                            .chinese(.simplified), .chinese(.traditional), .chinese(.hongKong),
                            .japanese]

Bundle.set(language: languages[indexPath.row].language)

Sprachbildschirm auswählen


" Locale.current.languageCode " gibt immer die Systemeinstellungssprache zurück. Wir müssen also " Locale.preferredLanguages.first " verwenden. Der Rückgabewert sieht jedoch wie "ko-US" aus. Das ist ein Problem ! Also habe ich den LocaleManager erstellt , um nur den Sprachcode zu erhalten.

LocaleManager.swift

import Foundation

    struct LocaleManager {

    /// "ko-US" → "ko"
    static var languageCode: String? {
        guard var splits = Locale.preferredLanguages.first?.split(separator: "-"), let first = splits.first else { return nil }
        guard 1 < splits.count else { return String(first) }
        splits.removeLast()
        return String(splits.joined(separator: "-"))
}

    static var language: Language? {
        return Language(languageCode: languageCode)
    }
}

Verwenden Sie so

guard let languageCode = LocaleManager.languageCode, let title = RemoteConfiguration.shared.logIn?.main?.title?[languageCode] else {
      return NSLocalizedString("Welcome!", comment: "")
}
return title
Den
quelle
Großartig, vielen Dank
Paresh. P
11

Jeremys Antwort ( hier ) funktioniert auch gut mit Swift 4 (ich habe gerade mit einer einfachen App getestet und die Sprache geändert, die im Controller für die Erstansicht verwendet wurde).

Hier ist die Swift-Version desselben Codes (aus bestimmten Gründen bevorzugen meine Teamkollegen nur Swift als gemischt mit Objective-C, daher habe ich sie übersetzt):

import UIKit

private var kBundleKey: UInt8 = 0

class BundleEx: Bundle {

    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        if let bundle = objc_getAssociatedObject(self, &kBundleKey) {
            return (bundle as! Bundle).localizedString(forKey: key, value: value, table: tableName)
        }
        return super.localizedString(forKey: key, value: value, table: tableName)
    }

}

extension Bundle {

    static let once: Void = {
        object_setClass(Bundle.main, type(of: BundleEx()))
    }()

    class func setLanguage(_ language: String?) {
        Bundle.once
        let isLanguageRTL = Bundle.isLanguageRTL(language)
        if (isLanguageRTL) {
            UIView.appearance().semanticContentAttribute = .forceRightToLeft
        } else {
            UIView.appearance().semanticContentAttribute = .forceLeftToRight
        }
        UserDefaults.standard.set(isLanguageRTL, forKey: "AppleTextDirection")
        UserDefaults.standard.set(isLanguageRTL, forKey: "NSForceRightToLeftWritingDirection")
        UserDefaults.standard.synchronize()

        let value = (language != nil ? Bundle.init(path: (Bundle.main.path(forResource: language, ofType: "lproj"))!) : nil)
        objc_setAssociatedObject(Bundle.main, &kBundleKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }

    class func isLanguageRTL(_ languageCode: String?) -> Bool {
        return (languageCode != nil && Locale.characterDirection(forLanguage: languageCode!) == .rightToLeft)
    }

}
John Pang
quelle
funktioniert perfekt, stürzt aber ab, wenn Sie die englische Version ändern. Ich verwende objektiven C-Code, der in beiden Fällen perfekt funktioniert.
Maulik Shah
10

Nachdem ich einige Tage verbracht habe, habe ich tatsächlich die Lösung gefunden. muss nicht neu gestartet werden, ziemlich elegant: http://www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-your-ios-application.html , überprüfen die Methode # 2. Es ist nicht erforderlich, alle Titel und Texte manuell wiederherzustellen, sondern überschreibt lediglich die Lokalisierung für die benutzerdefinierte NSBundle-Kategorie. Funktioniert sowohl bei Obj-C- als auch bei Swift-Projekten (nach einiger Abstimmung) wie ein Zauber. Ich hatte einige Zweifel, ob es von Apple genehmigt wird, aber es tat es tatsächlich.

Whiteagle
quelle
Können Sie hier einen Link oder den Code der Swift-Variante einfügen?
Ravi Sisodia
@whiteagle funktioniert dies auch für die Änderung der Sprachrichtung des Projekts? Das heißt, wenn ich zu einer RTL-Sprache wechseln möchte, werden jetzt alle Elemente und Einschränkungen der Benutzeroberfläche umgedreht, wie es angenommen wird?
Royherma
@royherma, nicht sicher, aber es sollte, da es ganze Lokalisierungseinstellungen ändert.
Whiteagle
@rPragma, ich habe es mit der Obj-C-Kategoriedefinition gemacht, die vom schnellen Code aufgerufen wurde.
Whiteagle
Cool, danke! Ich habe tatsächlich eine andere Lösung gefunden, wenn Sie nur die Lokalisierung einer App erzwingen möchten, bevor alle UI-Elemente geladen sind (schnell). Wenn also jemand hier landet
Royherma
5

Swift 4

UserDefaults.standard.set(["es", "de", "it"], forKey: "AppleLanguages")
UserDefaults.standard.synchronize()

Swift 3

NSUserDefaults.standardUserDefaults().setObject(["es", "de", "it"], forKey: "AppleLanguages")
NSUserDefaults.standardUserDefaults().synchronize()

Quelle: hier

Dan Rosenstark
quelle
1
Danke! Hat für mich gearbeitet, um das Festlegen der App-Sprache beim Start zu erzwingen, und diese Zeilen zu AppDelegate hinzugefügt.
Vitaliy A
Das Problem ist jedoch, dass dies die App-Sprache beim Neustart ändert. Es sei denn, die Sprache ändert sich nicht. Es wird in der Antwort besprochen, auf die Sie sich beziehen. stackoverflow.com/a/1670524/10505343
Wimukthi Rajapaksha
3

Die durch Whiteagle verknüpfte Lösung funktioniert tatsächlich, um die Sprache im laufenden Betrieb zu wechseln. Hier ist der Beitrag.

Ich habe den Beispielcode dort auf eine einzige .h / .m vereinfacht, die die Sprache im laufenden Betrieb im Speicher ändert. Ich habe gezeigt, wie man es von Swift 3 aus nennt.

Header:

//
//  NSBundle+Language.h
//  ios_language_manager
//
//  Created by Maxim Bilan on 1/10/15.
//  Copyright (c) 2015 Maxim Bilan. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NSBundle (Language)

+ (void)setLanguage:(NSString *)language;

@end

Implementierung:

//
//  NSBundle+Language.m
//  ios_language_manager
//
//  Created by Maxim Bilan on 1/10/15.
//  Copyright (c) 2015 Maxim Bilan. All rights reserved.
//

#import "NSBundle+Language.h"
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

static const char kBundleKey = 0;

@interface BundleEx : NSBundle

@end

@implementation BundleEx

- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName
{
    NSBundle *bundle = objc_getAssociatedObject(self, &kBundleKey);
    if (bundle) {
        return [bundle localizedStringForKey:key value:value table:tableName];
    }
    else {
        return [super localizedStringForKey:key value:value table:tableName];
    }
}

@end

@implementation NSBundle (Language)

+ (void)setLanguage:(NSString *)language
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object_setClass([NSBundle mainBundle], [BundleEx class]);
    });

    BOOL isLanguageRTL = [self isLanguageRTL:language];
    if (isLanguageRTL) {
        if ([[[UIView alloc] init] respondsToSelector:@selector(setSemanticContentAttribute:)]) {
            [[UIView appearance] setSemanticContentAttribute:
             UISemanticContentAttributeForceRightToLeft];
        }
    }else {
        if ([[[UIView alloc] init] respondsToSelector:@selector(setSemanticContentAttribute:)]) {
            [[UIView appearance] setSemanticContentAttribute:UISemanticContentAttributeForceLeftToRight];
        }
    }
    [[NSUserDefaults standardUserDefaults] setBool:isLanguageRTL forKey:@"AppleTextDirection"];
    [[NSUserDefaults standardUserDefaults] setBool:isLanguageRTL forKey:@"NSForceRightToLeftWritingDirection"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    id value = language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil;
    objc_setAssociatedObject([NSBundle mainBundle], &kBundleKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

+ (BOOL)isLanguageRTL:(NSString *)languageCode
{
    return ([NSLocale characterDirectionForLanguage:languageCode] == NSLocaleLanguageDirectionRightToLeft);
}


@end

Um dies von Swift aus aufzurufen, stellen Sie sicher, dass Ihr Bridging Header Folgendes enthält:

#import "NSBundle+Language.h"

Rufen Sie dann von Ihrem Code aus an:

Bundle.setLanguage("es")

Dinge zu beachten:

  • Ich habe keinen Beispielcode eingefügt, um eine Sprachauswahl oder etwas anderes anzuzeigen. Der ursprünglich verlinkte Beitrag enthält einige.

  • Ich habe diesen Code geändert, um nichts dauerhaft zu ändern. Bei der nächsten Ausführung der App wird weiterhin versucht, die vom Benutzer bevorzugte Sprache zu verwenden. (Die einzige Ausnahme sind Sprachen von rechts nach links, siehe unten)

  • Sie können dies jederzeit tun, bevor eine Ansicht geladen wird, und die neuen Zeichenfolgen werden wirksam. Wenn Sie jedoch eine bereits geladene Ansicht ändern müssen, möchten Sie den rootViewController möglicherweise neu initialisieren, wie im ursprünglichen Beitrag angegeben.

  • Dies sollte für Sprachen von rechts nach links funktionieren, legt jedoch zwei interne dauerhafte Einstellungen in NSUserDefaults für diese Sprachen fest. Möglicherweise möchten Sie dies rückgängig machen, indem Sie die Sprache beim Beenden der App auf die Standardeinstellung des Benutzers zurücksetzen:Bundle.setLanguage(Locale.preferredLanguages.first!)

Jeremy
quelle
Sieht gut aus, außer dass ich NSLocalizedStringWithDefaultValuemeinen lokalisierten Text verwende und nicht in ein Storyboard einfüge :(
Jules
Ich schrieb eine Swift - Version für Swift-Liebhaber hier . Vielen Dank an Jeremy für die Vereinfachung des Codes.
John Pang
2

Zuallererst - das ist eine schlechte Idee undApple empfohlen, für die Lokalisierung die von iOS ausgewählte Sprache zu verwenden.

Aber wenn Sie es wirklich brauchen, können Sie einen kleinen Service für diesen Zweck machen

enum LanguageName: String {
    case undefined
    case en
    case es
    case fr
    case uk
    case ru
    case de
    case pt
}

let DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey = "DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey"


func dynamicLocalizableString(_ key: String) -> String {
    return LanguageService.service.dynamicLocalizedString(key)
}

class LanguageService {

    private struct Defaults {
        static let keyCurrentLanguage = "KeyCurrentLanguage"
    }

    static let service:LanguageService = LanguageService()

    var languageCode: String {
        get {
            return language.rawValue
        }
    }

    var currentLanguage:LanguageName {
        get {
            var currentLanguage = UserDefaults.roxy.object(forKey: Defaults.keyCurrentLanguage)
            if currentLanguage == nil {
                currentLanguage = Locale.preferredLanguages[0]

            }
            if var currentLanguage = currentLanguage as? String, 
                let lang = LanguageName(rawValue: currentLanguage.truncatedBy(by:2)) {
                return lang
            }
            return LanguageName.en
        }
    }

    var defaultLanguageForLearning:LanguageName {
        get {
            var language: LanguageName = .es
            if currentLanguage == language {
                language = .en
            }
            return language
        }
    }

    func switchToLanguage(_ lang:LanguageName) {
        language = lang
        NotificationCenter.default.post(name: NSNotification.Name(rawValue: DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
    }

    func clearLanguages() {
        UserDefaults.roxy.setValue(nil, forKey:Defaults.keyCurrentLanguage)
        print(UserDefaults.roxy.synchronize())
    }

    private var localeBundle:Bundle?

    fileprivate var language: LanguageName = LanguageName.en {
        didSet {
            let currentLanguage = language.rawValue
            UserDefaults.roxy.setValue(currentLanguage, forKey:Defaults.keyCurrentLanguage)
            UserDefaults.roxy.synchronize()

            setLocaleWithLanguage(currentLanguage)            
        }
    }

    // MARK: - LifeCycle

    private init() {
        prepareDefaultLocaleBundle()
    }

    //MARK: - Private

    fileprivate func dynamicLocalizedString(_ key: String) -> String {
        var localizedString = key
        if let bundle = localeBundle {
            localizedString = NSLocalizedString(key, bundle: bundle, comment: "")
        } else {
            localizedString = NSLocalizedString(key, comment: "")
        }
        return localizedString
    }

    private func prepareDefaultLocaleBundle() {
        var currentLanguage = UserDefaults.roxy.object(forKey: Defaults.keyCurrentLanguage)
        if currentLanguage == nil {
            currentLanguage = Locale.preferredLanguages[0]
        }

        if let currentLanguage = currentLanguage as? String {
            updateCurrentLanguageWithName(currentLanguage)
        }
    }

    private func updateCurrentLanguageWithName(_ languageName: String) {
        if let lang = LanguageName(rawValue: languageName) {
            language = lang
        }
    }

    private func setLocaleWithLanguage(_ selectedLanguage: String) {
        if let pathSelected = Bundle.main.path(forResource: selectedLanguage, ofType: "lproj"),
            let bundleSelected = Bundle(path: pathSelected)  {
            localeBundle = bundleSelected
        } else if let pathDefault = Bundle.main.path(forResource: LanguageName.en.rawValue, ofType: "lproj"),
            let bundleDefault = Bundle(path: pathDefault) {
            localeBundle = bundleDefault
        }
    }
}

Und dann machen rootViewControllerClass wie folgt:

import Foundation

protocol Localizable {
    func localizeUI()
}

und

class LocalizableViewController: UIViewController, Localizable {

    // MARK: - LifeCycle

    override func viewDidLoad() {
        super.viewDidLoad()

        NotificationCenter.default.addObserver(self, selector: #selector(self.localizeUI), name: NSNotification.Name(rawValue:DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
    }

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

        localizeUI()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }
}

extension LocalizableViewController: Localizable {
    // MARK: - Localizable

    func localizeUI() {
        fatalError("Must Override to provide inApp localization functionality")
    }
}

Dann erbt jeder Controller von LocalizableViewController und implementiertlocalizeUI()

Und statt NSLocalizedStringGebrauch dynamicLocalizableStringwie:

func localizeOnceUI() {
    label.text = dynamicLocalizableString("keyFrom<"Localizable.strings">")
}

So wechseln Sie die Sprache:

LanguageService.service.switchToLanguage(.en)

Beachten Sie auch, dass zusätzliche Schritte und Änderungen der Logik erforderlich sind, wenn Sie Ihre Widgets oder andere App-Teile dynamisch lokalisieren möchten.

hbk
quelle
1
Hallo! Könnten Sie bitte auf die von Ihnen erwähnte Empfehlung von Apple verweisen (die Betriebssystemsprache anstelle des benutzerdefinierten Umschalters zu verwenden)? Ich bin der Meinung, dass Sie, wenn Sie so etwas angeben, immer mindestens den Link zur Quelle angeben sollten.
NeverwinterMoon
2

Hier ist meine Lösung mit String-Erweiterung. Verbesserte Sicherheit durch @Das Antwort.

extension String {
  var localized: String {
    guard let path = Bundle.main.path(forResource:    Locale.current.regionCode?.lowercased(), ofType: "lproj"), let bundle = Bundle(path: path) else {
      return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "")
    }

    return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
  }
}
Kristaps Grinbergs
quelle
2

Hier ist eine aktualisierte Antwort für Swift 4

let language = "es" // replace with Locale code
guard let path = Bundle.main.path(forResource: language, ofType: "lproj") else {
  return self
}
guard let bundle = Bundle(path: path) else {
  return self
}
return NSLocalizedString(self, tableName: nil, bundle: bundle, value: "", comment: "")
moger777
quelle
1
class ViewController: UIViewController {

@IBOutlet weak var resetOutlet: MyButton! {
    didSet {
        resetOutlet.setTitle("RESET".localized().uppercased(), for: .normal)
    }
}`
}

extension String {

func localized(tableName: String = "Localizable") -> String {
    if let languageCode = Locale.current.languageCode, let preferredLanguagesFirst = Locale.preferredLanguages.first?.prefix(2)  {
        if languageCode != preferredLanguagesFirst {
            if let path = Bundle.main.path(forResource: "en", ofType: "lproj") {
                let bundle = Bundle.init(path: path)
                return NSLocalizedString(self, tableName: tableName, bundle: bundle!, value: self, comment: "")
            }
        }
        }
    return NSLocalizedString(self, tableName: tableName, value: self, comment: "")
}
}
Serhii Zhuravlov
quelle
0

Neueste Swift-Syntax:

import Foundation

extension String {
    func localized(lang:String) ->String {

        let path = Bundle.main.path(forResource: lang, ofType: "lproj")
        let bundle = Bundle(path: path!)

        return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
    }
}
Arunabh Das
quelle
0

Dies ist die erweiterte Lösung von John Pang , wenn Sie Systemstrings sofort übersetzen müssen (Zurück, Abbrechen, Fertig ...):

private var kBundleKey: UInt8 = 0

class BundleEx: Bundle {

    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        if let bundle = objc_getAssociatedObject(self, &kBundleKey) {
            return (bundle as! Bundle).localizedString(forKey: key, value: value, table: tableName)
        }
        return super.localizedString(forKey: key, value: value, table: tableName)
    }

}

private var kBundleUIKitKey: UInt8 = 0

class BundleUIKitEx: Bundle {

    override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String {
        if let bundle = objc_getAssociatedObject(self, &kBundleUIKitKey) {
            return (bundle as! Bundle).localizedString(forKey: key, value: value, table: tableName)
        }
        return super.localizedString(forKey: key, value: value, table: tableName)
    }

}

extension Bundle {

    static let once: Void = {
        object_setClass(Bundle.main, type(of: BundleEx()))
        object_setClass(Bundle(identifier:"com.apple.UIKit"), type(of: BundleUIKitEx()))
    }()

    class func setLanguage(_ language: String?) {
        Bundle.once
        let isLanguageRTL = Bundle.isLanguageRTL(language)
        if (isLanguageRTL) {
            UIView.appearance().semanticContentAttribute = .forceRightToLeft
        } else {
            UIView.appearance().semanticContentAttribute = .forceLeftToRight
        }
        UserDefaults.standard.set(isLanguageRTL, forKey: "AppleTextDirection")
        UserDefaults.standard.set(isLanguageRTL, forKey: "NSForceRightToLeftWritingDirection")
        UserDefaults.standard.synchronize()

        let value = (language != nil ? Bundle.init(path: (Bundle.main.path(forResource: language, ofType: "lproj"))!) : nil)
        objc_setAssociatedObject(Bundle.main, &kBundleKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);

        if let uiKitBundle = Bundle(identifier: "com.apple.UIKit") {
            var valueUIKit: Bundle? = nil
            if let lang = language,
                let path = uiKitBundle.path(forResource: lang, ofType: "lproj") {
                valueUIKit = Bundle(path: path)
            }
            objc_setAssociatedObject(uiKitBundle, &kBundleUIKitKey, valueUIKit, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        }

    class func isLanguageRTL(_ languageCode: String?) -> Bool {
        return (languageCode != nil && Locale.characterDirection(forLanguage: languageCode!) == .rightToLeft)
    }

}

Wenn Sie Systemzeichenfolgen übersetzen möchten, müssen Sie dasselbe für das UIKit-Bundle tun.

bojanb89
quelle
0

Folgendes sagt Apple zum Ändern der Sprache:

Im Allgemeinen sollten Sie die iOS-Systemsprache (mithilfe der AppleLanguages-Voreinstellung) nicht in Ihrer Anwendung ändern. Dies widerspricht dem grundlegenden iOS-Benutzermodell zum Wechseln der Sprache in der Einstellungen-App und verwendet auch einen Voreinstellungsschlüssel, der nicht dokumentiert ist. Dies bedeutet, dass sich der Schlüsselname zu einem späteren Zeitpunkt ändern kann, wodurch Ihre Anwendung beschädigt wird.

Als Empfehlung sollten Sie Ihre Benutzer zur Seite mit den allgemeinen Einstellungen Ihrer App navigieren, die Sie unter finden

Einstellungen -> [Ihr_Anwendungsname] -> Bevorzugte Sprache

Um Anwendungseinstellungen direkt in Ihrer App zu öffnen , können Sie diese Codeteile verwenden.

Für Swift;

let settingsURL = URL(string: UIApplication.openSettingsURLString)!
UIApplication.shared.open(settingsURL, options: [:], completionHandler: nil)

Für Obj-C;

NSURL *settingsURL = [NSURL URLWithString:UIApplicationOpenSettingsURLString];
[[UIApplication sharedApplication] openURL:settingsURL options:@{} completionHandler:nil];

Tipp: Bevor Sie zur Einstellungsseite navigieren, sollten Sie ein Popup-Fenster erstellen und angeben, was die Benutzer nach dem Wechsel zur Einstellungsseite tun sollen, um eine bessere Benutzererfahrung für Ihre Anwendung zu erzielen.

Onur Şahindur
quelle
0

So unterstützen Sie die Spracheinstellungen pro App in Ihrer App:
https://developer.apple.com/news/?id=u2cfuj88

So wechseln Sie von einer benutzerdefinierten Sprachauswahl in Ihrer App weg

wechseln Sie weg Mit der systemweiten Unterstützung für In-App-Sprachauswahl müssen Sie keine Möglichkeit mehr bieten, Sprachen in Ihrer App auszuwählen, wenn Sie iOS 13 oder macOS Catalina oder höher unterstützen. Wenn Sie derzeit eine solche Benutzeroberfläche anbieten, sollten Sie sie entfernen, um Kundenverwirrung und mögliche Konflikte mit dem System zu vermeiden.

Wenn Sie Benutzer zu den Systemeinstellungen für die Sprachauswahl führen möchten, können Sie die benutzerdefinierte Benutzeroberfläche Ihrer App durch einen Flow ersetzen, der direkt in die Einstellungen-App unter iOS gestartet wird.

Fügen Sie unter iOS Folgendes hinzu:

UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)

Weisen Sie unter macOS Personen zu Systemeinstellungen> Sprache und Region, um eine Spracheinstellung für Ihre App hinzuzufügen.

Muhammad Asyraf
quelle