ios 如何使用 Swift 以编程方式更改语言环境

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/29985614/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 05:50:40  来源:igfitidea点击:

How can I change locale programmatically with Swift

iosxcodeswiftlocalization

提问by Varis Darasirikul

I am making ios app on XCODE 6.3 by Swift. And my app will have the choose language function like the image below

我正在通过 Swift 在 XCODE 6.3 上制作 ios 应用程序。我的应用程序将具有如下图所示的选择语言功能

enter image description here

在此处输入图片说明

I already have storyboard for my local language. But i can't find out how to change the localization programmatically off the app by the button.

我已经有本地语言的故事板。但我无法找到如何通过按钮以编程方式关闭应用程序的本地化。

Anyone know how to do it

任何人都知道怎么做

回答by dijipiji

Here's a way to change it on the fly with Swift, add an extension function to String:

这是一种使用 Swift 动态更改它的方法,向 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:

斯威夫特 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: "")
}}

then assuming you have the regular Localizable.strings set up with lang_id.lproj ( e.g. en.lproj, de.lproj etc. ) you can use this anywhere you need:

然后假设您使用 lang_id.lproj(例如 en.lproj、de.lproj 等)设置了常规的 Localizable.strings,您可以在任何需要的地方使用它:

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

回答by Alexandre G.

This allows to change the languagejust by updating a UserDefaultskey.

这允许仅通过更新密钥更改语言UserDefaults

This is based on the great answer from @dijipiji. This is a Swift 3version.

这是基于@dijipiji 的精彩回答。这是一个Swift 3版本。

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

Usage

用法

Just add .localizedto your string, as such :

只需添加.localized到您的字符串中,例如:

"MyString".localized, MyStringbeing a key in the Localizable.stringsfile.

"MyString".localizedMyString作为Localizable.strings文件中的一个键。

Changing the language

更改语言

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

回答by mr.boyfox

Usable code in Swift 4:

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

and

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

call:

称呼:

let localizedString = "enter".localized()

set new a locale(for example "ru"):

设置新的语言环境(例如“ru”):

Bundle.setLanguage(lang: "ru")

回答by whiteagle

after spending several days I've actually found the solution. doesn't need re-launch, quite elegant: http://www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-your-ios-application.html, check the method #2. It doesn't require to manually re-establish all the titles and texts, just overrides the localization for the custom NSBundle category. Works on both Obj-C and Swift projects (after some tuning) like a charm. I had some doubts if it will be approved by apple, but it actually did.

花了几天后,我实际上找到了解决方案。不需要重新启动,非常优雅:http: //www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-your-ios-application.html,检查方法#2。它不需要手动重新建立所有标题和文本,只需覆盖自定义 NSBundle 类别的本地化。适用于 Obj-C 和 Swift 项目(经过一些调整)就像一个魅力。我怀疑它是否会被苹果批准,但它确实做到了。

回答by Den

Swift 4.2

斯威夫特 4.2

In my case, if user change the language setting, I have to update 2 things at runtime.

就我而言,如果用户更改语言设置,我必须在运行时更新两件事。

1. Localizable.strings

1. Localizable.strings

Localizable strings

可本地化的字符串

2. Storyboard Localization

2. 故事板本地化

Storyboard localization

故事板本地化

I make @John Pang code more swifty

我让@John Pang 代码更快捷

BundleExtension.swift

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

语言.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
        }
    }
}

Use like this

像这样使用

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)

Select language screen

选择语言画面



"Locale.current.languageCode" will always return system setting language. So we have to use "Locale.preferredLanguages.first". However the return value looks like "ko-US". This is problem ! So I made the LocaleManagerto get only the language code.

Locale.current.languageCode”将始终返回系统设置语言。所以我们必须使用“ Locale.preferredLanguages.first”。然而,返回值看起来像“ko-US”。这是问题!所以我让LocaleManager只获取语言代码。

LocaleManager.swift

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

Use like this

像这样使用

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

回答by John Pang

Jeremy's answer (here) works well on Swift 4 too (I just tested with a simple app and I changed language used in initial view controller).

Jeremy 的回答(此处)也适用于 Swift 4(我刚刚用一个简单的应用程序进行了测试,并更改了初始视图控制器中使用的语言)。

Here is the Swift version of the same piece of code (for some reasons, my teammates prefer Swift-only than mixed with Objective-C, so I translated it):

这是同一段代码的 Swift 版本(出于某些原因,我的队友更喜欢 Swift-only 而不是与 Objective-C 混合,所以我翻译了它):

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

}

回答by Dan Rosenstark

Swift 4

斯威夫特 4

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

Swift 3

斯威夫特 3

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

Source: here

来源:这里

回答by Jeremy

The solution linked by whiteagleactually works to switch the language on the fly. Here's the post.

whiteagle链接的解决方案实际上可以即时切换语言。这是帖子。

I simplified the sample code on there to a single .h/.m that will change the language on-the-fly, in memory. I've shown how to call it from Swift 3.

我将那里的示例代码简化为单个 .h/.m,它将在内存中即时更改语言。我已经展示了如何从 Swift 3 调用它。

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

Implementation:

执行:

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

To call this from Swift, ensure your Bridging Header has:

要从 Swift 调用它,请确保您的桥接头具有:

#import "NSBundle+Language.h"

Then from your code, call:

然后从您的代码中,调用:

Bundle.setLanguage("es")

Things to note:

注意事项:

  • I did not include any sample code to show a language picker or anything. The original linked post does include some.

  • I changed this code to not change anything persistently. The next time the app runs, it will still try to use the user's preferred language. (The one exception is right-to-left languages, see below)

  • You can do this anytime before a view is loaded, and the new strings will take effect. However, if you need to change a view that's already loaded, you may want to re-initialize the rootViewController as the original post says.

  • This should work for right-to-left languages, but it sets two internal persistent preferences in NSUserDefaults for those languages. You may want to undo that by setting the language back to the user's default upon app exit: Bundle.setLanguage(Locale.preferredLanguages.first!)

  • 我没有包含任何示例代码来显示语言选择器或任何东西。原始链接的帖子确实包含一些。

  • 我更改了此代码以不持续更改任何内容。应用程序下次运行时,仍会尝试使用用户的首选语言。(一个例外是从右到左的语言,见下文)

  • 您可以在加载视图之前随时执行此操作,新字符串将生效。但是,如果您需要更改已加载的视图,您可能需要像原始帖子所说的那样重新初始化 rootViewController。

  • 这应该适用于从右到左的语言,但它在 NSUserDefaults 中为这些语言设置了两个内部持久性首选项。您可能希望通过在应用程序退出时将语言设置回用户的默认值来撤消该操作:Bundle.setLanguage(Locale.preferredLanguages.first!)

回答by gbk

First of all - this is bad ideaand Applerecommend to use iOS selected language for localization.

首先 -这是个坏主意Apple建议使用 iOS 选择的语言进行本地化。

But if you really need it you can make some small service for such purpose

但是如果你真的需要它,你可以为此目的做一些小服务

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

And than make rootViewControllerClass like:

而不是让 rootViewControllerClass 像:

import Foundation

protocol Localizable {
    func localizeUI()
}

and

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

Than inherit every controller from LocalizableViewControllerand implement localizeUI()

比继承每个控制器LocalizableViewController并实现localizeUI()

And instead of NSLocalizedStringuse dynamicLocalizableStringlike :

而不是NSLocalizedString使用dynamicLocalizableString像:

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

To switch language:

切换语言:

LanguageService.service.switchToLanguage(.en)

Also note - additional steps and modification of logic required if you want to dynamically localize your widgets or other app parts.

另请注意 -如果您想动态本地化您的小部件或其他应用程序部件,则需要额外的步骤和逻辑修改。

回答by moger777

Here is an updated answer for Swift 4

这是 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: "")