From 1673a2f4be37dbca0adaef65c8c76ba361b59431 Mon Sep 17 00:00:00 2001 From: CodeST <694468528@qq.com> Date: Mon, 3 Nov 2025 16:37:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=A4=9A=E8=AF=AD=E8=A8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CustomKeyboard/PrefixHeader.pch | 1 + Shared/KBLocalizationManager.h | 57 ++++++ Shared/KBLocalizationManager.m | 180 ++++++++++++++++++ .../Localization/en.lproj/Localizable.strings | 18 ++ .../zh-Hans.lproj/Localizable.strings | 18 ++ keyBoard.xcodeproj/project.pbxproj | 39 ++++ keyBoard/Class/Home/VC/HomeVC.m | 64 ++++++- keyBoard/Class/Home/VC/KBLangTestVC.h | 14 ++ keyBoard/Class/Home/VC/KBLangTestVC.m | 61 ++++++ keyBoard/KeyBoardPrefixHeader.pch | 1 + keyBoard/VC/KBPermissionViewController.m | 9 +- 11 files changed, 449 insertions(+), 13 deletions(-) create mode 100644 Shared/KBLocalizationManager.h create mode 100644 Shared/KBLocalizationManager.m create mode 100644 Shared/Localization/en.lproj/Localizable.strings create mode 100644 Shared/Localization/zh-Hans.lproj/Localizable.strings create mode 100644 keyBoard/Class/Home/VC/KBLangTestVC.h create mode 100644 keyBoard/Class/Home/VC/KBLangTestVC.m diff --git a/CustomKeyboard/PrefixHeader.pch b/CustomKeyboard/PrefixHeader.pch index 53bc43b..5d96a48 100644 --- a/CustomKeyboard/PrefixHeader.pch +++ b/CustomKeyboard/PrefixHeader.pch @@ -19,6 +19,7 @@ #import "KBConfig.h" #import "Masonry.h" #import "KBHUD.h" // 复用 App 内的 HUD 封装 +#import "KBLocalizationManager.h" // 复用多语言封装(可在扩展内使用) // 通用链接(Universal Links)统一配置 // 配置好 AASA 与 Associated Domains 后,只需修改这里即可切换域名/path。 diff --git a/Shared/KBLocalizationManager.h b/Shared/KBLocalizationManager.h new file mode 100644 index 0000000..ba6430c --- /dev/null +++ b/Shared/KBLocalizationManager.h @@ -0,0 +1,57 @@ +// +// KBLocalizationManager.h +// 多语言管理(App 与键盘扩展共用) +// 功能: +// - 运行时切换语言(不依赖系统设置) +// - 可选跨 Target 同步(共享钥匙串),让 App 与扩展语言一致 +// - 提供便捷宏 KBLocalized(key) +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +/// 当前语言变更通知(不附带 userInfo) +extern NSString * const KBLocalizationDidChangeNotification; + +/// 轻量多语言管理器:支持运行时切换、跨 Target 同步 +@interface KBLocalizationManager : NSObject + +/// 单例 ++ (instancetype)shared; + +/// 当前语言代码(如:"en"、"zh-Hans"、"ja")。 +/// 默认会在受支持语言中,按系统首选语言择优匹配。 +@property (nonatomic, copy, readonly) NSString *currentLanguageCode; + +/// 支持的语言代码列表。默认 @[@"en", @"zh-Hans"]。 +/// 建议在启动早期设置;或设置后再调用 `-setCurrentLanguageCode:persist:` 以刷新。 +@property (nonatomic, copy) NSArray *supportedLanguageCodes; + +/// 设置当前语言。 +/// @param code 语言代码 +/// @param persist 是否持久化到共享钥匙串(以便 App 与扩展共享该选择) +- (void)setCurrentLanguageCode:(NSString *)code persist:(BOOL)persist; + +/// 清除用户选择,恢复为系统最佳匹配。 +- (void)resetToSystemLanguage; + +/// 从默认表(Localizable.strings)取文案。 +- (NSString *)localizedStringForKey:(NSString *)key; + +/// 指定表名(不含扩展名)取文案。 +- (NSString *)localizedStringForKey:(NSString *)key + table:(nullable NSString *)table + value:(nullable NSString *)value; + +/// 基于一组“偏好语言”计算最佳支持语言。 +- (NSString *)bestSupportedLanguageForPreferred:(NSArray *)preferred; + +@end + +/// 便捷宏:与 NSLocalizedString 类似,但遵循 KBLocalizationManager 当前语言 +#ifndef KBLocalized +#define KBLocalized(key) [[KBLocalizationManager shared] localizedStringForKey:(key)] +#endif + +NS_ASSUME_NONNULL_END diff --git a/Shared/KBLocalizationManager.m b/Shared/KBLocalizationManager.m new file mode 100644 index 0000000..d9cc341 --- /dev/null +++ b/Shared/KBLocalizationManager.m @@ -0,0 +1,180 @@ +// +// KBLocalizationManager.m +// 多语言管理实现 +// + +#import "KBLocalizationManager.h" +#import +#import "KBConfig.h" + +/// 语言变更通知名称 +NSString * const KBLocalizationDidChangeNotification = @"KBLocalizationDidChangeNotification"; + +// 通过共享钥匙串跨 Target 持久化语言选择 +static NSString * const kKBLocService = @"com.keyBoardst.loc"; +static NSString * const kKBLocAccount = @"lang"; // 保存 UTF8 的语言代码 + +@interface KBLocalizationManager () +@property (nonatomic, copy, readwrite) NSString *currentLanguageCode; // 当前语言代码 +@property (nonatomic, strong) NSBundle *langBundle; // 对应语言的 .lproj 资源包 +@end + +// 避免 +shared 初始化阶段递归触发自身: +// 这里提供一个 C 级别的工具函数,构建钥匙串查询,不依赖实例或 +shared。 +static inline NSMutableDictionary *KBLocBaseKCQuery(void) { + NSMutableDictionary *q = [@{ (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword, + (__bridge id)kSecAttrService: kKBLocService, + (__bridge id)kSecAttrAccount: kKBLocAccount } mutableCopy]; + if (KB_KEYCHAIN_ACCESS_GROUP.length > 0) { + q[(__bridge id)kSecAttrAccessGroup] = KB_KEYCHAIN_ACCESS_GROUP; + } + return q; +} + +@implementation KBLocalizationManager + ++ (instancetype)shared { + static KBLocalizationManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ + m = [KBLocalizationManager new]; + // 默认支持语言;可在启动时由外部重置 + m.supportedLanguageCodes = @[ @"en", @"zh-Hans" ]; + // 启动读取:先取共享钥匙串,再按系统偏好回退 + NSString *saved = [[self class] kc_read]; + if (saved.length == 0) { + saved = [m bestSupportedLanguageForPreferred:[NSLocale preferredLanguages]] ?: @"en"; + } + [m applyLanguage:saved]; + }); + return m; +} + +#pragma mark - 对外 API + +- (void)setSupportedLanguageCodes:(NSArray *)supportedLanguageCodes { + // 归一化:去重、去空 + NSMutableOrderedSet *set = [NSMutableOrderedSet orderedSet]; + for (NSString *c in supportedLanguageCodes) { + if (c.length) { [set addObject:c]; } + } + _supportedLanguageCodes = set.array.count ? set.array : @[ @"en" ]; + // 若当前语言不再受支持,则按最佳匹配切回(不持久化,仅内存),并广播变更 + if (self.currentLanguageCode.length && ![set containsObject:self.currentLanguageCode]) { + NSString *best = [self bestSupportedLanguageForPreferred:@[self.currentLanguageCode]]; + [self applyLanguage:best ?: _supportedLanguageCodes.firstObject]; + [[NSNotificationCenter defaultCenter] postNotificationName:KBLocalizationDidChangeNotification object:nil]; + } +} + +- (void)setCurrentLanguageCode:(NSString *)code persist:(BOOL)persist { + if (code.length == 0) return; // 忽略空值 + if ([code isEqualToString:self.currentLanguageCode]) return; // 无变更 + [self applyLanguage:code]; + if (persist) { [[self class] kc_write:code]; } // 需同步到 App/扩展 + [[NSNotificationCenter defaultCenter] postNotificationName:KBLocalizationDidChangeNotification object:nil]; +} + +- (void)resetToSystemLanguage { + NSString *best = [self bestSupportedLanguageForPreferred:[NSLocale preferredLanguages]] ?: @"en"; + [self setCurrentLanguageCode:best persist:NO]; +} + +- (NSString *)localizedStringForKey:(NSString *)key { + return [self localizedStringForKey:key table:nil value:key]; +} + +- (NSString *)localizedStringForKey:(NSString *)key table:(NSString *)table value:(NSString *)value { + if (key.length == 0) return @""; + NSBundle *bundle = self.langBundle ?: NSBundle.mainBundle; + NSString *tbl = table ?: @"Localizable"; + // 使用 bundle API,避免 NSLocalizedString 被系统语言钉死 + NSString *str = [bundle localizedStringForKey:key value:value table:tbl]; + return str ?: (value ?: key); +} + +- (NSString *)bestSupportedLanguageForPreferred:(NSArray *)preferred { + if (self.supportedLanguageCodes.count == 0) return @"en"; + // 1) 完全匹配 + for (NSString *p in preferred) { + NSString *pLC = p.lowercaseString; + for (NSString *s in self.supportedLanguageCodes) { + if ([pLC isEqualToString:s.lowercaseString]) { return s; } + } + } + // 2) 前缀匹配:如 zh-Hans-CN -> zh-Hans, en-GB -> en + for (NSString *p in preferred) { + NSString *pLC = p.lowercaseString; + for (NSString *s in self.supportedLanguageCodes) { + NSString *sLC = s.lowercaseString; + if ([pLC hasPrefix:[sLC stringByAppendingString:@"-"]] || [pLC hasPrefix:[sLC stringByAppendingString:@"_"]]) { + return s; + } + // also allow reverse: when supported is regional (rare) + if ([sLC hasPrefix:[pLC stringByAppendingString:@"-"]] || [sLC hasPrefix:[pLC stringByAppendingString:@"_"]]) { + return s; + } + } + } + // 3) 特殊处理中文:将 zh-Hant/zh-TW/zh-HK 映射到 zh-Hant(若受支持) + for (NSString *p in preferred) { + NSString *pLC = p.lowercaseString; + if ([pLC hasPrefix:@"zh-hant"] || [pLC hasPrefix:@"zh-tw"] || [pLC hasPrefix:@"zh-hk"]) { + for (NSString *s in self.supportedLanguageCodes) { + if ([s.lowercaseString isEqualToString:@"zh-hant"]) { return s; } + } + } + if ([pLC hasPrefix:@"zh-hans"] || [pLC hasPrefix:@"zh-cn"]) { + for (NSString *s in self.supportedLanguageCodes) { + if ([s.lowercaseString isEqualToString:@"zh-hans"]) { return s; } + } + } + } + // 4) 兜底:取第一个受支持语言 + return self.supportedLanguageCodes.firstObject ?: @"en"; +} + +#pragma mark - 内部实现 + +- (void)applyLanguage:(NSString *)code { + _currentLanguageCode = [code copy]; + // 基于当前 Target(App 或扩展)的主 bundle 加载 .lproj 资源 + NSString *path = [NSBundle.mainBundle pathForResource:code ofType:@"lproj"]; + if (!path) { + // 尝试去区域后缀:如 en-GB -> en + NSString *shortCode = [[code componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"-_"]] firstObject]; + if (shortCode.length > 0) { + path = [NSBundle.mainBundle pathForResource:shortCode ofType:@"lproj"]; + } + } + if (path) { + self.langBundle = [NSBundle bundleWithPath:path]; + } else { + self.langBundle = NSBundle.mainBundle; // 兜底 + } +} + +#pragma mark - 钥匙串读写(App/扩展共享) + ++ (BOOL)kc_write:(NSString *)lang { + NSMutableDictionary *q = KBLocBaseKCQuery(); + SecItemDelete((__bridge CFDictionaryRef)q); + if (lang.length == 0) return YES; // 等价于删除 + NSData *data = [lang dataUsingEncoding:NSUTF8StringEncoding]; + q[(__bridge id)kSecValueData] = data ?: [NSData data]; + q[(__bridge id)kSecAttrAccessible] = (__bridge id)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; + OSStatus st = SecItemAdd((__bridge CFDictionaryRef)q, NULL); + return (st == errSecSuccess); +} + ++ (NSString *)kc_read { + NSMutableDictionary *q = KBLocBaseKCQuery(); + q[(__bridge id)kSecReturnData] = @YES; + q[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne; + CFTypeRef dataRef = NULL; OSStatus st = SecItemCopyMatching((__bridge CFDictionaryRef)q, &dataRef); + if (st != errSecSuccess || !dataRef) return nil; + NSData *data = (__bridge_transfer NSData *)dataRef; + if (data.length == 0) return nil; + NSString *lang = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; + return lang; +} + +@end diff --git a/Shared/Localization/en.lproj/Localizable.strings b/Shared/Localization/en.lproj/Localizable.strings new file mode 100644 index 0000000..ac2d787 --- /dev/null +++ b/Shared/Localization/en.lproj/Localizable.strings @@ -0,0 +1,18 @@ +/* + Localizable.strings (English) + Keys shared by App and Keyboard extension +*/ + +"perm_title_enable" = "Enable Keyboard"; +"perm_steps" = "1 Enable Keyboard > 2 Allow Full Access"; +"perm_open_settings" = "Open in Settings"; +"perm_help" = "Can't find the keyboard? Go to Settings > General > Keyboard > Keyboards > Add New Keyboard"; + +// Home page & language test +"home_title" = "Home"; +"home_input_placeholder" = "Type here to test the keyboard"; +"home_item_lang_test" = "Language Test"; + +"lang_test_title" = "Language Test"; +"lang_toggle" = "Toggle Language"; +"current_lang" = "Current: %@"; diff --git a/Shared/Localization/zh-Hans.lproj/Localizable.strings b/Shared/Localization/zh-Hans.lproj/Localizable.strings new file mode 100644 index 0000000..2b89438 --- /dev/null +++ b/Shared/Localization/zh-Hans.lproj/Localizable.strings @@ -0,0 +1,18 @@ +/* + Localizable.strings (简体中文) + App 与键盘扩展共用的文案 Key +*/ + +"perm_title_enable" = "启用输入法"; +"perm_steps" = "1 开启键盘 > 2 允许完全访问"; +"perm_open_settings" = "去设置中开启"; +"perm_help" = "没有找到键盘? 请前往 设置 > 通用 > 键盘 > 键盘 > 添加新键盘"; + +// 首页与多语言测试 +"home_title" = "首页"; +"home_input_placeholder" = "在此输入,测试键盘"; +"home_item_lang_test" = "多语言测试"; + +"lang_test_title" = "多语言测试"; +"lang_toggle" = "切换语言"; +"current_lang" = "当前:%@"; diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 792db1c..1e31e13 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -11,6 +11,10 @@ 04A9FE0F2EB481100020DB6D /* KBHUD.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC97082EB31B14007BD342 /* KBHUD.m */; }; 04A9FE132EB4D0D20020DB6D /* KBFullAccessManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */; }; 04A9FE162EB873C80020DB6D /* UIViewController+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */; }; + 04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE192EB892460020DB6D /* KBLocalizationManager.m */; }; + 04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04A9FE192EB892460020DB6D /* KBLocalizationManager.m */; }; + 04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 04A9FE1E2EB893F10020DB6D /* Localizable.strings */; }; + 04A9FE212EB893F10020DB6D /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 04A9FE1E2EB893F10020DB6D /* Localizable.strings */; }; 04C6EABA2EAF86530089C901 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 04C6EAAE2EAF86530089C901 /* Assets.xcassets */; }; 04C6EABC2EAF86530089C901 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04C6EAB12EAF86530089C901 /* LaunchScreen.storyboard */; }; 04C6EABD2EAF86530089C901 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 04C6EAB42EAF86530089C901 /* Main.storyboard */; }; @@ -34,6 +38,7 @@ 04FC95D22EB1E7AE007BD342 /* MyVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95D12EB1E7AE007BD342 /* MyVC.m */; }; 04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95D62EB1EA16007BD342 /* BaseTableView.m */; }; 04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95D42EB1EA16007BD342 /* BaseCell.m */; }; + A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */ = {isa = PBXBuildFile; fileRef = A1B2D7012EB8C00100000001 /* KBLangTestVC.m */; }; 04FC95DD2EB202A3007BD342 /* KBGuideVC.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95DC2EB202A3007BD342 /* KBGuideVC.m */; }; 04FC95E52EB220B5007BD342 /* UIColor+Extension.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E42EB220B5007BD342 /* UIColor+Extension.m */; }; 04FC95E92EB23B67007BD342 /* KBNetworkManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 04FC95E72EB23B67007BD342 /* KBNetworkManager.m */; }; @@ -86,6 +91,10 @@ 04A9FE112EB4D0D20020DB6D /* KBFullAccessManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBFullAccessManager.m; sourceTree = ""; }; 04A9FE142EB873C80020DB6D /* UIViewController+Extension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Extension.h"; sourceTree = ""; }; 04A9FE152EB873C80020DB6D /* UIViewController+Extension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Extension.m"; sourceTree = ""; }; + 04A9FE182EB892460020DB6D /* KBLocalizationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLocalizationManager.h; sourceTree = ""; }; + 04A9FE192EB892460020DB6D /* KBLocalizationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLocalizationManager.m; sourceTree = ""; }; + 04A9FE1C2EB893F10020DB6D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; + 04A9FE1D2EB893F10020DB6D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; 04C6EAAC2EAF86530089C901 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 04C6EAAD2EAF86530089C901 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 04C6EAAE2EAF86530089C901 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -127,6 +136,8 @@ 04FC95CB2EB1E780007BD342 /* BaseTabBarController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BaseTabBarController.m; sourceTree = ""; }; 04FC95CD2EB1E7A1007BD342 /* HomeVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HomeVC.h; sourceTree = ""; }; 04FC95CE2EB1E7A1007BD342 /* HomeVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HomeVC.m; sourceTree = ""; }; + A1B2D7002EB8C00100000001 /* KBLangTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLangTestVC.h; sourceTree = ""; }; + A1B2D7012EB8C00100000001 /* KBLangTestVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLangTestVC.m; sourceTree = ""; }; 04FC95D02EB1E7AE007BD342 /* MyVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MyVC.h; sourceTree = ""; }; 04FC95D12EB1E7AE007BD342 /* MyVC.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MyVC.m; sourceTree = ""; }; 04FC95D32EB1EA16007BD342 /* BaseCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BaseCell.h; sourceTree = ""; }; @@ -207,6 +218,14 @@ path = Manager; sourceTree = ""; }; + 04A9FE1F2EB893F10020DB6D /* Localization */ = { + isa = PBXGroup; + children = ( + 04A9FE1E2EB893F10020DB6D /* Localizable.strings */, + ); + path = Localization; + sourceTree = ""; + }; 04C6EAB92EAF86530089C901 /* keyBoard */ = { isa = PBXGroup; children = ( @@ -306,6 +325,8 @@ children = ( 04FC95CD2EB1E7A1007BD342 /* HomeVC.h */, 04FC95CE2EB1E7A1007BD342 /* HomeVC.m */, + A1B2D7002EB8C00100000001 /* KBLangTestVC.h */, + A1B2D7012EB8C00100000001 /* KBLangTestVC.m */, ); path = VC; sourceTree = ""; @@ -551,11 +572,14 @@ 04FC98002EB36AAB007BD342 /* Shared */ = { isa = PBXGroup; children = ( + 04A9FE1F2EB893F10020DB6D /* Localization */, 04FC98012EB36AAB007BD342 /* KBConfig.h */, A1B2C4002EB4A0A100000001 /* KBAuthManager.h */, A1B2C4002EB4A0A100000002 /* KBAuthManager.m */, A1B2C4232EB4B7A100000001 /* KBKeyboardPermissionManager.h */, A1B2C4222EB4B7A100000001 /* KBKeyboardPermissionManager.m */, + 04A9FE182EB892460020DB6D /* KBLocalizationManager.h */, + 04A9FE192EB892460020DB6D /* KBLocalizationManager.m */, ); path = Shared; sourceTree = ""; @@ -676,6 +700,7 @@ knownRegions = ( en, Base, + "zh-Hans", ); mainGroup = 727EC74A2EAF848B00B36487; minimizedProjectReferenceProxies = 1; @@ -695,6 +720,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 04A9FE202EB893F10020DB6D /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -703,6 +729,7 @@ buildActionMask = 2147483647; files = ( 04C6EABA2EAF86530089C901 /* Assets.xcassets in Resources */, + 04A9FE212EB893F10020DB6D /* Localizable.strings in Resources */, 04C6EABC2EAF86530089C901 /* LaunchScreen.storyboard in Resources */, 04C6EABD2EAF86530089C901 /* Main.storyboard in Resources */, ); @@ -790,6 +817,7 @@ 04C6EAD82EAF870B0089C901 /* KeyboardViewController.m in Sources */, 04FC95762EB095DE007BD342 /* KBFunctionPasteView.m in Sources */, A1B2C3D42EB0A0A100000001 /* KBFunctionTagCell.m in Sources */, + 04A9FE1A2EB892460020DB6D /* KBLocalizationManager.m in Sources */, A1B2C3E22EB0C0A100000001 /* KBNetworkManager.m in Sources */, 04FC956A2EB05497007BD342 /* KBKeyButton.m in Sources */, 04FC95B22EB0B2CC007BD342 /* KBSettingView.m in Sources */, @@ -814,6 +842,7 @@ 04C6EABE2EAF86530089C901 /* AppDelegate.m in Sources */, 04FC95F12EB339A7007BD342 /* LoginViewController.m in Sources */, 04FC96142EB34E00007BD342 /* KBLoginSheetViewController.m in Sources */, + 04A9FE1B2EB892460020DB6D /* KBLocalizationManager.m in Sources */, 04FC95D72EB1EA16007BD342 /* BaseTableView.m in Sources */, 04FC95D82EB1EA16007BD342 /* BaseCell.m in Sources */, 04FC95C92EB1E4C9007BD342 /* BaseNavigationController.m in Sources */, @@ -826,6 +855,7 @@ 04FC970E2EB334F8007BD342 /* UIImageView+KBWebImage.m in Sources */, 04FC970F2EB334F8007BD342 /* KBWebImageManager.m in Sources */, 04FC95CF2EB1E7A1007BD342 /* HomeVC.m in Sources */, + A1B2D7022EB8C00100000001 /* KBLangTestVC.m in Sources */, 04C6EABF2EAF86530089C901 /* main.m in Sources */, 04FC95CC2EB1E780007BD342 /* BaseTabBarController.m in Sources */, 04FC95F42EB339C1007BD342 /* AppleSignInManager.m in Sources */, @@ -846,6 +876,15 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + 04A9FE1E2EB893F10020DB6D /* Localizable.strings */ = { + isa = PBXVariantGroup; + children = ( + 04A9FE1C2EB893F10020DB6D /* en */, + 04A9FE1D2EB893F10020DB6D /* zh-Hans */, + ); + name = Localizable.strings; + sourceTree = ""; + }; 04C6EAB12EAF86530089C901 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( diff --git a/keyBoard/Class/Home/VC/HomeVC.m b/keyBoard/Class/Home/VC/HomeVC.m index 5959460..e24d760 100644 --- a/keyBoard/Class/Home/VC/HomeVC.m +++ b/keyBoard/Class/Home/VC/HomeVC.m @@ -7,10 +7,12 @@ #import "HomeVC.h" #import "KBGuideVC.h" +#import "KBLangTestVC.h" -@interface HomeVC () -@property (nonatomic, strong) UITextView *textView; - +@interface HomeVC () +@property (nonatomic, strong) UITextView *textView; // 作为表头,保持原有键盘测试 +@property (nonatomic, strong) UITableView *tableView; // 首页列表 +@property (nonatomic, copy) NSArray *items; // 简单数据源 @end @implementation HomeVC @@ -18,14 +20,32 @@ - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; - CGRect frame = CGRectMake(([UIScreen mainScreen].bounds.size.width - 200)/2, 150, 200, 200); + + // 表头中的文本输入框:保留原有键盘测试能力 + CGFloat width = [UIScreen mainScreen].bounds.size.width; + UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, width, 220)]; + CGRect frame = CGRectMake(16, 16, width - 32, 188); self.textView = [[UITextView alloc] initWithFrame:frame]; - self.textView.text = @"测试"; + self.textView.text = KBLocalized(@"home_input_placeholder"); self.textView.layer.borderColor = [UIColor blackColor].CGColor; self.textView.layer.borderWidth = 0.5; - [self.view addSubview:self.textView]; - [self.textView becomeFirstResponder]; - + [header addSubview:self.textView]; + + // 列表 + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStyleInsetGrouped]; + self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.tableView.tableHeaderView = header; + [self.view addSubview:self.tableView]; + + // 数据 + self.items = @[ KBLocalized(@"home_item_lang_test") ]; + + // 首次进入,聚焦到输入框方便测试键盘 + dispatch_async(dispatch_get_main_queue(), ^{ [self.textView becomeFirstResponder]; }); + + // 可选:示例网络请求(保持原逻辑) [[KBNetworkManager shared] GET:@"app/config" parameters:nil headers:nil completion:^(id _Nullable jsonOrData, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSLog(@"===="); }]; @@ -33,7 +53,11 @@ - (void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated]; - NSLog(@"==="); + // 刷新本页涉及的多语言文案 + self.title = KBLocalized(@"home_title"); + // 重建 items 以更新本地化的 cell 标题 + self.items = @[ KBLocalized(@"home_item_lang_test") ]; + [self.tableView reloadData]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{ @@ -44,6 +68,28 @@ // [KBHUD showAllowTapToDismiss:true]; } +#pragma mark - UITableViewDataSource +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return self.items.count; } +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { + static NSString *cellId = @"home.cell"; + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; + if (!cell) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId]; } + cell.textLabel.text = self.items[indexPath.row]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + return cell; +} + +#pragma mark - UITableViewDelegate +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + if (indexPath.row == 0) { + // 多语言测试页 + KBLangTestVC *vc = [KBLangTestVC new]; + [self.navigationController pushViewController:vc animated:YES]; + } +} + /* #pragma mark - Navigation diff --git a/keyBoard/Class/Home/VC/KBLangTestVC.h b/keyBoard/Class/Home/VC/KBLangTestVC.h new file mode 100644 index 0000000..810ee13 --- /dev/null +++ b/keyBoard/Class/Home/VC/KBLangTestVC.h @@ -0,0 +1,14 @@ +// +// KBLangTestVC.h +// 多语言测试页:点击按钮在中英文之间切换,并刷新文案。 +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface KBLangTestVC : UIViewController +@end + +NS_ASSUME_NONNULL_END + diff --git a/keyBoard/Class/Home/VC/KBLangTestVC.m b/keyBoard/Class/Home/VC/KBLangTestVC.m new file mode 100644 index 0000000..b743b56 --- /dev/null +++ b/keyBoard/Class/Home/VC/KBLangTestVC.m @@ -0,0 +1,61 @@ +// +// KBLangTestVC.m +// 多语言测试页:点击切换语言(在 zh-Hans 与 en 之间),演示运行时生效。 +// + +#import "KBLangTestVC.h" + +@interface KBLangTestVC () +@property (nonatomic, strong) UILabel *label; +@property (nonatomic, strong) UIButton *toggleBtn; +@end + +@implementation KBLangTestVC + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = UIColor.whiteColor; + [self buildUI]; + [self refreshTexts]; + + // 监听语言变更,实时刷新 + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshTexts) name:KBLocalizationDidChangeNotification object:nil]; +} + +- (void)dealloc { + [[NSNotificationCenter defaultCenter] removeObserver:self]; +} + +- (void)buildUI { + CGFloat w = UIScreen.mainScreen.bounds.size.width; + + self.label = [[UILabel alloc] initWithFrame:CGRectMake(24, 120, w - 48, 60)]; + self.label.textAlignment = NSTextAlignmentCenter; + self.label.numberOfLines = 0; + [self.view addSubview:self.label]; + + self.toggleBtn = [UIButton buttonWithType:UIButtonTypeSystem]; + self.toggleBtn.frame = CGRectMake(40, CGRectGetMaxY(self.label.frame) + 24, w - 80, 48); + self.toggleBtn.layer.cornerRadius = 8; + self.toggleBtn.backgroundColor = [UIColor colorWithRed:0.22 green:0.49 blue:0.96 alpha:1]; + [self.toggleBtn setTitleColor:UIColor.whiteColor forState:UIControlStateNormal]; + [self.toggleBtn addTarget:self action:@selector(onToggle) forControlEvents:UIControlEventTouchUpInside]; + [self.view addSubview:self.toggleBtn]; +} + +- (void)refreshTexts { + self.title = KBLocalized(@"lang_test_title"); + NSString *code = [KBLocalizationManager shared].currentLanguageCode ?: @""; + NSString *fmt = KBLocalized(@"current_lang"); + self.label.text = [NSString stringWithFormat:fmt.length ? fmt : @"当前:%@", code]; + [self.toggleBtn setTitle:KBLocalized(@"lang_toggle") forState:UIControlStateNormal]; +} + +- (void)onToggle { + KBLocalizationManager *mgr = [KBLocalizationManager shared]; + NSString *next = [mgr.currentLanguageCode.lowercaseString hasPrefix:@"zh"] ? @"en" : @"zh-Hans"; + [mgr setCurrentLanguageCode:next persist:YES]; +} + +@end + diff --git a/keyBoard/KeyBoardPrefixHeader.pch b/keyBoard/KeyBoardPrefixHeader.pch index ece6649..b8caeb7 100644 --- a/keyBoard/KeyBoardPrefixHeader.pch +++ b/keyBoard/KeyBoardPrefixHeader.pch @@ -29,6 +29,7 @@ #import "BaseNavigationController.h" #import "BaseTableView.h" #import "BaseCell.h" +#import "KBLocalizationManager.h" // 全局多语言封装 //-----------------------------------------------宏定义全局----------------------------------------------------------/ diff --git a/keyBoard/VC/KBPermissionViewController.m b/keyBoard/VC/KBPermissionViewController.m index 83692c8..2868970 100644 --- a/keyBoard/VC/KBPermissionViewController.m +++ b/keyBoard/VC/KBPermissionViewController.m @@ -28,7 +28,7 @@ [self.view addSubview:close]; UILabel *title = [[UILabel alloc] init]; - title.text = @"启用输入法"; + title.text = [[KBLocalizationManager shared] localizedStringForKey:@"perm_title_enable" table:nil value:@"启用输入法"]; title.font = [UIFont systemFontOfSize:22 weight:UIFontWeightSemibold]; title.textColor = [UIColor blackColor]; title.textAlignment = NSTextAlignmentCenter; @@ -37,7 +37,8 @@ [self.view addSubview:title]; UILabel *tips = [[UILabel alloc] init]; - tips.text = @"1 开启键盘 > 2 允许完全访问"; + // 保留简体中文为默认值;正式多语言请在 Localizable.strings 中提供 + tips.text = [[KBLocalizationManager shared] localizedStringForKey:@"perm_steps" table:nil value:@"1 开启键盘 > 2 允许完全访问"]; tips.font = [UIFont systemFontOfSize:14]; tips.textColor = [UIColor darkGrayColor]; tips.textAlignment = NSTextAlignmentCenter; @@ -54,7 +55,7 @@ [self.view addSubview:card]; UIButton *open = [UIButton buttonWithType:UIButtonTypeSystem]; - [open setTitle:@"去设置中开启" forState:UIControlStateNormal]; + [open setTitle:[[KBLocalizationManager shared] localizedStringForKey:@"perm_open_settings" table:nil value:@"去设置中开启"] forState:UIControlStateNormal]; open.titleLabel.font = [UIFont systemFontOfSize:17 weight:UIFontWeightSemibold]; [open setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal]; open.backgroundColor = [UIColor colorWithRed:0.22 green:0.49 blue:0.96 alpha:1.0]; @@ -66,7 +67,7 @@ [self.view addSubview:open]; UILabel *help = [[UILabel alloc] init]; - help.text = @"没有找到键盘? 请前往 设置 > 通用 > 键盘 > 键盘 > 添加新键盘"; + help.text = [[KBLocalizationManager shared] localizedStringForKey:@"perm_help" table:nil value:@"没有找到键盘? 请前往 设置 > 通用 > 键盘 > 键盘 > 添加新键盘"]; help.font = [UIFont systemFontOfSize:12]; help.textColor = [UIColor grayColor]; help.textAlignment = NSTextAlignmentCenter;