在运行中的iOS应用程序中,编程实现动态更改语言

35

我在Stack Overflow和Google上搜索了好几个小时,现在有些绝望了。我想在我的应用程序中更改语言,而不仅仅是使用默认语言。

根据我尝试的内容,我遇到了常见的问题,即必须重新启动应用程序。这意味着,苹果强制你手动重启应用程序。也就是说,你必须退出应用程序,然后再重新启动它。

经过搜索,我尝试设置闹钟,然后通过以下方式强制退出应用程序:

exit(0);

很抱歉,苹果似乎不喜欢这个并阻止开发人员使用它... 我猜我没有指向正确的方向。

尽管遇到了所有的问题,但最终我还是想讨论一下这个问题。

有什么提示吗?


编辑,来自APPLE的信息

总的来说,您不应该在应用程序内部更改iOS系统语言(通过使用AppleLanguages pref键)。 这违反了基本的iOS用户模型,无法在设置应用程序中切换语言,并且还使用了未记录的首选项密钥,这意味着在将来的某个时候,密钥名称可能会更改,从而破坏您的应用程序。

如果您想在应用程序中切换语言,可以通过手动加载资源文件到您的包中来实现。 您可以使用NSBundle:pathForResource:ofType:inDirectory:forLocalization:来实现此目的,但要记住,您的应用程序将负责所有本地化数据的加载。

关于exit(0)的问题,苹果DTS无法对应用程序批准过程进行评论。 您应该联系appreview@apple.com以获取有关此问题的答案。

好吧,我现在必须做出选择。


2
我以前在UIAlertView提醒用户应用程序即将退出后使用过exit(0),从未在与苹果的审核中遇到问题。 - Rog
请查看此链接 https://gist.github.com/1922569 - 0xDE4E15B
嗨@gabrielstuff,你知道那个“来自APPLE的信息”的链接吗? - david
嗨,这是文档中的内容。相当古老 :) - gabrielstuff
7个回答

22

这真的很有帮助。一直在苦恼这个问题。谢谢 Swissdude。 - Azeem Shaikh
@Swissdude 非常感谢,我也曾经为此苦恼。我稍微修改了你在 Aggressive Mediocrity 上的代码以适应我的用例,希望这样可以。如果有人对我稍微扩展的版本感兴趣,请联系我。 - chritaso
最近我发现了一些有趣的东西,当你需要将NSLocalizedString调用更改为自定义内容时,这可能会对你所有人都有帮助:http://blog.spritebandits.com/2012/01/25/ios-iphone-app-localization-genstrings-tips/ - chritaso

8

4

是的,我遇到了同样的问题,后来我通过我的prefFile中的语言设置来管理它,我在那里设置了一个变量来进行语言设置:

// write a new value in file and set the var
- (void)changeLangInPrefFile:(NSString *)newLanguage {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"myPreference.plist"];
    NSMutableDictionary *data = [[NSMutableDictionary alloc] initWithContentsOfFile: path];
    //here add elements to data file and write data to file
    [data setObject:newLanguage forKey:@"language"];
    [data writeToFile:path atomically:YES];
    [data release];

// NSString *chosenLang; <- declared in .h file
    if (chosenLang != nil){
        [chosenLang release];
        chosenLang = nil;
    }
    chosenLang = [[NSString alloc] initWithString:(@"%@",newLanguage)];

}

// read the language from file and set the var:
- (void)readFromFileInBundleDocuments {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"myPreference.plist"];
    NSMutableDictionary *savedStock = [[NSMutableDictionary alloc] initWithContentsOfFile:path];

    NSString *chosenLangTemp = [savedStock objectForKey:@"language"];
    NSLog (@"read in file: %@", chosenLangTemp);
    if (chosenLang != nil){
        [chosenLang release];
        chosenLang = nil;
    }
    chosenLang = [[NSString alloc] initWithString:(@"%@",chosenLangTemp)];
    [savedStock release];
}

然后根据语言的不同,我会从不同的文件中加载所有内容。例如,我可以加载“an_image_eng.png”或“an_image_ita.png”,或者有两个不同的.xib文件。对于要加载的文本,我使用不同的字典文件,每种语言一个,其中包含所有单词/表达式的翻译,我只需加载选择的字典文件,并在其中读取每个要加载的文本的正确表达式(加载它的代码类似于我在此示例中编写的方法,您只需安排它以读取每个表达式的正确单词即可:只需查看正确字典文件中objectForKey的值,其中objectForKey是要翻译的单词,其值是翻译后的单词)...


1
通常,用户看到的语言是由区域设置决定的,这是一个系统范围的设置。只有用户可以更改它,当他这样做时,SpringBoard和设备上运行的每个应用程序都必须重新启动。没有办法绕过这一点,因为所有系统应用程序和框架都假定一旦它们开始运行,区域设置就不会改变。让应用程序和框架不需要重新启动将非常困难,苹果公司很难实现这一点。
我猜你要么想完全独立于系统区域设置来改变应用程序界面的语言,要么想默认使用系统区域设置,但允许用户仅针对你的应用程序进行覆盖。
您可以使用 + [NSLocale currentLocale] 获取当前区域设置并检查其各种值。要在与系统区域设置无关的语言中显示应用程序的用户界面,您需要完全避免使用 NSLocalizedString(),并使用某种自定义状态来确定向用户显示哪些字符串以及如何修改界面以适应您的应用程序语言。你需要保持你的应用程序语言状态并适当地修改它的用户界面。

是的,我已经尝试获取当前语言环境。我尝试了这个:NSUserDefaults* defs = [NSUserDefaults standardUserDefaults]; NSArray* languages = [defs objectForKey:@"AppleLanguages"]; NSString* preferredLang = [languages objectAtIndex:0]; NSLog(@"preferredLang: %@", preferredLang);但我只得到了应用程序的首选语言。对我来说,似乎很奇怪,无法根据特定事件完全重新加载所有资源?感谢您的帮助! - gabrielstuff
记录一下,我一直在使用UIAlertview方法来防止用户退出应用程序并不得不重新启动。它很有效! - gabrielstuff

1
这是一个老问题,但我正在开发一个助手,可以在语言实时更改时通知我。
请查看助手的代码:
import Foundation

class LocalizableLanguage {

    // MARK: Constants

    fileprivate static let APPLE_LANGUAGE_KEY = "AppleLanguages"

    /// Notification Name to observe when language change
    static let ApplicationDidChangeLanguage = Notification.Name("ApplicationDidChangeLanguage")

    // MARK: Properties

    /// An array with all available languages as String
    static var availableLanguages: [String]? = {
        return UserDefaults.standard.object(forKey: APPLE_LANGUAGE_KEY) as? [String]
    }()

    /// The first element of available languages that is the current language
    static var currentLanguageCode: String? = {
        return availableLanguages?.first
    }()

    /// The current language code with just 2 characters
    static var currentShortLanguageCode: String? = {
        guard let currentLanguageCode = currentLanguageCode else {
            return nil
        }

        let strIndex = currentLanguageCode.index(currentLanguageCode.startIndex, offsetBy: 2)
        return currentLanguageCode.substring(to: strIndex)
    }()

    // MARK: Handle functions

    /// This accepts the short language code or full language code
    /// Setting this will send a notification with name "ApplicationDidChangeLanguage", that can be observed in order to refresh your localizable strings
    class func setLanguage(withCode langCode: String) {

        let matchedLangCode = availableLanguages?.filter {
            $0.contains(langCode)
        }.first

        guard let fullLangCode = matchedLangCode else {
            return
        }

        var reOrderedArray = availableLanguages?.filter {
            $0.contains(langCode) == false
        }

        reOrderedArray?.insert(fullLangCode, at: 0)

        guard let langArray = reOrderedArray else {
            return
        }

        UserDefaults.standard.set(langArray, forKey: APPLE_LANGUAGE_KEY)
        UserDefaults.standard.synchronize()

        LocalizableLanguage.refreshAppBundle()

        NotificationCenter.default.post(name: ApplicationDidChangeLanguage, object: fullLangCode)
    }
}

// MARK: Refresh Bundle Helper

private extension LocalizableLanguage {

    class func refreshAppBundle() {
        MethodSwizzleGivenClassName(cls: Bundle.self, originalSelector: #selector(Bundle.localizedString(forKey:value:table:)), overrideSelector: #selector(Bundle.specialLocalizedStringForKey(_:value:table:)))
    }

    class func MethodSwizzleGivenClassName(cls: AnyClass, originalSelector: Selector, overrideSelector: Selector) {
        let origMethod: Method = class_getInstanceMethod(cls, originalSelector);
        let overrideMethod: Method = class_getInstanceMethod(cls, overrideSelector);
        if (class_addMethod(cls, originalSelector, method_getImplementation(overrideMethod), method_getTypeEncoding(overrideMethod))) {
            class_replaceMethod(cls, overrideSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        } else {
            method_exchangeImplementations(origMethod, overrideMethod);
        }
    }
}

extension Bundle {

    func specialLocalizedStringForKey(_ key: String, value: String?, table tableName: String?) -> String {

        let availableLanguages = UserDefaults.standard.object(forKey: LocalizableLanguage.APPLE_LANGUAGE_KEY) as? [String]
        let currentLanguageCode = availableLanguages?.first ?? "en-US"
        let currentShortLanguageCode = currentLanguageCode.substring(to: currentLanguageCode.index(currentLanguageCode.startIndex, offsetBy: 2))

        let path =
                Bundle.main.path(forResource: currentLanguageCode, ofType: "lproj") ??
                Bundle.main.path(forResource: currentShortLanguageCode, ofType: "lproj") ??
                Bundle.main.path(forResource: "Base", ofType: "lproj")

        guard
            self == Bundle.main,
            let bundlePath = path,
            let bundle = Bundle(path: bundlePath)
        else {
            return self.specialLocalizedStringForKey(key, value: value, table: tableName)
        }

        return bundle.specialLocalizedStringForKey(key, value: value, table: tableName)
    }
}

你只需要复制那段代码并将其放入你的项目中。
然后,你只需像这样简单地实现监听器:
NotificationCenter.default.addObserver(forName: LocalizableLanguage.ApplicationDidChangeLanguage, object: nil, queue: nil) { notification in
            guard let langCode = notification.object as? String else {
                return
            }
            self.accountStore.languageCode.value = langCode
        } 

请注意,这一行代码 self.accountStore.languageCode.value = langCode 是我需要在应用程序语言更改时刷新的内容,然后我可以轻松地更改所有ViewModels中的字符串,以便立即将语言更改为用户所需的语言。
为了更改语言,您只需调用:
LocalizableLanguage.setLanguage(withCode: "en")

其他可能对您有帮助的辅助工具是:

import Foundation

extension String {

    var localized: String {
        return NSLocalizedString(self, comment: "")
    }

}

如果您在可本地化的文件中有以下内容:

main.view.title = "Title test";

您可以简单地调用:

"main.view.title".localized

并且您已经翻译了您的字符串。


0

你试过了吗?你如何启用它?我有一个包含所有本地化的项目..我在iOS 13上运行它...没有任何设置出现...我是否需要专门创建设置? Apple说一切都会自动完成。 - anoop4real
@anoop4real 是的,我做到了。你只需要调用UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)。它会在应用程序内打开设置。你可以看到一个名为“PREFERRED LANGUAGE”->语言的额外设置。但是请确保您已经添加了一些语言。从“通用”->“语言与地区”->“iPhone语言”可以完成这个操作。 - Manish Nahar
我注意到一个奇怪的事情,如果你的应用程序根本没有任何设置...例如:没有位置、没有照片、不需要互联网等...设置将永远不会出现...只需创建一个新项目,尝试通过URL打开设置,你最终会进入通用设置。 - anoop4real
@anoop4real 是的,正确的,这是默认行为。对于我们的应用程序,我们正在检查用户是否没有配置任何语言,然后我们会提示并提供如何添加语言的说明。这样用户就不会感到困惑了。 - Manish Nahar
什么指示?在iPhone设置中设置首选语言?我已经有很多语言在首选列表中了...但是应用程序本身没有设置...只需尝试一下,创建一个简单的应用程序,在手机上安装它...通过转到“常规-设置”查看它是否具有设置,现在将本地化文件添加到该项目中...再次尝试应用程序...仍然不会在“常规-设置”中看到设置。 - anoop4real
@anoop4real 好的,我明白了。也许在我的应用程序中,我们有很多其他设置,如联系人权限、位置、通知等等,所以我们没有遇到你所说的问题。我会尝试使用一个空白的应用程序加上本地化。 - Manish Nahar

0
根据苹果的指南,以编程方式更改应用程序中的语言不是一个好主意,但如果你无法更改所请求的行为,可以采取以下措施:
  1. 准备一些服务来管理您的语言,即使在应用程序重新启动后也是如此

    enum LanguageName: String {
        case undefined
        case en
        case es
    }
    
    let DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey = "DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey"
    
    func dynamicLocalizableString(_ key: String) -> String {
        return LanguageService.service.dynamicLocalizedString(key)
    }
    
    class LanguageService {
    
        private struct Defaults {
            static let keyAppleLanguage = "AppleLanguages"
            static let keyCurrentLanguage = "KeyCurrentLanguage"
        }
    
        static let service:LanguageService = LanguageService()
    
        var languageCode: String {
            get {
                return language.rawValue
            }
        }
    
        var currentLanguage:LanguageName {
            get {
                var currentLanguage = UserDefaults.standard.object(forKey: Defaults.keyCurrentLanguage)
                if let currentLanguage = currentLanguage as? String {
                    UserDefaults.standard.set([currentLanguage], forKey: Defaults.keyAppleLanguage)
                    UserDefaults.standard.synchronize()
                } else {
                    if let languages = UserDefaults.standard.object(forKey: Defaults.keyAppleLanguage) as? [String] {
                        currentLanguage = languages.first
                    }
                }
                if let currentLanguage = currentLanguage as? String, 
                    let lang = LanguageName(rawValue: currentLanguage) {
                    return lang
                }
                return LanguageName.undefined
            }
        }
    
        func switchToLanguage(_ lang:LanguageName) {
            language = lang
            NotificationCenter.default.post(name: NSNotification.Name(rawValue: DynamicLanguageServiceDidDetectLanguageSwitchNotificationKey), object: nil)
        }
    
        private var localeBundle:Bundle?
    
        fileprivate var language: LanguageName = LanguageName.en {
            didSet {
                let currentLanguage = language.rawValue
    
                UserDefaults.standard.set([currentLanguage], forKey:Defaults.keyAppleLanguage)
                UserDefaults.standard.setValue(currentLanguage, forKey:Defaults.keyCurrentLanguage)
                UserDefaults.standard.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.standard.object(forKey: Defaults.keyCurrentLanguage)
            if let currentLanguage = currentLanguage as? String {
                UserDefaults.standard.set([currentLanguage], forKey: Defaults.keyAppleLanguage)
                UserDefaults.standard.synchronize()
            } else {
                if let languages = UserDefaults.standard.object(forKey: Defaults.keyAppleLanguage) as? [String] {
                    currentLanguage = languages.first
                }
            }
    
            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
            }
        }
    }
    
  2. 添加一些规则以确保您的 UI 组件始终会被更新:

    protocol Localizable {
        func localizeUI()
    }
    
  3. 实现它们

    class LocalizableViewController: UIViewController {
    // 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("必须重写该函数,以提供应用内本地化功能") }
    注意:不要忘记添加与LanguageName中相同的代码的Localizable.strings

    enter image description here


你好,如何本地化从服务器获取的动态文本,在我的应用程序中,我必须以简体中文显示每个文本,但如何显示以英语呈现的文本。请帮助我。 - Mahesh Narla
@MaheshNarla 对于动态文本,您可以使用Google Translate APIs进行翻译。这是需要付费的。https://cloud.google.com/translate/docs/simple-translate-call - Manish Nahar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接