This commit is contained in:
2026-03-04 21:16:21 +08:00
parent cb0b8a0aee
commit 8316d42fb3
7 changed files with 78 additions and 247 deletions

View File

@@ -16,13 +16,12 @@ typedef NSString *KBLanguageCode NS_EXTENSIBLE_STRING_ENUM;
/// 项目内统一使用的语言常量
FOUNDATION_EXPORT KBLanguageCode const KBLanguageCodeEnglish; // @"en"
FOUNDATION_EXPORT KBLanguageCode const KBLanguageCodeSimplifiedChinese; // @"zh-Hans"
FOUNDATION_EXPORT KBLanguageCode const KBLanguageCodeTraditionalChinese; // @"zh-Hant"
FOUNDATION_EXPORT KBLanguageCode const KBLanguageCodeSpanish; // @"es"
FOUNDATION_EXPORT KBLanguageCode const KBLanguageCodePortuguese; // @"pt-PT"
FOUNDATION_EXPORT KBLanguageCode const KBLanguageCodeIndonesian; // @"id"
/// 默认支持的语言列表当前en / zh-Hans / zh-Hant / es / pt-PT / id
/// 默认支持的语言列表当前en / es / id / pt-PT / zh-Hant
FOUNDATION_EXPORT NSArray<KBLanguageCode> *KBDefaultSupportedLanguageCodes(void);
/// 当前语言变更通知(不附带 userInfo
@@ -34,7 +33,7 @@ extern NSString * const KBLocalizationDidChangeNotification;
/// 单例
+ (instancetype)shared;
/// 当前语言代码KBLanguageCodeEnglish、KBLanguageCodeSimplifiedChinese
/// 当前语言代码KBLanguageCodeEnglish、KBLanguageCodeTraditionalChinese
/// 默认会在受支持语言中,按系统首选语言择优匹配。
@property (nonatomic, copy, readonly) KBLanguageCode currentLanguageCode;

View File

@@ -9,7 +9,6 @@
///
KBLanguageCode const KBLanguageCodeEnglish = @"en";
KBLanguageCode const KBLanguageCodeSimplifiedChinese = @"zh-Hans";
KBLanguageCode const KBLanguageCodeTraditionalChinese = @"zh-Hant";
KBLanguageCode const KBLanguageCodeSpanish = @"es";
KBLanguageCode const KBLanguageCodePortuguese = @"pt-PT";
@@ -22,11 +21,10 @@ NSArray<KBLanguageCode> *KBDefaultSupportedLanguageCodes(void) {
dispatch_once(&onceToken, ^{
codes = @[
KBLanguageCodeEnglish,
KBLanguageCodeSimplifiedChinese,
KBLanguageCodeTraditionalChinese,
KBLanguageCodeSpanish,
KBLanguageCodeIndonesian,
KBLanguageCodePortuguese,
KBLanguageCodeIndonesian
KBLanguageCodeTraditionalChinese
];
});
return codes;
@@ -125,7 +123,7 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
if ([pLC isEqualToString:s.lowercaseString]) { return s; }
}
}
// 2) zh-Hans-CN -> zh-Hans, en-GB -> en
// 2) en-GB -> en, es-MX -> es
for (NSString *p in preferred) {
NSString *pLC = p.lowercaseString;
for (NSString *s in self.supportedLanguageCodes) {
@@ -139,6 +137,15 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
}
}
}
// 2.5) pt-BR / pt-PT / pt -> pt-PT
for (NSString *p in preferred) {
NSString *pLC = p.lowercaseString;
if ([pLC isEqualToString:@"pt"] || [pLC hasPrefix:@"pt-"] || [pLC hasPrefix:@"pt_"]) {
for (NSString *s in self.supportedLanguageCodes) {
if ([s.lowercaseString isEqualToString:@"pt-pt"]) { return s; }
}
}
}
// 3) zh-Hant/zh-TW/zh-HK zh-Hant
for (NSString *p in preferred) {
NSString *pLC = p.lowercaseString;
@@ -147,11 +154,6 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
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 ?: KBLanguageCodeEnglish;
@@ -161,9 +163,14 @@ static inline NSMutableDictionary *KBLocBaseKCQuery(void) {
- (void)applyLanguage:(NSString *)code {
NSString *normalizedCode = code;
if ([normalizedCode.lowercaseString isEqualToString:@"pt"]) {
NSString *lower = normalizedCode.lowercaseString;
if ([lower isEqualToString:@"pt"] || [lower hasPrefix:@"pt-"] || [lower hasPrefix:@"pt_"]) {
normalizedCode = @"pt-PT";
}
lower = normalizedCode.lowercaseString;
if ([lower hasPrefix:@"zh-hant"] || [lower hasPrefix:@"zh_hant"] || [lower hasPrefix:@"zh-tw"] || [lower hasPrefix:@"zh_hk"]) {
normalizedCode = KBLanguageCodeTraditionalChinese;
}
_currentLanguageCode = [normalizedCode copy];
// TargetApp bundle .lproj
NSString *path = [NSBundle.mainBundle pathForResource:normalizedCode ofType:@"lproj"];

View File

@@ -1,220 +0,0 @@
/*
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" = "多语言测试";
"home_item_keyboard_permission" = "键盘权限引导";
"lang_test_title" = "多语言测试";
"lang_toggle" = "切换语言";
"current_lang" = "当前:%@";
"common_back" = "返回";
// search
"Recommended Skin" = "推荐皮肤";
"Historical Search" = "历史搜索";
"Search Themes" = "搜索主题";
"Search" = "搜索";
// 登录与账号(以英文 key 为准)
"Log In" = "登录";
"Signed in successfully" = "登录成功";
"Sign-in failed" = "登录失败";
"Sign in to unlock all features" = "登录后可使用全部功能";
"We'll use Apple for a quick, secure sign-in" = "我们将使用 Apple 进行快速安全登录";
"Log In To Key Of Love" = "登录 Key Of Love";
"Apple 登录需要 iOS 13 及以上版本" = "Apple 登录需要 iOS 13 及以上版本";
"Sign in with Apple requires iOS 13 or later" = "需要 iOS13+ 才能使用 Apple 登录";
"Invalid login credential" = "无效的登录凭证";
"No token returned" = "未返回 token";
"Failed to save login state" = "保存登录态失败";
"Sign-in canceled" = "登录已取消";
"Please switch to the key of love app to finish signing in" = "请切换到Key of Love App完成登录";
"Continue Via Email" = "通过邮箱登录";
"Login With Email Password" = "使用邮箱密码登录";
"Enter Email Address" = "请输入邮箱地址";
"Enter Password" = "请输入密码";
"Enter Repeat Password" = "请再次输入密码";
"Enter Your Email Address" = "请输入您的邮箱地址";
"Enter The Password" = "请输入密码";
"Please Enter Your Email Address" = "请输入您的邮箱地址";
"Please Enter The Password" = "请输入密码";
"Please complete all fields" = "请先填写完整信息";
"The two passwords do not match" = "两次输入的密码不一致";
"Reset Password" = "重置密码";
"Next Step" = "下一步";
"Enter Email Verification Code" = "请输入邮箱验证码";
"Verify Email" = "验证邮箱";
"We have already sent it to the email address %@. Please enter the 6-digit verification code from the email to verify your mailbox." = "我们已将验证码发送至邮箱 %@,请在邮件中查看并输入 6 位验证码完成验证。";
"Forgot Password?" = "忘记密码?";
"Already Have An Account?" = "已经有账号了?";
"Don't Have An Account?" = "还没有账号?";
"Sign Up" = "注册";
"Sign In" = "登录";
"Login" = "登录";
// 语言切换提示
"Change Language" = "切换语言";
"Changing language will reload the Home screen." = "切换语言后将重新加载首页。";
// 通用按钮与提示(英文 key
"OK" = "好";
"Confirm" = "确定";
"Cancel" = "取消";
"Close" = "关闭";
"Delete" = "删除";
"Clear" = "清空";
"Paste" = "粘贴";
"Send" = "发送";
"Retry" = "重试";
"Success" = "成功";
"Failed" = "失败";
"Network error" = "网络错误";
"Saved" = "已保存";
"Copy Success" = "复制成功";
"Email Copy Success" = "Email Copy Success";
// 网络相关(英文 key
"Network unavailable" = "网络不可用";
"Network disabled (Full Access may be off)" = "网络未启用(可能未开启完全访问)";
"Invalid URL" = "无效的URL";
"Invalid response" = "无效的响应";
"No data" = "无数据";
"Failed to parse JSON" = "JSON解析失败";
"Parse failed" = "解析失败";
"No data received" = "未获取到数据";
"请求失败\nURL: %@\n状态: %ld\n错误: %@\nUserInfo: %@" = "请求失败\nURL: %@\n状态: %ld\n错误: %@\nUserInfo: %@";
"响应成功(JSON)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@" = "响应成功(JSON)\nURL: %@\n状态: %ld\nContent-Type: %@\n数据: %@";
"响应解析失败(JSON)\nURL: %@\n错误: %@" = "响应解析失败(JSON)\nURL: %@\n错误: %@";
"HTTP GET\nURL: %@\nHeaders: %@\n参数: %@" = "HTTP GET\nURL: %@\nHeaders: %@\n参数: %@";
"无效响应\nURL: %@\n说明: %@" = "无效响应\nURL: %@\n说明: %@";
"Please check this app's wireless-data permission or network connection in Settings." = "请在“设置”中检查本应用的无线数据权限或网络连接。";
// 权限与引导(英文 key
"Turn on Allow Full Access to experience all features" = "开启【允许完全访问】,体验完整功能";
"Allow Full Access" = "允许完全访问";
"Follow: Settings → General → Keyboard → Keyboards → %@ → Allow Full Access" = "请按路径:设置→通用→键盘→键盘→%@→允许完全访问";
"Go enable" = "去开启";
"Open Settings" = "去设置";
"After pasting the conversation in the keyboard, choose a reply style" = "在键盘粘贴对话后,选择回复方式";
"Current: %@" = "当前:%@";
// 首页 / Tab 文案(英文 key
"Home" = "首页";
"Shop" = "商城";
"Circle" = "社区";
"Mine" = "我的";
"Hot" = "热门";
"Rank" = "排行";
"Recharge Now" = "立即充值";
"By clicking Pay, you indicate your agreement to the" = "点击“立即充值”即表示您同意";
"《Embership Agreement》" = "《会员协议》";
// Mine
"Settings" = "设置";
"Personal" = "个人";
"My Keyboard" = "我的键盘";
"Notice" = "通知";
"invite" = "邀请";
"Feedback" = "反馈";
"E-mail" = "联系我们";
"Agreement" = "协议";
"Privacy Policy" = "隐私政策";
"Notice" = "通知";
"Feedback" = "反馈";
// 通知 & 反馈详情页
"Notification Setting" = "通知设置";
"Please Enter The Content" = "请输入反馈内容";
"Commit" = "提交";
"Nickname" = "用户名";
"Gender" = "性别";
"Input Language" = "输入语言";
"Choose Layout" = "选择键盘布局";
"Multiple Keyboard Layouts" = "多种键盘布局";
"This language has a default skin configured. It won't be auto-applied when switching language." = "该语言已配置默认皮肤,切换语言时不会自动应用。";
"Please configure a default skin for this language before switching." = "请先为该语言配置默认皮肤";
"Default skin install failed. Please check skin resource configuration." = "默认皮肤安装失败,请检查皮肤资源配置";
"User ID" = "用户ID";
"Modify Gender" = "修改性别";
"Male" = "男";
"Female" = "女";
"The Third Gender" = "第三性别";
" Paste Ta's Words" = " 粘贴TA的话";
// 搜索与历史(英文 key
"Clear history" = "清空历史";
"Delete all history?" = "是否删除所有历史记录?";
"Delete this tag?" = "删除该标签?";
"This action cannot be undone" = "删除后不可恢复";
"Loaded more successfully" = "加载更多成功";
// 皮肤与商城(英文 key
"Skin Center" = "皮肤中心";
"No skins yet" = "暂无皮肤";
"Pull down to refresh" = "下拉刷新试试";
"Download & Apply" = "下载并应用";
"Applied. Switch to the keyboard to view." = "已应用,切到键盘查看";
"Apply failed" = "应用失败";
"Open agreement" = "跳转协议";
"Shop Mall" = "购物商城";
"My skin" = "我的皮肤";
"my_skin_selected_count" = "已选择:%ld 个皮肤";
"Editor" = "编辑";
"Cancel" = "取消";
"Delete" = "删除";
"Points\nMall" = "积分\n商城";
"Log Out" = "退出";
"Cancel Account" = "注销账户";
"After cancellation, your account will be deactivated and local login data will be cleared. Continue?" = "注销后账号将被停用,并清除本地登录数据,是否继续?";
"Please enter your password" = "请输入密码";
"Cancel Account Notice" = "注销账户须知";
"Confirm Cancel Account" = "确认注销";
"Ranking List" = "排行榜";
"Persona circle" = "圈子";
"Clear" = "立刻清空";
"Copy" = "复制";
"Report" = "举报";
"Thumbs Up" = "赞过";
"Chatting" = "聊过";
// 皮肤示例名称
// 支付与内购(英文 key
"Payment successful" = "支付成功";
"Payment failed" = "支付失败";
"Purchase: %@ Coins %@" = "购买:%@ Coins %@";
"Pay clicked" = "点击支付";
"Points Recharge" = "积分充值";
"Recharge" = "充值";
"Consumption Record" = "消费记录";
"My Points" = "我的积分";
"Consumption Details" = "消费明细";
"No data" = "暂无数据";
// 其它
"Test" = "测试";
"暂无数据" = "暂无数据"; // 已有英文 key "No data"
"Change The Nickname" = "修改名称";
"Please Enter The Modified Nickname" = "请输入修改后的昵称";
"Save" = "保存";
"Please copy the text first" = "请先复制文本";
"Purchase cancelled." = "已取消购买";
"Purchase pending approval." = "购买等待确认";
"Unable to obtain transaction payload." = "无法获取交易凭据";
"Resume Purchase" = "恢复购买";
"Downloading..." = "正在下载...";

View File

@@ -726,7 +726,6 @@
04A9FE182EB892460020DB6D /* KBLocalizationManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBLocalizationManager.h; sourceTree = "<group>"; };
04A9FE192EB892460020DB6D /* KBLocalizationManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBLocalizationManager.m; sourceTree = "<group>"; };
04A9FE1C2EB893F10020DB6D /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
04A9FE1D2EB893F10020DB6D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
04B5A1A02EEFA12300AAAAAA /* KBPayProductModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBPayProductModel.h; sourceTree = "<group>"; };
04B5A1A12EEFA12300AAAAAA /* KBPayProductModel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KBPayProductModel.m; sourceTree = "<group>"; };
04BBF8992F3ACD8800B1FBB2 /* KBKeyboardStressTestVC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KBKeyboardStressTestVC.h; sourceTree = "<group>"; };
@@ -2363,7 +2362,6 @@
knownRegions = (
en,
Base,
"zh-Hans",
"zh-Hant",
es,
id,
@@ -2828,7 +2826,6 @@
isa = PBXVariantGroup;
children = (
04A9FE1C2EB893F10020DB6D /* en */,
04A9FE1D2EB893F10020DB6D /* zh-Hans */,
04837AE12F5848050012BDE2 /* zh-Hant */,
04837AE42F58485A0012BDE2 /* es */,
04837AE72F5848680012BDE2 /* id */,

View File

@@ -71,10 +71,10 @@
if (!self) return;
KBLocalizationManager *mgr = [KBLocalizationManager shared];
//
//
NSString *next = [mgr.currentLanguageCode.lowercaseString hasPrefix:@"zh"]
? KBLanguageCodeEnglish
: KBLanguageCodeSimplifiedChinese;
: KBLanguageCodeTraditionalChinese;
[mgr setCurrentLanguageCode:next persist:YES];
//

View File

@@ -348,6 +348,8 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
@property (nonatomic, strong) UIImageView *avatarView; //
@property (nonatomic, strong) UIButton *editBadge; //
@property (nonatomic, strong) UILabel *modifyLabel; // Modify
@property (nonatomic, strong) UILabel *userIdLabel; // ID
@property (nonatomic, strong) UIButton *userIdCopyButton; // ID
// 退
@property (nonatomic, strong) UIButton *logoutBtn;
@@ -421,6 +423,7 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
weakSelf.userModel = user;
[weakSelf.avatarView kb_setAvatarURL:weakSelf.userModel.avatarUrl placeholder:KBAvatarPlaceholderImage];
weakSelf.modifyLabel.text = weakSelf.userModel.nickName;
weakSelf.userIdLabel.text = weakSelf.userModel.userId ?: @"";
}
[weakSelf rebuildItems];
}];
@@ -443,12 +446,10 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
NSString *nickname = self.userModel.nickName ?: @"";
NSString *genderText = [self kb_genderDisplayText];
NSString *languageText = [self currentInputProfileDisplayText];
NSString *userId = self.userModel.userId ?: @"";
self.items = @[
@{ @"title": KBLocalized(@"Nickname"), @"value": nickname, @"arrow": @YES, @"copy": @NO },
@{ @"title": KBLocalized(@"Gender"), @"value": genderText, @"arrow": @YES, @"copy": @NO },
@{ @"title": KBLocalized(@"Input Language"), @"value": languageText, @"arrow": @YES, @"copy": @NO },
@{ @"title": KBLocalized(@"User ID"), @"value": userId, @"arrow": @NO, @"copy": @YES },
];
[self.tableView reloadData];
}
@@ -838,6 +839,13 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
[self.myVM logout];
}
- (void)onTapUserIdCopy {
NSString *userId = self.userModel.userId ?: @"";
if (userId.length == 0) { return; }
UIPasteboard.generalPasteboard.string = userId;
[KBHUD showInfo:KBLocalized(@"Copied")];
}
#pragma mark - Lazy UI
- (UITableView *)tableView {
@@ -856,12 +864,14 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
- (UIView *)headerView {
if (!_headerView) {
CGFloat w = UIScreen.mainScreen.bounds.size.width;
UIView *hv = [[UIView alloc] initWithFrame:CGRectMake(0, 0, w, 180)];
UIView *hv = [[UIView alloc] initWithFrame:CGRectMake(0, 0, w, 200)];
hv.backgroundColor = UIColor.clearColor;
[hv addSubview:self.avatarView];
[hv addSubview:self.editBadge];
[hv addSubview:self.modifyLabel];
[hv addSubview:self.userIdLabel];
[hv addSubview:self.userIdCopyButton];
[self.avatarView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(hv);
@@ -877,6 +887,15 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
make.top.equalTo(self.avatarView.mas_bottom).offset(10);
make.centerX.equalTo(hv);
}];
[self.userIdLabel mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.modifyLabel.mas_bottom).offset(10);
make.centerX.equalTo(hv).offset(-KBFit(12.0));
}];
[self.userIdCopyButton mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.userIdLabel);
make.left.equalTo(self.userIdLabel.mas_right).offset(6);
make.width.height.mas_equalTo(18);
}];
//
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapAvatarEdit)];
@@ -928,6 +947,31 @@ typedef void(^KBInputProfileSelectHandler)(NSString *languageCode, NSString *lay
return _modifyLabel;
}
- (UILabel *)userIdLabel {
if (!_userIdLabel) {
_userIdLabel = [UILabel new];
_userIdLabel.textColor = [UIColor colorWithHex:0x9B9B9B];
_userIdLabel.font = [KBFont regular:12];
}
return _userIdLabel;
}
- (UIButton *)userIdCopyButton {
if (!_userIdCopyButton) {
_userIdCopyButton = [UIButton buttonWithType:UIButtonTypeCustom];
UIImage *image = [UIImage imageNamed:@"copy_icon"];
if (image) {
[_userIdCopyButton setImage:image forState:UIControlStateNormal];
} else {
[_userIdCopyButton setTitle:KBLocalized(@"Copy") forState:UIControlStateNormal];
[_userIdCopyButton setTitleColor:[UIColor colorWithHex:KBBlackValue] forState:UIControlStateNormal];
_userIdCopyButton.titleLabel.font = [KBFont regular:12];
}
[_userIdCopyButton addTarget:self action:@selector(onTapUserIdCopy) forControlEvents:UIControlEventTouchUpInside];
}
return _userIdCopyButton;
}
- (UIButton *)logoutBtn {
if (!_logoutBtn) {
_logoutBtn = [UIButton buttonWithType:UIButtonTypeSystem];

View File

@@ -491,11 +491,15 @@ NSString * const KBUserCharacterDeletedNotification = @"KBUserCharacterDeletedNo
- (void)fetchCancelAccountWarningWithCompletion:(KBCancelAccountAgreementCompletion)completion {
KBLanguageCode langCode = [KBLocalizationManager shared].currentLanguageCode;
NSString *locale;
if ([langCode isEqualToString:KBLanguageCodeSimplifiedChinese]) {
locale = @"zh-CN";
} else {
locale = @"en-US";
NSString *locale = @"en-US";
if ([langCode isEqualToString:KBLanguageCodeTraditionalChinese]) {
locale = @"zh-TW";
} else if ([langCode isEqualToString:KBLanguageCodeSpanish]) {
locale = @"es-ES";
} else if ([langCode isEqualToString:KBLanguageCodeIndonesian]) {
locale = @"id-ID";
} else if ([langCode isEqualToString:KBLanguageCodePortuguese]) {
locale = @"pt-PT";
}
[[KBNetworkManager shared] GET:API_CANCEL_ACCOUNT_WARNING