在运行iOS时,以编程方式动态更改语言

时间:2011-02-24 19:06:21

标签: iphone ios localization runtime

我一直在堆叠和谷歌搜索几个小时。我现在有点绝望了。 我想在应用程序中更改应用程序的语言,而不仅仅是使用默认语言。

从我的尝试开始,我就像重启步骤一样陷入困境。意思是,苹果强制您手动重启应用程序。这意味着您必须退出应用程序然后再次启动它。

好吧,谷歌搜索后,我试图设置一个警报,然后强迫应用程序退出

exit(0);

我的不好,苹果似乎不喜欢这样,并阻止开发者使用它...我想我没有指向正确的方向。

最后,尽管存在所有问题,我还是可以见面,我想讨论一下。

任何提示?


编辑,APPLE的信息

  

一般情况下,你不应该改变   iOS系统语言(通过使用   AppleLanguages pref key)来自内部   你的申请。这违背了   用于切换的基本iOS用户模型   “设置”应用中的语言和   还使用了不是的首选键   记录在案,意思是在某些时候   在未来,关键名称可以   改变,这将打破你的   应用

     

如果要切换语言   你的申请,你可以通过   手动加载资源文件   你的捆绑。您可以使用   一个NSBundle:pathForResource:ofType:inDirectory:forLocalization:   为此目的,但请记住   你的申请会是   负责所有装载   本地化数据。

     

关于退出(0)问题,Apple   DTS无法评论应用批准   处理。你应该联系   appreview@apple.com得到答案   对于这个问题。

好吧,到目前为止我必须选择。

7 个答案:

答案 0 :(得分:21)

这是一个相当古老的问题,但我只是在努力解决同样的问题,并找到了这个解决方案:

http://aggressive-mediocrity.blogspot.com/2010/03/custom-localization-system-for-your.html

这正是您所需要的(对于有同样问题的其他人可能会有所帮助:)

答案 1 :(得分:5)

下面的链接有一个很好的实现,在应用程序中使用自定义语言。

manual language selection in an iOS-App (iPhone and iPad)

尝试SWIFT版本在此处找到它 LanguageSettings_Swift

LanguageChange

-anoop

答案 2 :(得分:3)

是的,我有同样的问题,然后我在我的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”, 或者有2个不同的.xib文件 并且对于要加载的文本,我使用不同的字典文件,每种语言一个,所有单词/表达式都已翻译,我只需加载所选单词并为其读入每个要加载的文本的正确表达式(加载它的代码)类似于我在这个例子中写的方法,你可以安排它为每个表达式读取正确的单词:只需在右侧的字典文件中查看objectForKey的值,其中objectForKey是要翻译的单词,其值是单词翻译)...

答案 3 :(得分:1)

通常,用户看到的语言由区域设置决定,区域设置是系统范围的设置。只有用户可以更改它,当他这样做时,SpringBoard和设备上的每个正在运行的应用程序都必须重新启动。没有办法解决这个问题,因为所有系统应用程序和框架都假设一旦开始运行,语言环境就不会改变。将应用程序和框架更改为不需要重新启动对Apple来说非常困难。

我猜您要么完全独立于系统区域设置而改变应用界面的语言,要么默认情况下要使用系统区域设置,但允许用户仅为您的应用覆盖它。

您可以使用+[NSLocale currentLocale]获取当前区域设置并检查其各种值。要以独立于系统区域设置的语言显示应用程序的用户界面,您需要完全避免使用NSLocalizedString(),并使用您自己的某种自定义状态来确定要显示的字符串。用户以及如何修改界面以适合您的应用程序的语言。您可以自行保留应用的语言状态并适当修改其用户界面。

答案 4 :(得分:0)

根据Apple guidelines,以编程方式更改应用中的语言并不是一个好主意,但如果您无权更改请求的行为,则可以执行下一步操作:

  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("Must Override to provide inApp localization functionality")
        }
    }
    
  4. 继承您想要符合动态应用交换机功能的任何控制器并实现localizeUI() func

    final class WelcomeTableViewController: LoadableTableViewController
    
  5. 根据需要切换语言:

    LanguageService.service.switchToLanguage(.en)
    
  6. 所有localizabled字符串都应设置为:

    label.text = dynamicLocalizableString(<KEY_IN_STRINGS>)
    
  7. 注意:不要忘记添加Localizable.stringsLanguageName

    中相同的代码

    enter image description here

答案 5 :(得分:0)

这是一个古老的问题,但是我正在开发一个帮助程序,当即时更改语言时会通知我。

看看帮助程序的代码:

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

您已经翻译了字符串。

答案 6 :(得分:0)

在iOS 13中,用户可以选择特定于应用的语言。如果您确实要提供仅通过应用程序选择语言的功能,则可以提供在应用程序中打开设置以选择语言的功能。 https://developer.apple.com/videos/play/wwdc2019/403/