Je crée l'application ios sur XCODE 6.3 par Swift . Et mon application aura la fonction de choix de la langue, comme l'image ci-dessous.
J'ai déjà un storyboard pour ma langue locale. Mais je ne peux pas trouver comment changer la localisation par programmation en dehors de l'application par le bouton.
Quelqu'un sait comment le faire
Voici un moyen de le changer à la volée avec Swift, ajoutez une fonction d'extension à 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: "")
}}
en supposant ensuite que vous avez les chaînes locales Localizable.strings configurées avec lang_id.lproj (par exemple, en.lproj, de.lproj, etc.), vous pouvez l’utiliser n'importe où:
var val = "MY_LOCALIZED_STRING".localized("de")
Cela permet de changer la langue simplement en mettre à jour une clé UserDefaults
.
Ceci est basé sur l'excellente réponse de @dijipiji. Ceci est une version 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: "")
}
}
Utilisation
Ajoutez simplement .localized
à votre chaîne, en tant que tel:
"MyString".localized
, MyString
étant une clé du fichier Localizable.strings
.
Changer de langue
UserDefaults.standard.set("en", forKey: "i18n_language")
après avoir passé plusieurs jours, j'ai effectivement trouvé la solution. pas besoin de re-lancement, assez élégant: http://www.factorialcomplexity.com/blog/2015/01/28/how-to-change-localization-internally-in-y-votre-ios-application.html , vérifie la méthode # 2. Il n'est pas nécessaire de rétablir manuellement tous les titres et textes, mais remplace simplement la localisation pour la catégorie NSBundle personnalisée. Fonctionne comme un charme sur les projets Obj-C et Swift (après quelques ajustements)… Je me demandais si cela allait être approuvé par Apple, mais c'est ce qui est arrivé.
La réponse de Jeremy ( ici ) fonctionne également bien sur Swift 4 (je viens de tester avec une application simple et j'ai changé la langue utilisée dans le contrôleur de vue initial).
Voici la version Swift du même morceau de code (pour certaines raisons, mes coéquipiers préfèrent Swift uniquement que le mélange avec Objective-C, je l'ai donc traduit):
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)
}
}
Code utilisable dans 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!)
}
}
et
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)
}
}
appel:
let localizedString = "enter".localized()
définir une nouvelle locale (par exemple "ru"):
Bundle.setLanguage(lang: "ru")
Essaye ça
NSUserDefaults.standardUserDefaults().setObject(["es", "de", "it"], forKey: "AppleLanguages")
NSUserDefaults.standardUserDefaults().synchronize()
Source: ici
Swift 4.2
Dans mon cas, si l'utilisateur change le paramètre de langue, je dois mettre à jour 2 choses au moment de l'exécution.
1. Localizable.strings
2. Localisation du storyboard
Je rends le code de @John Pang plus rapide
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
}
}
}
Utiliser comme ceci
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" renvoie toujours la langue du paramètre système. Nous devons donc utiliser "Locale.preferredLanguages.first". Cependant, la valeur de retour ressemble à "ko-US". C'est un problème ! J'ai donc fait le LocaleManager pour obtenir uniquement le code de langue.
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)
}
}
Utiliser comme ceci
guard let languageCode = LocaleManager.languageCode, let title = RemoteConfiguration.shared.logIn?.main?.title?[languageCode] else {
return NSLocalizedString("Welcome!", comment: "")
}
return title
La solution liée par whiteagle fonctionne réellement pour changer de langue à la volée. Voici le post.
J'ai simplifié l'exemple de code sur un seul fichier .h/.m qui changera la langue à la volée, en mémoire. J'ai montré comment l'appeler depuis Swift 3.
Entête:
//
// 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
La mise en oeuvre:
//
// 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
Pour appeler cela depuis Swift, assurez-vous que votre en-tête de pontage a:
#import "NSBundle+Language.h"
Puis à partir de votre code, appelez:
Bundle.setLanguage("es")
Choses à noter:
Je n'ai inclus aucun exemple de code pour montrer un sélecteur de langue ou quoi que ce soit. La publication liée originale en inclut certains.
J'ai changé ce code pour ne rien changer de façon persistante. Lors de la prochaine exécution de l'application, l'application tentera toujours d'utiliser la langue préférée de l'utilisateur. (La seule exception concerne les langues de droite à gauche, voir ci-dessous)
Vous pouvez le faire à tout moment avant le chargement d'une vue et les nouvelles chaînes entreront en vigueur. Toutefois, si vous devez modifier une vue déjà chargée, vous souhaiterez peut-être réinitialiser rootViewController comme indiqué dans la publication d'origine.
Cela devrait fonctionner pour les langues de droite à gauche, mais cela définit deux préférences internes persistantes dans NSUserDefaults pour ces langues. Vous voudrez peut-être annuler cela en remettant la langue par défaut à la fermeture de l'application: Bundle.setLanguage(Locale.preferredLanguages.first!)
Voici une réponse mise à jour pour 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: "")
Tout d’abord - c’est une mauvaise idée et Apple
recommandent d’utiliser la langue sélectionnée par iOS pour la localisation.
Mais si vous en avez vraiment besoin, vous pouvez créer un petit service à cet effet.
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
}
}
}
Et que make rootViewControllerClass ressemble à ceci:
import Foundation
protocol Localizable {
func localizeUI()
}
et
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 hérite de chaque contrôleur de LocalizableViewController
et implémente localizeUI()
Et au lieu de NSLocalizedString
, utilisez dynamicLocalizableString
comme:
func localizeOnceUI() {
label.text = dynamicLocalizableString("keyFrom<"Localizable.strings">")
}
Pour changer de langue:
LanguageService.service.switchToLanguage(.en)
Notez également - des étapes supplémentaires et une modification de la logique sont nécessaires si vous souhaitez localiser de manière dynamique vos widgets ou d'autres éléments d'application.
Voici ma solution avec l'extension String. Amélioration de la sécurité 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: "")
}
}
Ceci est étendu La solution de John Pang , si vous devez traduire immédiatement les chaînes système (Retour, Annuler, Terminé ...)
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 vous souhaitez traduire des chaînes système, vous devez faire de même pour UIKit Bundle.
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: "")
}
}
Dernière syntaxe 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: "")
}
}