iOS国际化

Objective-C、Swift

920 发布: 2020/12/9 15:16 本文总阅读量

网上例子比较多步骤简单回顾

第一步:Projext - Localizations - + 自己需要的语言

第二步:Targets - Info 添加 Localized resources can be mixed YES

好像非必须,还未搞懂…

第三步:创建 Strings File 命名 InfoPlistLocalizable

InfoPlist.strings 存放系统的国际化
Localizable.strings 存放本地的国际化

选中InfoPlist或者Localizable右侧边栏勾选需要的语言就会产生下拉框,不同的语言区分文件填写

方法实现

Objextive-c

宏文件

#define YZMsg(key) [[RookieTools shareInstance] getStringForKey:key withTable:@"InfoPlist"]
#define CurrentLanguage @"will_show_language"
#define getImagename(a) [NSString stringWithFormat:@"%@%@",a,[Config canshu]]
#define lagType [[NSUserDefaults standardUserDefaults] objectForKey:CurrentLanguage]
#define ZH_CN @"zh-Hans"
#define EN @"en"

.h

@interface RookieTools : NSObject

+(id)shareInstance;
/**
 *  返回table中指定的key的值
 *
 *  @param key   key
 *  @param table table
 *
 *  @return 返回table中指定的key的值
 */
-(NSString *)getStringForKey:(NSString *)key withTable:(NSString *)table;
/**
 *  重置语言
 *
 *  @param language 新语言
 */
-(void)resetLanguage:(NSString*)language withFrom:(NSString *)appdelegate;

@end

.m

static RookieTools *shareTool = nil;

@interface RookieTools()

@property(nonatomic,strong)NSBundle *bundle;
@property(nonatomic,copy)NSString *language;

@end

@implementation RookieTools
+ (void)load {
    // 交换MJ的国际化方法
    Method mjMethod = class_getClassMethod([NSBundle class],@selector(mj_localizedStringForKey:value:));
    Method myMethod = class_getClassMethod(self, @selector(hook_mj_localizedStringForKey:value:));
    method_exchangeImplementations(mjMethod, myMethod);
}
/// hook刷新控件的提示文字
+ (NSString *)hook_mj_localizedStringForKey:(NSString *)key value:(NSString *)value {
    NSString *language;
    if ([lagType isEqual:ZH_CN]) {
        language = @"zh-Hans";
    }else{
        language = @"en";
    }
    NSBundle *bundle = [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]];
    return [bundle localizedStringForKey:key value:nil table:@"Localizable"];
}

+(id)shareInstance {
    @synchronized (self) {
        if (shareTool == nil) {
            shareTool = [[RookieTools alloc]init];
        }
    }
    return shareTool;
}
+(instancetype)allocWithZone:(struct _NSZone *)zone {
    if (shareTool == nil) {
        shareTool = [super allocWithZone:zone];
    }
    return shareTool;
}

-(NSString *)getStringForKey:(NSString *)key withTable:(NSString *)table {
    if (self.bundle) {
        return NSLocalizedStringFromTableInBundle(key, table, self.bundle, @"");
    }
    return NSLocalizedStringFromTable(key, table, @"");
}

-(void)resetLanguage:(NSString *)language withFrom:(NSString *)appdelegate{
    if ([language isEqualToString:self.language]) {
        return;
    }
    if ([language isEqualToString:@"en"] || [language isEqualToString:@"zh-Hans"]) {
        NSString *path = [[NSBundle mainBundle]pathForResource:language ofType:@"lproj"];
        self.bundle = [NSBundle bundleWithPath:path];
    }
    self.language = language;
    [[NSUserDefaults standardUserDefaults] setObject:language forKey:CurrentLanguage];

    [[NSUserDefaults standardUserDefaults]synchronize];
    if (![appdelegate isEqualToString:@"appdelegate"]) {
        [self resetRootViewController];
    }
    
    // ===>20220426 并新增NSBundle扩展
    NSMutableArray *userDefaultLanguages = [[NSUserDefaults standardUserDefaults]objectForKey:@"AppleLanguages"];
    NSLog(@"rk-language-tool-get:%@",userDefaultLanguages);
    NSString *langStr = @"zh-Hans-US";
    if (![lagType isEqual:ZH_CN]) {
        langStr = @"en-US";
    }
    [NSBundle setLanguage:langStr];
    [[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObjects:langStr,nil] forKey:@"AppleLanguages"];
    [[NSUserDefaults standardUserDefaults] synchronize];
    
    NSMutableArray *userDefaultLanguages1 = [[NSUserDefaults standardUserDefaults]objectForKey:@"AppleLanguages"];
    NSLog(@"rk-language-tool-set:%@",userDefaultLanguages1);
    // <===20220426

}
-(void)resetRootViewController {
    UIWindow *window = [UIApplication sharedApplication].keyWindow;
    
    YBTabBarController *root = [[YBTabBarController alloc]init];
    UINavigationController *navi = [[UINavigationController alloc]initWithRootViewController:root];
    window.rootViewController = navi;
    [[TUIKit sharedInstance] initKit:TXIMSdkAppid accountType:TXIMSdkAccountType withConfig:[TUIKitConfig defaultConfig]];

//    [root changeLanguage];
    
}

@end

NSBundle扩展

注意:目前没有发现有什么作用,切换语言立即打开系统相机还是不会立刻发生变化…

.h

//
//  NSBundle+RKLanguage.h
//  iphoneLive
//
//  Created by YB007 on 2022/4/20.
//  Copyright © 2022 cat. All rights reserved.
//

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface NSBundle (RKLanguage)


// 设置语言
+ (void)setLanguage:(NSString *)language;

@end

NS_ASSUME_NONNULL_END

.m

//
//  NSBundle+RKLanguage.m
//  iphoneLive
//
//  Created by YB007 on 2022/4/20.
//  Copyright © 2022 cat. All rights reserved.
//

#import "NSBundle+RKLanguage.h"

#import <objc/runtime.h>

static const char _bundle = 0;

@interface BundleEx : NSBundle

@end

@implementation BundleEx

- (NSString *)localizedStringForKey:(NSString *)key value:(NSString *)value table:(NSString *)tableName {
    NSBundle *bundle = objc_getAssociatedObject(self, &_bundle);
    NSString *locStr = bundle ? [bundle localizedStringForKey:key value:value table:tableName] : [super localizedStringForKey:key value:value table:tableName];
    NSLog(@"rk-language-budle-set-val:%@",locStr);
    return locStr;
}

@end


@implementation NSBundle (RKLanguage)

+ (void)setLanguage:(NSString *)language {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        object_setClass([NSBundle mainBundle], [BundleEx class]);
    });
    objc_setAssociatedObject([NSBundle mainBundle], &_bundle, language ? [NSBundle bundleWithPath:[[NSBundle mainBundle] pathForResource:language ofType:@"lproj"]] : nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    NSLog(@"rk-language-budle-set-language:%@",language);
}

@end

Swift

import UIKit
import MJRefresh
/// 键值替换
func rkLocalized(key:String) -> String {
    let currnetL = rkLanguageType()
    let getPath = Bundle.main.path(forResource: currnetL as String, ofType: "lproj")
    guard let pathL = getPath else {
        return key
    }
    let getValue = Bundle.init(path: pathL)?.localizedString(forKey: key as String, value: nil, table: "Localizable")
    let value:String = getValue ?? key
    return value
}

/// 初始化
private let onceToken = "Method Swizzling"
func rkLanguageInit() {
    if (UserDefaults.standard.object(forKey: rkLanguageKey) == nil) {
        let languageArray = NSLocale.preferredLanguages;
        let languageStr = languageArray[0]
        if languageStr.hasPrefix(rkZhCn as String) {
            UserDefaults.standard.set(rkZhCn, forKey: rkLanguageKey)
        }else{
            UserDefaults.standard.set(rkEn, forKey: rkLanguageKey)
        }
        UserDefaults.standard.synchronize()
    }
    
    //动态设置MJ国际化
    let langClas = RKHook()
    langClas.mjLanguage()
//    langClas.hookTest()
    
}

/// 获取语言
func rkLanguageType() -> String {
    return UserDefaults.standard.object(forKey: rkLanguageKey) as! String
}

/// 切换语言
func rkSwitchLanguage() {
    let currentL = rkLanguageType()
    if currentL.isEqual(rkEn) {
        UserDefaults.standard.set(rkZhCn, forKey: rkLanguageKey)
    }else{
        UserDefaults.standard.set(rkEn, forKey: rkLanguageKey)
    }
    UserDefaults.standard.synchronize()
    
    let tabVC = RKTabBarController()
    UIApplication.shared.rkWindow?.rootViewController = tabVC
    UIApplication.shared.rkWindow?.makeKeyAndVisible()
    tabVC.selectedIndex = 4
    let setVC = RKSetVC()
    UIApplication.shared.pushViewController(setVC, animated: false)
    
}
/**
 * Swift 自定义类使用 method swizzling
 * 1.包含 swizzle 的class必须是继承自 NSObject
 * 2.swizzle 方法必须有动态属性
 */
class RKHook:NSObject {
    @objc  var aBool: Bool = true
    /// 测试方法
    @objc func hookTest(){
        var methodNum:UInt32 = 0
        let rkHook:RKHook = RKHook()
        let methodList = class_copyMethodList(object_getClass(rkHook), &methodNum)
        rkprint("method-start")
        for index in 0 ..< numericCast(methodNum) {
            
            let method: Method = methodList![index]
            rkprint(String(utf8String: method_getTypeEncoding(method)!) as Any)
            rkprint(String(utf8String: method_copyReturnType(method)) as Any)
            rkprint(String(_sel:method_getName(method)))
            rkprint("==a:\(method_getImplementation(method))")
        }
        rkprint("method-end")
        
        rkprint("property-start")
        var propertyNums:UInt32 = 0
        let propertyList = class_copyPropertyList(object_getClass(rkHook), &propertyNums)
        for index in 0 ..< numericCast(propertyNums) {
            let property:objc_property_t = propertyList![index]
            rkprint(String(utf8String: property_getName(property)) as Any)
            rkprint(String(utf8String: property_getAttributes(property)!) as Any)
        }
        rkprint("property-end")
    }
    /// 动态设置MJ的国际化
    @objc func mjLanguage() {
        DispatchQueue.once(token: onceToken) {
            let mj_selector = #selector(Bundle.mj_localizedString(forKey:value:))
            let hook_selector = #selector(self.hook_mj_localizedString(forKey:value:))
            
            let mjMethod = class_getClassMethod(Bundle.self, mj_selector)
            let hookMethod = class_getInstanceMethod(RKHook.self, hook_selector)
            method_exchangeImplementations(mjMethod!, hookMethod!)
            
            /*
            //在进行 Swizzling 的时候,需要用 class_addMethod 先进行判断一下原有类中是否有要替换方法的实现
            let didAddMethod: Bool = class_addMethod(Bundle.self, mj_selector, method_getImplementation(hookMethod!), method_getTypeEncoding(hookMethod!))
            //如果 class_addMethod 返回 yes,说明当前类中没有要替换方法的实现,所以需要在父类中查找,这时候就用到 method_getImplemetation 去获取 class_getInstanceMethod 里面的方法实现,然后再进行 class_replaceMethod 来实现 Swizzing
            if didAddMethod {
                class_replaceMethod(RKHook.self, hook_selector, method_getImplementation(mjMethod!), method_getTypeEncoding(mjMethod!))
            } else {
                method_exchangeImplementations(mjMethod!, hookMethod!)
            }
            */
        }
    }
    
    @objc dynamic func hook_mj_localizedString(forKey:String,value:String) -> String{
        let currentL = rkLanguageType()
        var mjLanguage:String = ""
        if currentL.isEqual(rkEn) {
            mjLanguage = "en"
        }else{
            mjLanguage = "zh-Hans"
        }
        let getPath = Bundle.mj_refresh().path(forResource: mjLanguage, ofType: "lproj")
        //rkprint("===kk:\(String(describing: getPath))")
        guard let pathL = getPath else {
            return currentL
        }
        let rkMjBundle = Bundle.init(path: pathL)
        guard let getMjb = rkMjBundle else {
            return currentL
        }
        let getValue = getMjb.localizedString(forKey: forKey, value: nil, table: "Localizable")
        let value:String = getValue
        //rkprint("kkkllll:\(forKey)\nbbbbb:\(value)")
        return value
    }
    
}

推送

推送国际化

总结

oc中可以使用load方法来来实现动态改变MJ的国际化

Swift要想利用oc的运行时需要注意两点:
1.继承自NSObject
2.动态属性

参考
如何在 Swift 中高效地使用 Method Swizzling