diff --git a/CustomKeyboard/KeyboardViewController.m b/CustomKeyboard/KeyboardViewController.m index 9aae850..353a9ae 100644 --- a/CustomKeyboard/KeyboardViewController.m +++ b/CustomKeyboard/KeyboardViewController.m @@ -17,6 +17,7 @@ #import "KBKeyboardSubscriptionView.h" #import "KBLocalizationManager.h" #import "KBSkinManager.h" +#import "KBSkinInstallBridge.h" #import "KBSuggestionEngine.h" #import "KBKeyboardLayoutResolver.h" #import @@ -254,10 +255,9 @@ static NSString *KBFormatMB(uint64_t bytes) { - (void)kb_checkAndApplyLayoutIfNeeded { NSString *currentProfileId = [[KBKeyboardLayoutResolver sharedResolver] currentProfileId]; if (currentProfileId.length == 0) { - currentProfileId = @"en_US_qwerty"; // 默认回退 + currentProfileId = @"en_US_qwerty"; } - // 如果 profileId 没有变化,不需要重新加载 if ([currentProfileId isEqualToString:_kb_lastLoadedProfileId]) { return; } @@ -265,16 +265,20 @@ static NSString *KBFormatMB(uint64_t bytes) { NSLog(@"[KeyboardViewController] Detected profileId change: %@ -> %@", _kb_lastLoadedProfileId, currentProfileId); _kb_lastLoadedProfileId = currentProfileId; - // 通知 KBKeyBoardMainView 切换布局 if (self.keyBoardMainView && [self.keyBoardMainView respondsToSelector:@selector(reloadLayoutWithProfileId:)]) { [self.keyBoardMainView performSelector:@selector(reloadLayoutWithProfileId:) withObject:currentProfileId]; } - // 更新联想引擎类型 NSString *suggestionEngine = [[KBKeyboardLayoutResolver sharedResolver] suggestionEngineForProfileId:currentProfileId]; if (suggestionEngine.length > 0) { [self kb_updateSuggestionEngineType:suggestionEngine]; } + + NSString *languageCode = [[KBKeyboardLayoutResolver sharedResolver] currentLanguageCode]; + if (languageCode.length > 0) { + NSLog(@"[KeyboardViewController] Reloading skin icon map for language: %@", languageCode); + [KBSkinInstallBridge reloadCurrentSkinIconMapForLanguageCode:languageCode]; + } } - (void)kb_updateSuggestionEngineType:(NSString *)engineType { diff --git a/CustomKeyboard/Resource/KBSkinIconMap_es.strings b/CustomKeyboard/Resource/KBSkinIconMap_es.strings index 1335d15..368f6ba 100644 --- a/CustomKeyboard/Resource/KBSkinIconMap_es.strings +++ b/CustomKeyboard/Resource/KBSkinIconMap_es.strings @@ -97,9 +97,11 @@ "letter_l_upper" = "key_l_up"; /* 字母 ñ(小写)- 西班牙语专用 */ -"letter_ñ_lower" = "key_n_tilde"; +"letter_ñ_lower" = "key_ñ"; /* 字母 Ñ(大写)- 西班牙语专用 */ -"letter_ñ_upper" = "key_n_tilde_up"; +"letter_ñ_upper" = "key_ñ_up"; +/* 字母 ñ(基础映射) */ +"letter_ñ" = "key_ñ"; /* 字母 z(小写) */ "letter_z_lower" = "key_z"; diff --git a/Shared/KBInputProfileManager.m b/Shared/KBInputProfileManager.m index ec88edf..91fda4a 100644 --- a/Shared/KBInputProfileManager.m +++ b/Shared/KBInputProfileManager.m @@ -118,7 +118,7 @@ @{ @"code": @"es", @"name": @"Español", - @"defaultSkinZip": @"", + @"defaultSkinZip": @"西班牙初始皮肤.zip", @"layouts": @[ @{@"variant": @"qwerty", @"title": @"QWERTY", @"profileId": @"es_ES_qwerty", @"layoutJsonId": @"letters", @"suggestionEngine": @"latin"}, @{@"variant": @"azerty", @"title": @"AZERTY", @"profileId": @"es_ES_azerty", @"layoutJsonId": @"letters_azerty", @"suggestionEngine": @"latin"}, diff --git a/Shared/KBSkinInstallBridge.h b/Shared/KBSkinInstallBridge.h index bb7ee63..7f0de32 100644 --- a/Shared/KBSkinInstallBridge.h +++ b/Shared/KBSkinInstallBridge.h @@ -89,6 +89,11 @@ typedef void (^KBSkinInstallConsumeCompletion)(BOOL success, NSError * _Nullable + (BOOL)applyInstalledSkinWithId:(NSString *)skinId error:(NSError * _Nullable __autoreleasing *)error; +/// 重新应用当前皮肤的图标映射(用于语言切换时更新图标)。 +/// @param languageCode 新的语言代码 +/// @return 是否成功重新应用 ++ (BOOL)reloadCurrentSkinIconMapForLanguageCode:(NSString *)languageCode; + @end NS_ASSUME_NONNULL_END diff --git a/Shared/KBSkinInstallBridge.m b/Shared/KBSkinInstallBridge.m index 8b5fb06..4a300d0 100644 --- a/Shared/KBSkinInstallBridge.m +++ b/Shared/KBSkinInstallBridge.m @@ -947,4 +947,88 @@ static NSString * const kKBSkinMetadataThemeKey = @"theme_json"; return applyOK; } ++ (BOOL)reloadCurrentSkinIconMapForLanguageCode:(NSString *)languageCode { + if (languageCode.length == 0) { + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: empty language code"); + return NO; + } + + NSString *targetSkinId = [NSString stringWithFormat:@"bundle_skin_default_%@", languageCode]; + NSString *currentSkinId = [KBSkinManager shared].current.skinId ?: @""; + + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: currentSkin=%@ targetSkin=%@ lang=%@", + currentSkinId, targetSkinId, languageCode); + + if ([targetSkinId isEqualToString:currentSkinId]) { + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: already on target skin, just refresh icon map"); + } else { + BOOL hasTargetSkin = [KBSkinManager kb_hasAssetsForSkinId:targetSkinId]; + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: hasTargetSkin=%d", hasTargetSkin); + + if (hasTargetSkin) { + NSError *applyError = nil; + BOOL applied = [self applyInstalledSkinWithId:targetSkinId error:&applyError]; + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: switched to %@ applied=%d error=%@", + targetSkinId, applied, applyError); + return applied; + } + } + + if (currentSkinId.length == 0 || [currentSkinId isEqualToString:@"default"]) { + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: no custom skin applied, skip"); + return NO; + } + + NSDictionary *iconShortNames = [self iconShortNamesForLanguageCode:languageCode]; + if (iconShortNames.count == 0) { + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: no icon mapping found for language: %@", languageCode); + return NO; + } + + NSFileManager *fm = [NSFileManager defaultManager]; + NSString *skinRoot = [[self kb_skinsRootPath] stringByAppendingPathComponent:currentSkinId]; + NSString *iconsDir = [skinRoot stringByAppendingPathComponent:@"icons"]; + + NSMutableDictionary *iconPathMap = [NSMutableDictionary dictionary]; + [iconShortNames enumerateKeysAndObjectsUsingBlock:^(NSString *identifier, NSString *shortName, BOOL *stop) { + if (![shortName isKindOfClass:NSString.class] || shortName.length == 0) return; + NSString *fileName = shortName; + if (fileName.pathExtension.length == 0) { + fileName = [fileName stringByAppendingPathExtension:@"png"]; + } + NSString *fullPath = [iconsDir stringByAppendingPathComponent:fileName]; + if ([fm fileExistsAtPath:fullPath]) { + NSString *relative = [NSString stringWithFormat:@"Skins/%@/icons/%@", currentSkinId, fileName]; + iconPathMap[identifier] = relative; + } + }]; + + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: skin=%@ lang=%@ iconCount=%lu", + currentSkinId, languageCode, (unsigned long)iconPathMap.count); + + if (iconPathMap.count == 0) { + return NO; + } + + NSDictionary *meta = [self kb_metadataForSkinId:currentSkinId]; + NSMutableDictionary *themeJSON = [NSMutableDictionary dictionary]; + themeJSON[@"id"] = currentSkinId; + NSString *name = [meta[kKBSkinMetadataNameKey] isKindOfClass:NSString.class] ? meta[kKBSkinMetadataNameKey] : currentSkinId; + themeJSON[@"name"] = name; + if (iconPathMap.count > 0) { + themeJSON[@"key_icons"] = iconPathMap.copy; + } + + NSString *bgPath = [skinRoot stringByAppendingPathComponent:@"background.png"]; + NSData *bgData = [NSData dataWithContentsOfFile:bgPath]; + + BOOL themeOK = [[KBSkinManager shared] applyThemeFromJSON:themeJSON]; + if (bgData.length > 0) { + [[KBSkinManager shared] applyImageSkinWithData:bgData skinId:currentSkinId name:name]; + } + + NSLog(@"[SkinBridge] reloadCurrentSkinIconMap: applied themeOK=%d", themeOK); + return themeOK; +} + @end diff --git a/Shared/KBSkinManager.h b/Shared/KBSkinManager.h index 82f9931..a8fd15f 100644 --- a/Shared/KBSkinManager.h +++ b/Shared/KBSkinManager.h @@ -77,6 +77,9 @@ extern NSString * const KBDarwinSkinChanged; // cross-process /// Parse a hex color string like "#RRGGBB"/"#RRGGBBAA" + (UIColor *)colorFromHexString:(NSString *)hex defaultColor:(UIColor *)fallback; +/// 判断指定 skinId 是否有可用资源目录 ++ (BOOL)kb_hasAssetsForSkinId:(NSString *)skinId; + @end NS_ASSUME_NONNULL_END diff --git a/Shared/KBSkinManager.m b/Shared/KBSkinManager.m index d21a9d3..62ec0e7 100644 --- a/Shared/KBSkinManager.m +++ b/Shared/KBSkinManager.m @@ -387,6 +387,20 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer, } } } + + // 调试日志:打印未找到映射的按键 ID + if (value.length == 0 && identifier.length > 0) { + static NSMutableSet *kb_loggedMissingIds = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + kb_loggedMissingIds = [NSMutableSet set]; + }); + if (![kb_loggedMissingIds containsObject:identifier]) { + [kb_loggedMissingIds addObject:identifier]; + NSLog(@"[SkinManager] ⚠️ Missing icon mapping: id='%@' skin='%@' mapCount=%lu", + identifier, self.current.skinId ?: @"default", (unsigned long)map.count); + } + } // 若在 keyIconMap 中找到了 value,按约定加载 if (value.length > 0) { diff --git a/Shared/Resource/kb_input_profiles.json b/Shared/Resource/kb_input_profiles.json index a5f4b66..0bb4aa0 100644 --- a/Shared/Resource/kb_input_profiles.json +++ b/Shared/Resource/kb_input_profiles.json @@ -13,22 +13,21 @@ "layoutJsonId": "letters", "suggestionEngine": "latin" } - ] - }, - { - "code": "es", - "name": "Español (Latinoamérica)", - "defaultSkinZip": "", - "layouts": [ - { - "variant": "qwerty", - "title": "QWERTY", - "profileId": "es_419_qwerty", - "layoutJsonId": "letters_es", - "suggestionEngine": "spanish" - } - ] - }, + ], + { + "code": "es", + "name": "Español (Latinoamérica)", + "defaultSkinZip": "西班牙初始皮肤.zip", + "layouts": [ + { + "variant": "qwerty", + "title": "QWERTY", + "profileId": "es_419_qwerty", + "layoutJsonId": "letters_es", + "suggestionEngine": "spanish" + } + ] + }, { "code": "pt", "name": "Português", diff --git a/keyBoard.xcodeproj/project.pbxproj b/keyBoard.xcodeproj/project.pbxproj index 8f98b97..b5d12c7 100644 --- a/keyBoard.xcodeproj/project.pbxproj +++ b/keyBoard.xcodeproj/project.pbxproj @@ -44,8 +44,8 @@ 043213B02F556DF80065C888 /* KBSkinIconMap_id.strings in Resources */ = {isa = PBXBuildFile; fileRef = 043213AC2F556DF80065C888 /* KBSkinIconMap_id.strings */; }; 043213B12F556DF80065C888 /* KBSkinIconMap_es.strings in Resources */ = {isa = PBXBuildFile; fileRef = 043213AB2F556DF80065C888 /* KBSkinIconMap_es.strings */; }; 043213B22F556DF80065C888 /* KBSkinIconMap_zh_hant.strings in Resources */ = {isa = PBXBuildFile; fileRef = 043213AE2F556DF80065C888 /* KBSkinIconMap_zh_hant.strings */; }; - 043213B42F557CC50065C888 /* 西班牙初始皮肤.zip in Resources */ = {isa = PBXBuildFile; fileRef = 043213B32F557CC50065C888 /* 西班牙初始皮肤.zip */; }; 043213B62F5582710065C888 /* spanish_words.json in Resources */ = {isa = PBXBuildFile; fileRef = 043213B52F5582710065C888 /* spanish_words.json */; }; + 043213B82F558BC20065C888 /* 西班牙初始皮肤.zip in Resources */ = {isa = PBXBuildFile; fileRef = 043213B72F558BC20065C888 /* 西班牙初始皮肤.zip */; }; 043FBCD22EAF97630036AFE1 /* KBPermissionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 04C6EAE12EAF940F0089C901 /* KBPermissionViewController.m */; }; 0450AA742EF013D000B6AF06 /* KBEmojiCollectionCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */; }; 0450AAE22EF03D5100B6AF06 /* KBPerson.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0450AAE12EF03D5100B6AF06 /* KBPerson.swift */; }; @@ -402,8 +402,8 @@ 043213AC2F556DF80065C888 /* KBSkinIconMap_id.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap_id.strings; sourceTree = ""; }; 043213AD2F556DF80065C888 /* KBSkinIconMap_pt.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap_pt.strings; sourceTree = ""; }; 043213AE2F556DF80065C888 /* KBSkinIconMap_zh_hant.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = KBSkinIconMap_zh_hant.strings; sourceTree = ""; }; - 043213B32F557CC50065C888 /* 西班牙初始皮肤.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "西班牙初始皮肤.zip"; sourceTree = ""; }; 043213B52F5582710065C888 /* spanish_words.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = spanish_words.json; sourceTree = ""; }; + 043213B72F558BC20065C888 /* 西班牙初始皮肤.zip */ = {isa = PBXFileReference; lastKnownFileType = archive.zip; path = "西班牙初始皮肤.zip"; sourceTree = ""; }; 0450AA722EF013D000B6AF06 /* KBEmojiCollectionCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBEmojiCollectionCell.h; sourceTree = ""; }; 0450AA732EF013D000B6AF06 /* KBEmojiCollectionCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBEmojiCollectionCell.m; sourceTree = ""; }; 0450AAE02EF03D5100B6AF06 /* keyBoard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "keyBoard-Bridging-Header.h"; sourceTree = ""; }; @@ -1298,7 +1298,7 @@ 047C652C2EBCAAAC0035E841 /* Resource */ = { isa = PBXGroup; children = ( - 043213B32F557CC50065C888 /* 西班牙初始皮肤.zip */, + 043213B72F558BC20065C888 /* 西班牙初始皮肤.zip */, 0479200A2ED87CEE004E8522 /* permiss_video.mp4 */, 047920102ED98E7D004E8522 /* permiss_video_2.mp4 */, 047920062ED86ABC004E8522 /* kb_guide_keyboard.gif */, @@ -1613,11 +1613,11 @@ 04E2277C2F516EBD001A8F14 /* PrivacyInfo.xcprivacy */, 04FC95F52EB33B52007BD342 /* keyBoard.entitlements */, 04FC95BF2EB1E3B1007BD342 /* Class */, - 04C6EAE32EAF942E0089C901 /* VC */, 04C6EAAC2EAF86530089C901 /* AppDelegate.h */, 04C6EAAD2EAF86530089C901 /* AppDelegate.m */, 04C6EAAE2EAF86530089C901 /* Assets.xcassets */, 04C6EAAF2EAF86530089C901 /* Info.plist */, + 04C6EAE32EAF942E0089C901 /* VC */, 04C6EAB12EAF86530089C901 /* LaunchScreen.storyboard */, 04C6EAB22EAF86530089C901 /* main.m */, 04C6EAB42EAF86530089C901 /* Main.storyboard */, @@ -2336,7 +2336,7 @@ 04C6EABC2EAF86530089C901 /* LaunchScreen.storyboard in Resources */, 04E0394F2F236E75002CA5A0 /* KBChatTableView_Usage.md in Resources */, 04286A132ECDEBF900CE730C /* KBSkinIconMap.strings in Resources */, - 043213B42F557CC50065C888 /* 西班牙初始皮肤.zip in Resources */, + 043213B82F558BC20065C888 /* 西班牙初始皮肤.zip in Resources */, 04C6EABD2EAF86530089C901 /* Main.storyboard in Resources */, 046086CB2F1A092500757C95 /* comments_mock.json in Resources */, 04E038E32F20E500002CA5A0 /* deepgramAPI.md in Resources */, diff --git a/keyBoard/Class/Me/VC/KBPersonInfoVC.m b/keyBoard/Class/Me/VC/KBPersonInfoVC.m index 6db631b..9c50d41 100644 --- a/keyBoard/Class/Me/VC/KBPersonInfoVC.m +++ b/keyBoard/Class/Me/VC/KBPersonInfoVC.m @@ -647,10 +647,13 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay } NSString *skinId = [NSString stringWithFormat:@"bundle_skin_default_%@", languageCode]; + NSDictionary *iconShortNames = [KBSkinInstallBridge iconShortNamesForLanguageCode:languageCode]; + NSLog(@"[KBPersonInfoVC] Installing skin %@ with %lu icon mappings for language %@", skinId, (unsigned long)iconShortNames.count, languageCode); + [KBSkinInstallBridge publishBundleSkinRequestWithId:skinId name:skinId zipName:zipName - iconShortNames:nil]; + iconShortNames:iconShortNames]; [KBSkinInstallBridge consumePendingRequestFromBundle:NSBundle.mainBundle completion:^(BOOL success, NSError * _Nullable error) { if (!success && error) { diff --git a/keyBoard/Class/Resource/西班牙初始皮肤.zip b/keyBoard/Class/Resource/西班牙初始皮肤.zip index dc701e8..8a841f5 100644 Binary files a/keyBoard/Class/Resource/西班牙初始皮肤.zip and b/keyBoard/Class/Resource/西班牙初始皮肤.zip differ