Hoe kan ik de landinstelling programmatisch wijzigen met Swift

Ik maak een ios-app op XCODE 6.3 door Swift.
En mijn app heeft de taalkeuzefunctie zoals de afbeelding hieronder

voer hier de afbeeldingsbeschrijving in

Ik heb al een storyboard voor mijn lokale taal.
Maar ik kan niet vinden hoe ik de lokalisatie programmatisch uit de app kan wijzigen met de knop.

Iedereen weet hoe het moet


Antwoord 1, autoriteit 100%

Hier is een manier om het direct te wijzigen met Swift, voeg een extensiefunctie toe aan String:

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: "")
}}

in de veronderstelling dat je de normale Localizable.strings hebt ingesteld met lang_id.lproj (bijv. en.lproj, de.lproj etc.), kun je dit overal gebruiken waar je maar wilt:

var val = "MY_LOCALIZED_STRING".localized("de")

Antwoord 2, autoriteit 31%

Hiermee kunt u de taal wijzigendoor een UserDefaults-sleutelbij te werken.

Dit is gebaseerd op het geweldige antwoord van @dijipiji. Dit is een Swift 3-versie.

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: "")
    }
}

Gebruik

Voeg gewoon .localizedtoe aan je string, als zodanig:

"MyString".localized, waarbij MyStringeen sleutel is in het bestand Localizable.strings.

De taal wijzigen

UserDefaults.standard.set("en", forKey: "i18n_language")

Antwoord 3, autoriteit 19%

Swift 4.2

In mijn geval, als de gebruiker de taalinstelling wijzigt, moet ik tijdens runtime 2 dingen bijwerken.

1. Localizable.strings

Localizeerbare strings

2. Storyboard-lokalisatie

Storyboard-lokalisatie

Ik maak de @John Pang-code sneller

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);
    }
}

Taal.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
        }
    }
}

Zo gebruiken

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)

Taal selecteren


Locale.current.languageCode” retourneert altijd de taal van de systeeminstelling.
We moeten dus “Locale.preferredLanguages.first” gebruiken. De retourwaarde ziet er echter uit als “ko-US”. Dit is het probleem ! Dus heb ik de LocaleManagergemaakt om alleen de taalcode te krijgen.

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

Zo gebruiken

guard let languageCode = LocaleManager.languageCode, let title = RemoteConfiguration.shared.logIn?.main?.title?[languageCode] else {
      return NSLocalizedString("Welcome!", comment: "")
}
return title

Antwoord 4, autoriteit 16%

Bruikbare 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!)
    }
}

en

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

bel:

let localizedString = "enter".localized()

stel een nieuwe landinstelling in (bijvoorbeeld “ru”):

Bundle.setLanguage(lang: "ru")

Antwoord 5, autoriteit 11%

Jeremy’s antwoord (hier) werkt ook goed op Swift 4 (ik heb net getest met een eenvoudige app en ik heb van taal veranderd gebruikt in de initiële weergavecontroller).

Hier is de Swift-versie van hetzelfde stuk code (om de een of andere reden geven mijn teamgenoten de voorkeur aan Swift-only dan gemengd met Objective-C, dus ik heb het vertaald):

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

6, Autoriteit 10%

Na het doorbrengen van enkele dagen heb ik daadwerkelijk de oplossing gevonden. Noodzaak van herlancering, vrij elegant: http://www.factorialComplexity.com/blog/2015/01/28/How-to-change-localisation-Internal-in-your-ios-Application.html Controleer de methode # 2.
Het vereist niet om alle titels en teksten handmatig opnieuw in te stellen, negeert u gewoon de lokalisatie voor de categorie Aangepaste NSBUNDLE. Werkt op zowel OBJ-C en SWIFT-projecten (na wat afstemming) als een charme.
Ik had wat twijfels als het door Apple wordt goedgekeurd, maar het daadwerkelijk deed.


Antwoord 7

Nieuwste Swift-syntaxis:

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: "")
    }
}

Antwoord 8

Dit is een uitgebreide John Pang’s oplossing, als je systeemstrings onmiddellijk moet vertalen (Terug, Annuleren, Klaar.. .):

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

Als je systeemstrings wilt vertalen, moet je hetzelfde doen voor UIKit Bundle.


Antwoord 9

Voor lokalisatie tijdens runtime kan een van de volgende bibliotheken worden gebruikt:
Localize_Swift
of
LanguageManager-iOS

Als je de lokalisatie wilt wijzigen volgens de aanbevelingen van Apple, kun je de beschrijving vinden in het antwoord van @Muhammad Asyraf.


Antwoord 10

Geoptimaliseerde code voor mr.boyfox.

Stel de code system languagein op de sleutel i18n_language in StandardUserDefaults.

extension String {
var localized: String {
    if let _ = UserDefaults.standard.string(forKey: "i18n_language") {} else {
        // we set a default, just in case
        let lang = Bundle.main.preferredLocalizations.first ?? "en"
        UserDefaults.standard.set(lang, 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: "")
 } 
}

Antwoord 11

Als u SwiftUI gebruikt. In de praktijk is het overschrijven van Bundleonbetrouwbaar gebleken.

Hierdoor kunt u de gebruikte taal op een betrouwbare manier overschrijven. U hoeft alleen uw door de app ondersteunde talen in te stellen op SupportedLanguageCode.

(mogelijk moet u opnieuw laden als u de huidige weergave onmiddellijk wilt lokaliseren)

import SwiftUI
class Language {
    static let shared = Language()
    static let overrideKey = "override.language.code"
    var currentBundle: Bundle!
    init() {
        loadCurrentBundle()
    }
    func loadCurrentBundle() {
        let path = Bundle.main.path(forResource: current.rawValue, ofType: "lproj")!
        currentBundle = Bundle(path: path)!
    }
    enum SupportedLanguageCode: String, Equatable, CaseIterable {
        case en
        case ar
        case de
        case es
        case fr
        case hi
        case it
        case ja
        case ko
        case nl
        case ru
        case th
        case tr
        case vi
        case pt_BR = "pt-BR"
        case zh_Hans = "zh-Hans"
        case zh_Hant = "zh-Hant"
    }
    func set(language: Language.SupportedLanguageCode) {
        UserDefaults.standard.set(language.rawValue, forKey: type(of: self).overrideKey)
        loadCurrentBundle()
    }
    var current: Language.SupportedLanguageCode {
        let code = UserDefaults.standard.string(forKey: type(of: self).overrideKey) ?? Locale.current.languageCode!
        guard let language = Language.SupportedLanguageCode(rawValue: code) else {
            fatalError("failed to load language")
        }
        return language
    }
}
extension String {
    var localized: String {
        Language.shared.currentBundle.localizedString(forKey: self, value: nil, table: nil)
    }
}

Je laadt gewoon de benodigde bundel op en specificeert die bundel wanneer je lokaliseert in de overschreven localized.

Other episodes