语言逻辑处理
我的项目里有5个国家的语言,如果用户在app里手动切换了国家语言,只要不卸载app,用户在手机设置切换语言,app的语言不要变;如果app被删 除,重新安装,app语言要跟随手机设置的语言(如果语言对不上,app就显示英语) 用户没有在app里手动设置过国家语言,用户在手机设置界面切换国家,app要跟随手机设置的语言(如果语言对不上,app就显示英语)。
This commit is contained in:
@@ -38,6 +38,11 @@
|
||||
#define AppGroup_SelectedKeyboardLanguageCode @"AppGroup_SelectedKeyboardLanguageCode"
|
||||
#define AppGroup_SelectedKeyboardLayoutVariant @"AppGroup_SelectedKeyboardLayoutVariant"
|
||||
|
||||
/// 是否用户手动选择过键盘输入配置(语言/布局/profile)。
|
||||
/// YES:主 App 不再自动把键盘配置跟随 App 语言变更(避免覆盖用户手选)。
|
||||
/// NO :主 App 会在“App 语言变化且未手动选择键盘配置”时同步更新键盘配置,使键盘语言/布局与 App 一致。
|
||||
#define AppGroup_DidUserSelectKeyboardProfile @"AppGroup_DidUserSelectKeyboardProfile"
|
||||
|
||||
/// Darwin 跨进程通知:键盘扩展发送聊天消息后通知主 App 刷新
|
||||
#define kKBDarwinChatUpdated @"com.loveKey.nyx.chat.updated"
|
||||
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#import <Security/Security.h>
|
||||
#import "KBConfig.h"
|
||||
|
||||
// Darwin 跨进程通知:语言变更(App <-> 扩展)
|
||||
static NSString * const kKBDarwinLocalizationChanged = @"com.loveKey.nyx.loc.changed";
|
||||
|
||||
/// 语言常量定义
|
||||
KBLanguageCode const KBLanguageCodeEnglish = @"en";
|
||||
KBLanguageCode const KBLanguageCodeTraditionalChinese = @"zh-Hant";
|
||||
@@ -37,9 +40,20 @@ NSString * const KBLocalizationDidChangeNotification = @"KBLocalizationDidChange
|
||||
static NSString * const kKBLocService = @"com.loveKey.nyx.loc";
|
||||
static NSString * const kKBLocAccount = @"lang"; // 保存 UTF8 的语言代码
|
||||
|
||||
// 首次安装/首次启动标记(仅主 App 侧使用标准 UserDefaults;卸载后会被清除,用于抵消 Keychain 在卸载重装后仍保留的问题)
|
||||
static NSString * const kKBLocDidLaunchKey = @"com.loveKey.nyx.loc.did_launch";
|
||||
// 主 App 启动标记(存 App Group,键盘扩展可读):用于“扩展先启动”场景下避免读取卸载重装遗留的 Keychain 语言。
|
||||
static NSString * const kKBLocAppBootstrappedKey = @"com.loveKey.nyx.loc.app_bootstrapped";
|
||||
// 用户是否在 App 内手动选择过语言(存 App Group;卸载重装会清理,满足“重装后跟随系统”)
|
||||
static NSString * const kKBLocUserSelectedLanguageKey = @"com.loveKey.nyx.loc.user_selected_language";
|
||||
|
||||
@interface KBLocalizationManager ()
|
||||
@property (nonatomic, copy, readwrite) KBLanguageCode currentLanguageCode; // 当前语言代码
|
||||
@property (nonatomic, strong) NSBundle *langBundle; // 对应语言的 .lproj 资源包
|
||||
|
||||
- (NSString *)normalizeLanguageCode:(NSString *)code;
|
||||
- (NSString *)supportedLanguageCodeForCandidate:(nullable NSString *)code;
|
||||
- (void)kb_onSystemLocaleMaybeChanged:(NSNotification *)note;
|
||||
@end
|
||||
|
||||
// 避免 +shared 初始化阶段递归触发自身:
|
||||
@@ -54,19 +68,141 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
return q;
|
||||
}
|
||||
|
||||
static void KBLocDarwinCallback(CFNotificationCenterRef center,
|
||||
void *observer,
|
||||
CFStringRef name,
|
||||
const void *object,
|
||||
CFDictionaryRef userInfo) {
|
||||
// 语言更新后,扩展侧需要主动 reload;App 侧收到也无害(会被 resolved==current short-circuit)。
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
KBLocalizationManager *m = [KBLocalizationManager shared];
|
||||
[m reloadFromSharedStorageIfNeeded];
|
||||
});
|
||||
}
|
||||
|
||||
@implementation KBLocalizationManager
|
||||
|
||||
/// 是否运行在 App Extension(如键盘扩展)里。
|
||||
/// 说明:扩展与主 App 共享 Keychain,但不应参与“首次启动”判定,避免扩展抢先写入标记导致主 App 不再清理历史 Keychain。
|
||||
+ (BOOL)kb_isRunningInAppExtension {
|
||||
return ([[NSBundle mainBundle] objectForInfoDictionaryKey:@"NSExtension"] != nil);
|
||||
}
|
||||
|
||||
+ (NSUserDefaults *)kb_appGroupUserDefaults {
|
||||
NSUserDefaults *ud = [[NSUserDefaults alloc] initWithSuiteName:AppGroup];
|
||||
return ud ?: [NSUserDefaults standardUserDefaults];
|
||||
}
|
||||
|
||||
+ (BOOL)kb_isAppBootstrapped {
|
||||
// App Group 在卸载重装后会被清理;扩展通过该标记判断“主 App 是否已在本次安装中启动过”。
|
||||
return [[self kb_appGroupUserDefaults] boolForKey:kKBLocAppBootstrappedKey];
|
||||
}
|
||||
|
||||
+ (void)kb_markAppBootstrappedIfNeeded {
|
||||
NSUserDefaults *ud = [self kb_appGroupUserDefaults];
|
||||
if ([ud boolForKey:kKBLocAppBootstrappedKey]) { return; }
|
||||
[ud setBool:YES forKey:kKBLocAppBootstrappedKey];
|
||||
[ud synchronize];
|
||||
}
|
||||
|
||||
+ (BOOL)kb_userDidSelectLanguage {
|
||||
NSUserDefaults *ud = [self kb_appGroupUserDefaults];
|
||||
return [ud boolForKey:kKBLocUserSelectedLanguageKey];
|
||||
}
|
||||
|
||||
+ (void)kb_setUserDidSelectLanguage:(BOOL)selected {
|
||||
NSUserDefaults *ud = [self kb_appGroupUserDefaults];
|
||||
[ud setBool:selected forKey:kKBLocUserSelectedLanguageKey];
|
||||
[ud synchronize];
|
||||
}
|
||||
|
||||
+ (instancetype)shared {
|
||||
static KBLocalizationManager *m; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{
|
||||
m = [KBLocalizationManager new];
|
||||
// 默认支持语言;可在启动时由外部重置
|
||||
m.supportedLanguageCodes = KBDefaultSupportedLanguageCodes();
|
||||
// 启动读取:先取共享钥匙串,再按系统偏好回退
|
||||
NSString *saved = [[self class] kc_read];
|
||||
if (saved.length == 0) {
|
||||
saved = [m bestSupportedLanguageForPreferred:[NSLocale preferredLanguages]] ?: KBLanguageCodeEnglish;
|
||||
|
||||
BOOL isAppExtension = [[self class] kb_isRunningInAppExtension];
|
||||
BOOL wasAppBootstrapped = [[self class] kb_isAppBootstrapped];
|
||||
BOOL isAppBootstrapped = wasAppBootstrapped;
|
||||
if (!isAppExtension) {
|
||||
// 仅主 App 写入该标记;扩展只读取
|
||||
[[self class] kb_markAppBootstrappedIfNeeded];
|
||||
isAppBootstrapped = YES;
|
||||
}
|
||||
[m applyLanguage:saved];
|
||||
// 新安装判定(优先使用 App Group 标记):卸载重装后 App Group 会被清理,但 Keychain 可能仍残留。
|
||||
// 仅主 App 在检测到“本次安装从未启动过”时清理 Keychain,并写入系统择优结果,供扩展读取。
|
||||
BOOL isFreshInstall = (!isAppExtension && !wasAppBootstrapped);
|
||||
if (isFreshInstall) {
|
||||
[[self class] kc_write:nil];
|
||||
// 新安装:确保“未手动选择”状态(App Group 卸载重装会清理,这里再兜底一次)
|
||||
[[self class] kb_setUserDidSelectLanguage:NO];
|
||||
}
|
||||
|
||||
// 兼容旧逻辑:仍保留标准 UserDefaults 的首次启动标记(仅主 App),但不再作为关键判定条件。
|
||||
BOOL isFirstLaunch = (!isAppExtension) ? [[self class] kb_markLaunchedAndReturnIsFirstLaunch] : NO;
|
||||
|
||||
// 语言选择策略(满足产品规则):
|
||||
// 1) 用户在 App 内手动选过语言 -> 锁定(不跟随系统);通过 Keychain 跨 Target 同步
|
||||
// 2) 用户未手动选择过 -> 跟随系统当前语言(首选列表第一个),不读取/不写入 Keychain
|
||||
BOOL userSelected = [[self class] kb_userDidSelectLanguage];
|
||||
NSString *saved = nil;
|
||||
if (userSelected) {
|
||||
// 扩展在“主 App 尚未启动过”的场景下,忽略 Keychain(防止卸载重装遗留旧值在扩展先启动时生效)
|
||||
if (!(isAppExtension && !isAppBootstrapped)) {
|
||||
saved = (isFreshInstall ? nil : [[self class] kc_read]);
|
||||
}
|
||||
} else {
|
||||
// 未手动选择:清理可能遗留的 Keychain(例如旧版本曾写入“系统默认”导致后续不再跟随系统)
|
||||
if (!isAppExtension) {
|
||||
NSString *legacy = [[self class] kc_read];
|
||||
if (legacy.length > 0) {
|
||||
[[self class] kc_write:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
#if DEBUG
|
||||
NSLog(@"[KBLoc] init bundle=%@ isExt=%d appBoot=%d wasBoot=%d fresh=%d firstLaunch=%d userSelected=%d preferred=%@ keychainSaved=%@ supported=%@",
|
||||
NSBundle.mainBundle.bundleIdentifier ?: @"",
|
||||
isAppExtension, isAppBootstrapped, wasAppBootstrapped, isFreshInstall, isFirstLaunch,
|
||||
userSelected,
|
||||
[NSLocale preferredLanguages],
|
||||
saved ?: @"(nil)",
|
||||
m.supportedLanguageCodes ?: @[]);
|
||||
#endif
|
||||
// 计算最终语言:优先手动选择;否则跟随系统当前语言(不在 5 种内则英文)
|
||||
NSString *resolved = [m supportedLanguageCodeForCandidate:saved];
|
||||
if (saved.length > 0 && resolved.length == 0) {
|
||||
// 若保存了不支持的语言码(历史遗留),视为“未手动选择”,清理并回退系统
|
||||
[[self class] kc_write:nil];
|
||||
[[self class] kb_setUserDidSelectLanguage:NO];
|
||||
saved = nil;
|
||||
resolved = @"";
|
||||
}
|
||||
if (resolved.length == 0) {
|
||||
resolved = [m bestSupportedLanguageForPreferred:[NSLocale preferredLanguages]] ?: KBLanguageCodeEnglish;
|
||||
}
|
||||
[m applyLanguage:resolved];
|
||||
#if DEBUG
|
||||
NSLog(@"[KBLoc] resolved=%@ current=%@ bundle=%@",
|
||||
resolved ?: @"",
|
||||
m.currentLanguageCode ?: @"",
|
||||
m.langBundle.bundlePath ?: @"");
|
||||
#endif
|
||||
|
||||
// 监听 Darwin 跨进程通知(语言变更),用于“App 内切语言 -> 立即刷新键盘”。
|
||||
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(),
|
||||
(__bridge const void *)(m),
|
||||
KBLocDarwinCallback,
|
||||
(__bridge CFStringRef)kKBDarwinLocalizationChanged,
|
||||
NULL,
|
||||
CFNotificationSuspensionBehaviorDeliverImmediately);
|
||||
|
||||
// 系统语言/地区变化:如果未手动锁定语言,则跟随系统刷新
|
||||
[[NSNotificationCenter defaultCenter] addObserver:m
|
||||
selector:@selector(kb_onSystemLocaleMaybeChanged:)
|
||||
name:NSCurrentLocaleDidChangeNotification
|
||||
object:nil];
|
||||
});
|
||||
return m;
|
||||
}
|
||||
@@ -90,15 +226,37 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
|
||||
- (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:self.currentLanguageCode]; } // 需同步到 App/扩展
|
||||
// 仅允许切到“受支持语言”;不支持(如 zh-Hans)时按最佳匹配回退(默认 en)
|
||||
NSString *resolved = [self supportedLanguageCodeForCandidate:code];
|
||||
if (resolved.length == 0) {
|
||||
resolved = [self bestSupportedLanguageForPreferred:@[code]] ?: (self.supportedLanguageCodes.firstObject ?: KBLanguageCodeEnglish);
|
||||
}
|
||||
if ([resolved isEqualToString:self.currentLanguageCode]) return; // 无变更
|
||||
[self applyLanguage:resolved];
|
||||
if (persist) {
|
||||
[[self class] kb_setUserDidSelectLanguage:YES];
|
||||
[[self class] kc_write:self.currentLanguageCode]; // 需同步到 App/扩展
|
||||
// 通知对端进程刷新语言(特别是键盘扩展正在显示时)
|
||||
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),
|
||||
(__bridge CFStringRef)kKBDarwinLocalizationChanged,
|
||||
NULL, NULL, true);
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:KBLocalizationDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (void)resetToSystemLanguage {
|
||||
[[self class] kb_setUserDidSelectLanguage:NO];
|
||||
[[self class] kc_write:nil];
|
||||
NSString *best = [self bestSupportedLanguageForPreferred:[NSLocale preferredLanguages]] ?: KBLanguageCodeEnglish;
|
||||
[self setCurrentLanguageCode:best persist:NO];
|
||||
if ([best isEqualToString:self.currentLanguageCode]) {
|
||||
return;
|
||||
}
|
||||
[self applyLanguage:best];
|
||||
// 通知对端进程刷新语言(特别是键盘扩展正在显示时)
|
||||
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),
|
||||
(__bridge CFStringRef)kKBDarwinLocalizationChanged,
|
||||
NULL, NULL, true);
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:KBLocalizationDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
- (NSString *)localizedStringForKey:(NSString *)key {
|
||||
@@ -116,15 +274,25 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
|
||||
- (NSString *)bestSupportedLanguageForPreferred:(NSArray<NSString *> *)preferred {
|
||||
if (self.supportedLanguageCodes.count == 0) return KBLanguageCodeEnglish;
|
||||
// 需求:只使用“系统当前语言”(首选语言列表的第一个)。
|
||||
// 若当前语言不在项目支持的 5 种语言内,则回退英文;不要从后续备选语言里再挑一个支持语言。
|
||||
NSString *primary = preferred.firstObject ?: @"";
|
||||
if (primary.length == 0) {
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
if ([s.lowercaseString isEqualToString:@"en"]) { return s; }
|
||||
}
|
||||
return self.supportedLanguageCodes.firstObject ?: KBLanguageCodeEnglish;
|
||||
}
|
||||
NSArray<NSString *> *primaryOnly = @[ primary ];
|
||||
// 1) 完全匹配
|
||||
for (NSString *p in preferred) {
|
||||
for (NSString *p in primaryOnly) {
|
||||
NSString *pLC = p.lowercaseString;
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
if ([pLC isEqualToString:s.lowercaseString]) { return s; }
|
||||
}
|
||||
}
|
||||
// 2) 前缀匹配:如 en-GB -> en, es-MX -> es
|
||||
for (NSString *p in preferred) {
|
||||
for (NSString *p in primaryOnly) {
|
||||
NSString *pLC = p.lowercaseString;
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
NSString *sLC = s.lowercaseString;
|
||||
@@ -138,7 +306,7 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
}
|
||||
}
|
||||
// 2.5) 特殊处理葡语:pt-BR / pt-PT / pt -> pt-PT(若受支持)
|
||||
for (NSString *p in preferred) {
|
||||
for (NSString *p in primaryOnly) {
|
||||
NSString *pLC = p.lowercaseString;
|
||||
if ([pLC isEqualToString:@"pt"] || [pLC hasPrefix:@"pt-"] || [pLC hasPrefix:@"pt_"]) {
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
@@ -147,7 +315,7 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
}
|
||||
}
|
||||
// 3) 特殊处理中文:将 zh-Hant/zh-TW/zh-HK 映射到 zh-Hant(若受支持)
|
||||
for (NSString *p in preferred) {
|
||||
for (NSString *p in primaryOnly) {
|
||||
NSString *pLC = p.lowercaseString;
|
||||
if ([pLC hasPrefix:@"zh-hant"] || [pLC hasPrefix:@"zh-tw"] || [pLC hasPrefix:@"zh-hk"]) {
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
@@ -155,7 +323,10 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 4) 兜底:取第一个受支持语言
|
||||
// 4) 兜底:若英文受支持则固定回退英文,否则取第一个
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
if ([s.lowercaseString isEqualToString:@"en"]) { return s; }
|
||||
}
|
||||
return self.supportedLanguageCodes.firstObject ?: KBLanguageCodeEnglish;
|
||||
}
|
||||
|
||||
@@ -165,7 +336,8 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
|
||||
#pragma mark - 内部实现
|
||||
|
||||
- (void)applyLanguage:(NSString *)code {
|
||||
- (NSString *)normalizeLanguageCode:(NSString *)code {
|
||||
if (code.length == 0) return @"";
|
||||
NSString *normalizedCode = code;
|
||||
NSString *lower = normalizedCode.lowercaseString;
|
||||
if ([lower isEqualToString:@"pt"] || [lower hasPrefix:@"pt-"] || [lower hasPrefix:@"pt_"]) {
|
||||
@@ -175,6 +347,22 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
if ([lower hasPrefix:@"zh-hant"] || [lower hasPrefix:@"zh_hant"] || [lower hasPrefix:@"zh-tw"] || [lower hasPrefix:@"zh_hk"]) {
|
||||
normalizedCode = KBLanguageCodeTraditionalChinese;
|
||||
}
|
||||
return normalizedCode;
|
||||
}
|
||||
|
||||
- (NSString *)supportedLanguageCodeForCandidate:(NSString *)code {
|
||||
NSString *normalized = [self normalizeLanguageCode:code];
|
||||
if (normalized.length == 0) return @"";
|
||||
for (NSString *s in self.supportedLanguageCodes) {
|
||||
if ([s.lowercaseString isEqualToString:normalized.lowercaseString]) {
|
||||
return s; // 返回“工程内规范码”(保留大小写/连字符风格)
|
||||
}
|
||||
}
|
||||
return @"";
|
||||
}
|
||||
|
||||
- (void)applyLanguage:(NSString *)code {
|
||||
NSString *normalizedCode = [self normalizeLanguageCode:code];
|
||||
_currentLanguageCode = [normalizedCode copy];
|
||||
// 基于当前 Target(App 或扩展)的主 bundle 加载 .lproj 资源
|
||||
NSString *path = [NSBundle.mainBundle pathForResource:normalizedCode ofType:@"lproj"];
|
||||
@@ -194,6 +382,16 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
|
||||
#pragma mark - 钥匙串读写(App/扩展共享)
|
||||
|
||||
+ (BOOL)kb_markLaunchedAndReturnIsFirstLaunch {
|
||||
// 注意:这里只能用主 App 自己的标准 UserDefaults。
|
||||
// 不要用 App Group,否则键盘扩展可能会抢先写入“已启动”标记,导致主 App 无法在首次启动时清理历史 Keychain。
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
if ([ud boolForKey:kKBLocDidLaunchKey]) { return NO; }
|
||||
[ud setBool:YES forKey:kKBLocDidLaunchKey];
|
||||
[ud synchronize];
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)kc_write:(NSString *)lang {
|
||||
NSMutableDictionary *q = KBLocBaseKCQuery();
|
||||
SecItemDelete((__bridge CFDictionaryRef)q);
|
||||
@@ -217,14 +415,60 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
|
||||
return lang;
|
||||
}
|
||||
|
||||
- (void)reloadFromSharedStorageIfNeeded {
|
||||
NSString *saved = [[self class] kc_read];
|
||||
if (saved.length == 0) return;
|
||||
if ([saved isEqualToString:self.currentLanguageCode]) return;
|
||||
- (void)kb_onSystemLocaleMaybeChanged:(NSNotification *)note {
|
||||
// 只有未手动选择语言时才需要跟随系统变化;手动选择情况下 reload 会优先读取保存值,不会被系统变化影响。
|
||||
[self reloadFromSharedStorageIfNeeded];
|
||||
}
|
||||
|
||||
[self applyLanguage:saved];
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:KBLocalizationDidChangeNotification
|
||||
object:nil];
|
||||
- (void)reloadFromSharedStorageIfNeeded {
|
||||
BOOL userSelected = [[self class] kb_userDidSelectLanguage];
|
||||
BOOL isAppExtension = [[self class] kb_isRunningInAppExtension];
|
||||
BOOL isAppBootstrapped = [[self class] kb_isAppBootstrapped];
|
||||
|
||||
NSString *target = @"";
|
||||
if (userSelected) {
|
||||
// 扩展在主 App 未启动前:不读取 Keychain(可能遗留旧值),保持当前按系统择优的结果。
|
||||
if (isAppExtension && !isAppBootstrapped) {
|
||||
#if DEBUG
|
||||
NSLog(@"[KBLoc] reload skip (app not bootstrapped yet) bundle=%@ current=%@",
|
||||
NSBundle.mainBundle.bundleIdentifier ?: @"",
|
||||
self.currentLanguageCode ?: @"");
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
NSString *saved = [[self class] kc_read];
|
||||
if (saved.length == 0) {
|
||||
// 标记为手动选择但无保存值:降级为“跟随系统”
|
||||
[[self class] kb_setUserDidSelectLanguage:NO];
|
||||
} else {
|
||||
NSString *resolved = [self supportedLanguageCodeForCandidate:saved];
|
||||
if (resolved.length == 0) {
|
||||
// 保存了不支持的语言码(历史遗留):清理并降级为“跟随系统”
|
||||
[[self class] kc_write:nil];
|
||||
[[self class] kb_setUserDidSelectLanguage:NO];
|
||||
} else {
|
||||
target = resolved;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (target.length == 0) {
|
||||
// 未手动选择:始终按系统当前语言(首选列表第一个)择优
|
||||
target = [self bestSupportedLanguageForPreferred:[NSLocale preferredLanguages]] ?: KBLanguageCodeEnglish;
|
||||
}
|
||||
|
||||
if ([target isEqualToString:self.currentLanguageCode]) {
|
||||
return;
|
||||
}
|
||||
|
||||
[self applyLanguage:target];
|
||||
// 系统跟随模式:主 App 语言变化时需要通知键盘扩展即时刷新(避免键盘仍停留旧语言)。
|
||||
if (!userSelected && !isAppExtension) {
|
||||
CFNotificationCenterPostNotification(CFNotificationCenterGetDarwinNotifyCenter(),
|
||||
(__bridge CFStringRef)kKBDarwinLocalizationChanged,
|
||||
NULL, NULL, true);
|
||||
}
|
||||
[[NSNotificationCenter defaultCenter] postNotificationName:KBLocalizationDidChangeNotification object:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1021,6 +1021,18 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json";
|
||||
targetSkinId, themeOK, (unsigned long)iconPathMap.count);
|
||||
return themeOK;
|
||||
}
|
||||
|
||||
// 目标默认皮肤不存在:如果当前仍是“默认类皮肤”,为避免 key_icons 仍是旧语言导致看起来“还是繁体”,回退到无图标的默认主题。
|
||||
// 等主 App 后续安装/应用对应语言的默认皮肤后,会通过 KBSkinManager 的 Darwin 通知同步到扩展。
|
||||
if (!hasTargetSkin && isDefaultLike &&
|
||||
currentSkinId.length > 0 &&
|
||||
![currentSkinId isEqualToString:@"default"] &&
|
||||
[currentSkinId hasPrefix:@"bundle_skin_default_"] &&
|
||||
![currentSkinId isEqualToString:targetSkinId]) {
|
||||
NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: missing target skin, fallback to default theme (clear old icon map)");
|
||||
[[KBSkinManager shared] resetToDefault];
|
||||
return YES;
|
||||
}
|
||||
|
||||
// 如果目标皮肤不存在,尝试更新当前皮肤的图标映射
|
||||
if (currentSkinId.length == 0 || [currentSkinId isEqualToString:@"default"]) {
|
||||
|
||||
Reference in New Issue
Block a user