This commit is contained in:
2026-03-02 20:20:28 +08:00
parent a68fb9657f
commit fb6db0649c
11 changed files with 143 additions and 29 deletions

View File

@@ -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"},

View File

@@ -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

View File

@@ -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<NSString *, NSString *> *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<NSString *, NSString *> *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

View File

@@ -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

View File

@@ -387,6 +387,20 @@ static void KBSkinDarwinCallback(CFNotificationCenterRef center, void *observer,
}
}
}
// ID
if (value.length == 0 && identifier.length > 0) {
static NSMutableSet<NSString *> *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) {

View File

@@ -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",