strings multi localize localizable language generate app ios xcode swift localization

ios - multi - swift string localization



¿Cómo puedo cambiar la configuración regional mediante programación con Swift? (14)

Última sintaxis de Swift:

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

Estoy haciendo la aplicación ios en XCODE 6.3 por Swift. Y mi aplicación tendrá la función de elegir idioma como la imagen a continuación

Ya tengo storyboard para mi idioma local. Pero no puedo averiguar cómo cambiar la localización mediante programación fuera de la aplicación con el botón.

Alguien sabe cómo hacerlo


Aquí está mi solución con la extensión de cadena. Seguridad mejorada de @Das answer.

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


Aquí hay una manera de cambiarlo sobre la marcha con Swift, agregue una función de extensión a 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: "") }}

luego, suponiendo que tenga las cadenas localizables regulares configuradas con lang_id.lproj (por ejemplo, en.lproj, de.lproj, etc.) puede usar esto en cualquier lugar que necesite:

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


Aquí hay una respuesta actualizada para 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: "")


Código utilizable en 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!) } }

y

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

llamada:

let localizedString = "enter".localized()

establecer un nuevo entorno local (por ejemplo, "ru"):

Bundle.setLanguage(lang: "ru")


Después de pasar varios días he encontrado la solución. no necesita relanzamiento, bastante elegante: http://www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-your-ios-application.html , consulte El método # 2. No requiere restablecer manualmente todos los títulos y textos, solo anula la localización para la categoría personalizada NSBundle. Funciona en proyectos Obj-C y Swift (después de algunos ajustes) como un encanto. Tenía algunas dudas sobre si Apple lo aprobaría, pero en realidad lo hizo.


En primer lugar, esta es una mala idea y Apple recomienda usar el idioma seleccionado de iOS para la localización.

Pero si realmente lo necesita, puede hacer un pequeño servicio para tal fin

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

Y luego hacer rootViewControllerClass como:

import Foundation protocol Localizable { func localizeUI() }

y

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

Que heredar todos los controladores de LocalizableViewController e implementar localizeUI()

Y en lugar de NSLocalizedString use dynamicLocalizableString como:

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

Para cambiar de idioma:

LanguageService.service.switchToLanguage(.en)

También tenga en cuenta: se requieren pasos adicionales y modificación de la lógica si desea localizar dinámicamente sus widgets u otras partes de la aplicación.


Esta es la solución extendida de John Pang , si necesita traducir las cadenas del sistema inmediatamente (Atrás, Cancelar, Listo ...):

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

Si desea traducir las cadenas del sistema, debe hacer lo mismo para UIKit Bundle.


Esto permite cambiar el idioma simplemente actualizando una clave UserDefaults .

Esto se basa en la gran respuesta de @dijipiji. Esta es una versión de 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: "") } }

Uso

Simplemente agregue .localized a su cadena, como tal:

"MyString".localized , MyString es una clave en el archivo Localizable.strings .

Cambiando el idioma

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


La respuesta de Jeremy ( here ) también funciona bien en Swift 4 (acabo de probar con una aplicación simple y cambié el idioma utilizado en el controlador de vista inicial).

Aquí está la versión Swift del mismo código (por algunas razones, mis compañeros de equipo prefieren Swift solo que mezclado con Objective-C, así que lo traduje):

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


La solución vinculada por realidad funciona para cambiar el idioma sobre la marcha. Aquí está la publicación.

Simplifiqué el código de muestra allí a un solo .h / .m que cambiará el idioma sobre la marcha, en la memoria. He mostrado cómo llamarlo desde Swift 3.

Encabezamiento:

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

Implementación:

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

Para llamar a esto desde Swift, asegúrese de que su Encabezado de puente tenga:

#import "NSBundle+Language.h"

Luego, desde su código, llame al:

Bundle.setLanguage("es")

Cosas a tener en cuenta:

  • No incluí ningún código de muestra para mostrar un selector de idioma ni nada. La publicación original vinculada incluye algunos.

  • Cambié este código para no cambiar nada de forma persistente. La próxima vez que se ejecute la aplicación, seguirá intentando utilizar el idioma preferido del usuario. (La única excepción son los idiomas de derecha a izquierda, ver más abajo)

  • Puede hacerlo en cualquier momento antes de cargar una vista, y las nuevas cadenas surtirán efecto. Sin embargo, si necesita cambiar una vista que ya está cargada, puede reiniciar rootViewController como dice la publicación original.

  • Esto debería funcionar para los idiomas de derecha a izquierda, pero establece dos preferencias internas persistentes en NSUserDefaults para esos idiomas. Es posible que desee deshacer eso volviendo a configurar el idioma al valor predeterminado del usuario al salir de la aplicación: Bundle.setLanguage(Locale.preferredLanguages.first!)


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

Fuente: here


Swift 4.2

En mi caso, si el usuario cambia la configuración de idioma, tengo que actualizar 2 cosas en tiempo de ejecución.

1. Cadenas localizables

2. Localización del guión gráfico

Hago que el código de @John Pang sea más rápido

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

Usar así

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)

" Locale.current.languageCode " siempre devolverá el idioma de configuración del sistema. Entonces tenemos que usar " Locale.preferredLanguages.first ". Sin embargo, el valor de retorno se parece a "ko-US". Esto es un problema ! Así que hice el LocaleManager para obtener solo el código de idioma.

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

Usar así

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


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