This commit is contained in:
2026-03-04 13:44:56 +08:00
parent f30b1d7640
commit b1f1ddec7e
9 changed files with 331 additions and 60 deletions

View File

@@ -8,8 +8,10 @@
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, KBSuggestionEngineType) {
KBSuggestionEngineTypeLatin = 0, // 拉丁字母(英语、葡萄牙语、印尼语
KBSuggestionEngineTypeLatin = 0, // 拉丁字母(英语)
KBSuggestionEngineTypeSpanish, // 西班牙语
KBSuggestionEngineTypePortuguese, // 葡萄牙语
KBSuggestionEngineTypeIndonesian, // 印度尼西亚语
KBSuggestionEngineTypePinyinSimplified, // 简体拼音
KBSuggestionEngineTypePinyinTraditional, // 繁体拼音
KBSuggestionEngineTypeBopomofo // 注音(繁体)

View File

@@ -15,6 +15,8 @@
@property (nonatomic, strong) NSDictionary<NSString *, NSArray<NSString *> *> *pinyinToTraditionalMap;
@property (nonatomic, strong) NSDictionary<NSString *, NSArray<NSString *> *> *bopomofoToChineseMap;
@property (nonatomic, copy) NSArray<NSString *> *spanishWords;
@property (nonatomic, copy) NSArray<NSString *> *portugueseWords;
@property (nonatomic, copy) NSArray<NSString *> *indonesianWords;
@end
@implementation KBSuggestionEngine
@@ -40,6 +42,8 @@
_pinyinToTraditionalMap = [self kb_loadPinyinToTraditionalMap];
_bopomofoToChineseMap = [self kb_loadBopomofoToChineseMap];
_spanishWords = [self kb_loadSpanishWords];
_portugueseWords = [self kb_loadPortugueseWords];
_indonesianWords = [self kb_loadIndonesianWords];
}
return self;
}
@@ -50,6 +54,10 @@
switch (self.engineType) {
case KBSuggestionEngineTypeSpanish:
return [self kb_spanishSuggestionsForPrefix:prefix limit:limit];
case KBSuggestionEngineTypePortuguese:
return [self kb_portugueseSuggestionsForPrefix:prefix limit:limit];
case KBSuggestionEngineTypeIndonesian:
return [self kb_indonesianSuggestionsForPrefix:prefix limit:limit];
case KBSuggestionEngineTypePinyinTraditional:
return [self kb_traditionalPinyinSuggestionsForPrefix:prefix limit:limit];
case KBSuggestionEngineTypePinyinSimplified:
@@ -163,6 +171,10 @@
self.engineType = KBSuggestionEngineTypeLatin;
} else if ([engineTypeString isEqualToString:@"spanish"]) {
self.engineType = KBSuggestionEngineTypeSpanish;
} else if ([engineTypeString isEqualToString:@"portuguese"]) {
self.engineType = KBSuggestionEngineTypePortuguese;
} else if ([engineTypeString isEqualToString:@"indonesian"]) {
self.engineType = KBSuggestionEngineTypeIndonesian;
} else if ([engineTypeString isEqualToString:@"pinyin_traditional"]) {
self.engineType = KBSuggestionEngineTypePinyinTraditional;
} else if ([engineTypeString isEqualToString:@"pinyin_simplified"]) {
@@ -539,35 +551,13 @@
#pragma mark - Spanish Suggestions
- (NSArray<NSString *> *)kb_spanishSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
NSString *lower = prefix.lowercaseString;
NSMutableArray<NSString *> *matches = [NSMutableArray array];
for (NSString *word in self.spanishWords) {
if ([word hasPrefix:lower]) {
[matches addObject:word];
if (matches.count >= limit * 2) {
break;
}
}
}
NSArray<NSString *> *matches = [self kb_suggestionsFromWordList:self.spanishWords
prefix:prefix
limit:limit];
if (matches.count == 0) {
return [self kb_latinSuggestionsForPrefix:prefix limit:limit];
}
[matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSInteger ca = self.selectionCounts[a].integerValue;
NSInteger cb = self.selectionCounts[b].integerValue;
if (ca != cb) {
return (cb > ca) ? NSOrderedAscending : NSOrderedDescending;
}
return [a compare:b];
}];
if (matches.count > limit) {
return [matches subarrayWithRange:NSMakeRange(0, limit)];
}
return matches.copy;
return matches;
}
- (NSArray<NSString *> *)kb_loadSpanishWords {
@@ -607,4 +597,136 @@
return result.count > 0 ? [result copy] : [self.class kb_defaultWords];
}
#pragma mark - Portuguese Suggestions
- (NSArray<NSString *> *)kb_portugueseSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
NSArray<NSString *> *matches = [self kb_suggestionsFromWordList:self.portugueseWords
prefix:prefix
limit:limit];
if (matches.count == 0) {
return [self kb_latinSuggestionsForPrefix:prefix limit:limit];
}
return matches;
}
- (NSArray<NSString *> *)kb_loadPortugueseWords {
NSString *path = [[NSBundle mainBundle] pathForResource:@"portuguese_words" ofType:@"json"];
if (!path) {
NSLog(@"[KBSuggestionEngine] portuguese_words.json not found, using default words");
return [self.class kb_defaultWords];
}
NSData *data = [NSData dataWithContentsOfFile:path];
if (!data) {
NSLog(@"[KBSuggestionEngine] Failed to read portuguese_words.json");
return [self.class kb_defaultWords];
}
NSError *error = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error || ![json isKindOfClass:NSDictionary.class]) {
NSLog(@"[KBSuggestionEngine] Failed to parse portuguese_words.json: %@", error);
return [self.class kb_defaultWords];
}
NSArray *wordsArray = json[@"words"];
if (![wordsArray isKindOfClass:NSArray.class]) {
NSLog(@"[KBSuggestionEngine] Invalid words array in portuguese_words.json");
return [self.class kb_defaultWords];
}
NSMutableArray<NSString *> *result = [NSMutableArray array];
for (id item in wordsArray) {
if ([item isKindOfClass:NSString.class]) {
[result addObject:item];
}
}
NSLog(@"[KBSuggestionEngine] Loaded %lu Portuguese words", (unsigned long)result.count);
return result.count > 0 ? [result copy] : [self.class kb_defaultWords];
}
#pragma mark - Indonesian Suggestions
- (NSArray<NSString *> *)kb_indonesianSuggestionsForPrefix:(NSString *)prefix limit:(NSUInteger)limit {
NSArray<NSString *> *matches = [self kb_suggestionsFromWordList:self.indonesianWords
prefix:prefix
limit:limit];
if (matches.count == 0) {
return [self kb_latinSuggestionsForPrefix:prefix limit:limit];
}
return matches;
}
- (NSArray<NSString *> *)kb_loadIndonesianWords {
NSString *path = [[NSBundle mainBundle] pathForResource:@"indonesian_words" ofType:@"json"];
if (!path) {
NSLog(@"[KBSuggestionEngine] indonesian_words.json not found, using default words");
return [self.class kb_defaultWords];
}
NSData *data = [NSData dataWithContentsOfFile:path];
if (!data) {
NSLog(@"[KBSuggestionEngine] Failed to read indonesian_words.json");
return [self.class kb_defaultWords];
}
NSError *error = nil;
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];
if (error || ![json isKindOfClass:NSDictionary.class]) {
NSLog(@"[KBSuggestionEngine] Failed to parse indonesian_words.json: %@", error);
return [self.class kb_defaultWords];
}
NSArray *wordsArray = json[@"words"];
if (![wordsArray isKindOfClass:NSArray.class]) {
NSLog(@"[KBSuggestionEngine] Invalid words array in indonesian_words.json");
return [self.class kb_defaultWords];
}
NSMutableArray<NSString *> *result = [NSMutableArray array];
for (id item in wordsArray) {
if ([item isKindOfClass:NSString.class]) {
[result addObject:item];
}
}
NSLog(@"[KBSuggestionEngine] Loaded %lu Indonesian words", (unsigned long)result.count);
return result.count > 0 ? [result copy] : [self.class kb_defaultWords];
}
#pragma mark - Word List Helpers
- (NSArray<NSString *> *)kb_suggestionsFromWordList:(NSArray<NSString *> *)words
prefix:(NSString *)prefix
limit:(NSUInteger)limit {
NSString *lower = prefix.lowercaseString;
NSMutableArray<NSString *> *matches = [NSMutableArray array];
for (NSString *word in words) {
if ([word hasPrefix:lower]) {
[matches addObject:word];
if (matches.count >= limit * 2) {
break;
}
}
}
if (matches.count == 0) { return @[]; }
[matches sortUsingComparator:^NSComparisonResult(NSString *a, NSString *b) {
NSInteger ca = self.selectionCounts[a].integerValue;
NSInteger cb = self.selectionCounts[b].integerValue;
if (ca != cb) {
return (cb > ca) ? NSOrderedAscending : NSOrderedDescending;
}
return [a compare:b];
}];
if (matches.count > limit) {
return [matches subarrayWithRange:NSMakeRange(0, limit)];
}
return matches.copy;
}
@end