diff --git a/Easydict.xcodeproj/project.pbxproj b/Easydict.xcodeproj/project.pbxproj index 1d97b189a..cded438e9 100644 --- a/Easydict.xcodeproj/project.pbxproj +++ b/Easydict.xcodeproj/project.pbxproj @@ -274,6 +274,10 @@ 62E2BF4B2B4082BA00E42D38 /* AliResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF482B4082BA00E42D38 /* AliResponse.swift */; }; 62E2BF4C2B4082BA00E42D38 /* AliTranslateType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 62E2BF492B4082BA00E42D38 /* AliTranslateType.swift */; }; 62ED29A22B15F1F500901F51 /* EZWrapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 62ED29A12B15F1F500901F51 /* EZWrapView.m */; }; + 6A8C988E2BAC88B500DB835A /* LanguageState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8C988C2BAC88B500DB835A /* LanguageState.swift */; }; + 6A8C988F2BAC88B500DB835A /* I18nHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8C988D2BAC88B500DB835A /* I18nHelper.swift */; }; + 6A8C98952BAE841600DB835A /* LocalizedBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A8C98942BAE841600DB835A /* LocalizedBundle.swift */; }; + 6ADED1552BAE8809004A15BE /* NSBundle+LanguagePreference.m in Sources */ = {isa = PBXBuildFile; fileRef = 6ADED1542BAE8809004A15BE /* NSBundle+LanguagePreference.m */; }; 960835502B6791F200C6A931 /* Shortcut+Validator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9608354F2B6791F200C6A931 /* Shortcut+Validator.swift */; }; 96099AE22B5D40330055C4DD /* ShortcutTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 96099AE12B5D40330055C4DD /* ShortcutTab.swift */; }; 9627F9382B59956800B1E999 /* GlobalShortcutSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9627F9352B59956800B1E999 /* GlobalShortcutSetting.swift */; }; @@ -801,6 +805,10 @@ 62ED29A02B15F1F500901F51 /* EZWrapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EZWrapView.h; sourceTree = ""; }; 62ED29A12B15F1F500901F51 /* EZWrapView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EZWrapView.m; sourceTree = ""; }; 6372B33DFF803C7096A82250 /* Pods_Easydict.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Easydict.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A8C988C2BAC88B500DB835A /* LanguageState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageState.swift; sourceTree = ""; }; + 6A8C988D2BAC88B500DB835A /* I18nHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = I18nHelper.swift; sourceTree = ""; }; + 6A8C98942BAE841600DB835A /* LocalizedBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedBundle.swift; sourceTree = ""; }; + 6ADED1542BAE8809004A15BE /* NSBundle+LanguagePreference.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBundle+LanguagePreference.m"; sourceTree = ""; }; 91E3E579C6DB88658B4BB102 /* Pods-Easydict.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Easydict.release.xcconfig"; path = "Target Support Files/Pods-Easydict/Pods-Easydict.release.xcconfig"; sourceTree = ""; }; 9608354F2B6791F200C6A931 /* Shortcut+Validator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Shortcut+Validator.swift"; sourceTree = ""; }; 96099AE12B5D40330055C4DD /* ShortcutTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutTab.swift; sourceTree = ""; }; @@ -2164,6 +2172,7 @@ 27FE98032B3DCA9F000AD654 /* NewApp */ = { isa = PBXGroup; children = ( + 6A8C988B2BAC88A600DB835A /* LanguagePreference */, 967712EB2B5B93E200105E0F /* Feature */, EA9943E12B534C2900EE7B97 /* Model */, EA9943DD2B534BAE00EE7B97 /* Utility */, @@ -2248,6 +2257,17 @@ path = EZWrapView; sourceTree = ""; }; + 6A8C988B2BAC88A600DB835A /* LanguagePreference */ = { + isa = PBXGroup; + children = ( + 6A8C988D2BAC88B500DB835A /* I18nHelper.swift */, + 6A8C988C2BAC88B500DB835A /* LanguageState.swift */, + 6A8C98942BAE841600DB835A /* LocalizedBundle.swift */, + 6ADED1542BAE8809004A15BE /* NSBundle+LanguagePreference.m */, + ); + path = LanguagePreference; + sourceTree = ""; + }; 713A345D86B5BC86D158B68F /* Frameworks */ = { isa = PBXGroup; children = ( @@ -2889,6 +2909,7 @@ 960835502B6791F200C6A931 /* Shortcut+Validator.swift in Sources */, 03542A522937B69200C34C33 /* EZYoudaoTranslateResponse.m in Sources */, 03B0230129231FA6001C7E63 /* EZQueryView.m in Sources */, + 6A8C988E2BAC88B500DB835A /* LanguageState.swift in Sources */, 03542A3D2937AF4F00C34C33 /* EZQueryResult.m in Sources */, 03262C1F29EF8EE500EFECA0 /* EZPrivacyViewController.m in Sources */, 9672D7D22B4008B40023B8FB /* MASShortcutBinder+EZMASShortcutBinder.m in Sources */, @@ -2935,6 +2956,7 @@ 03B0230229231FA6001C7E63 /* EZWordResultView.m in Sources */, 0399C6A529A747E600B4AFCC /* EZDeepLTranslateResponse.m in Sources */, 17BCAEF82B0DFF9000A7D372 /* EZNiuTransTranslate.m in Sources */, + 6A8C98952BAE841600DB835A /* LocalizedBundle.swift in Sources */, 039F5506294B6E29004AB940 /* EZSettingViewController.m in Sources */, 03BD281E29481C0400F5891A /* EZAudioPlayer.m in Sources */, 0A8685C82B552A590022534F /* DisabledAppTab.swift in Sources */, @@ -3013,9 +3035,11 @@ 03B0231729231FA6001C7E63 /* Snip.m in Sources */, 03BFFC6E295FE59C004E033E /* EZQueryResult+EZYoudaoDictModel.m in Sources */, DC3C643F2B187119008EEDD8 /* ChangeFontSizeView.swift in Sources */, + 6ADED1552BAE8809004A15BE /* NSBundle+LanguagePreference.m in Sources */, 03B0232829231FA6001C7E63 /* NSTextView+Height.m in Sources */, 03B0232129231FA6001C7E63 /* NSPasteboard+MM.m in Sources */, 03D043522928935300E7559E /* EZMainQueryWindow.m in Sources */, + 6A8C988F2BAC88B500DB835A /* I18nHelper.swift in Sources */, 03D8B26E292DBD2000D5A811 /* EZCoordinateUtils.m in Sources */, 03B0232029231FA6001C7E63 /* NSWindow+MM.m in Sources */, 0AC8A8432B6957B0006DA5CC /* BingService+ConfigurableService.swift in Sources */, diff --git a/Easydict/App/Localizable.xcstrings b/Easydict/App/Localizable.xcstrings index e49d75387..2972fb27c 100644 --- a/Easydict/App/Localizable.xcstrings +++ b/Easydict/App/Localizable.xcstrings @@ -1761,6 +1761,23 @@ } } }, + "language_preference" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Display Language" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "显示语言" + } + } + } + }, "large" : { "extractionState" : "manual", "localizations" : { @@ -2675,22 +2692,6 @@ } } }, - "service.configuration.custom_openai.name.title" : { - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Custom Service Name" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "自定义服务名称" - } - } - } - }, "service.configuration.custom_openai.model.placeholder" : { "localizations" : { "en" : { @@ -2707,6 +2708,22 @@ } } }, + "service.configuration.custom_openai.name.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Custom Service Name" + } + }, + "zh-Hans" : { + "stringUnit" : { + "state" : "translated", + "value" : "自定义服务名称" + } + } + } + }, "service.configuration.custom_openai.supported_models.title" : { "localizations" : { "en" : { @@ -4634,4 +4651,4 @@ } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/Easydict/App/PrefixHeader.pch b/Easydict/App/PrefixHeader.pch index 9f259206c..0f48bed10 100644 --- a/Easydict/App/PrefixHeader.pch +++ b/Easydict/App/PrefixHeader.pch @@ -40,3 +40,5 @@ #import "EZConst.h" #import "EZConstKey.h" #import "NSString+EZConvenience.h" + +#import "Easydict-Swift.h" diff --git a/Easydict/Feature/Service/Language/EZLanguageManager.m b/Easydict/Feature/Service/Language/EZLanguageManager.m index 6650763d5..29b69f511 100644 --- a/Easydict/Feature/Service/Language/EZLanguageManager.m +++ b/Easydict/Feature/Service/Language/EZLanguageManager.m @@ -355,7 +355,7 @@ - (NSString *)languageLocalName:(EZLanguage)language { /// Showing language name according user first language, Chinese: English -> 英语, English: English -> English. - (NSString *)showingLanguageName:(EZLanguage)language { NSString *languageName = language ?: EZLanguageAuto; - if ([self isSystemChineseFirstLanguage]) { + if (EZI18nHelper.shared.isSimplifiedChineseLocalize) { languageName = [self languageChineseName:language]; } else { if ([language isEqualToString:EZLanguageAuto]) { diff --git a/Easydict/Feature/Utility/EZCategory/NSObject+EZWindowType.m b/Easydict/Feature/Utility/EZCategory/NSObject+EZWindowType.m index 48567504a..a1ae71f4c 100644 --- a/Easydict/Feature/Utility/EZCategory/NSObject+EZWindowType.m +++ b/Easydict/Feature/Utility/EZCategory/NSObject+EZWindowType.m @@ -8,16 +8,16 @@ #import "NSObject+EZWindowType.h" -static NSString *EZWindowTypeKey = @"EZWindowTypeKey"; +static NSString *_EZWindowTypeKey = @"EZWindowTypeKey"; @implementation NSObject (EZWindowType) - (void)setWindowType:(EZWindowType)windowType { - objc_setAssociatedObject(self, (__bridge const void *)(EZWindowTypeKey), @(windowType), OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, (__bridge const void *)(_EZWindowTypeKey), @(windowType), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (EZWindowType)windowType { - return [objc_getAssociatedObject(self, (__bridge const void *)(EZWindowTypeKey)) integerValue]; + return [objc_getAssociatedObject(self, (__bridge const void *)(_EZWindowTypeKey)) integerValue]; } @end diff --git a/Easydict/Feature/Utility/Swift/Notification/Notification+Name.swift b/Easydict/Feature/Utility/Swift/Notification/Notification+Name.swift index bae75eb23..7addc58c7 100644 --- a/Easydict/Feature/Utility/Swift/Notification/Notification+Name.swift +++ b/Easydict/Feature/Utility/Swift/Notification/Notification+Name.swift @@ -12,6 +12,8 @@ extension Notification.Name { static let serviceHasUpdated = Notification.Name(EZServiceHasUpdatedNotification) static let openSettings = Notification.Name(EZOpenSettingsNotification) + + static let languagePreferenceChanged = Notification.Name(I18nHelper.languagePreferenceChangedNotification) } @objc @@ -19,4 +21,6 @@ extension NSNotification { public static let serviceHasUpdated = Notification.Name.serviceHasUpdated public static let openSettings = Notification.Name.openSettings + + public static let languagePreferenceChanged = Notification.Name.languagePreferenceChanged } diff --git a/Easydict/Feature/ViewController/View/CustomButton/LanguageButton/EZSelectLanguageButton.m b/Easydict/Feature/ViewController/View/CustomButton/LanguageButton/EZSelectLanguageButton.m index ae73f2782..cf071192b 100644 --- a/Easydict/Feature/ViewController/View/CustomButton/LanguageButton/EZSelectLanguageButton.m +++ b/Easydict/Feature/ViewController/View/CustomButton/LanguageButton/EZSelectLanguageButton.m @@ -118,7 +118,7 @@ - (void)setupMenu { NSString *languageFlag = [languageManager languageFlagEmoji:language]; if ([language isEqualToString:EZLanguageAuto]) { - if ([languageManager isSystemChineseFirstLanguage] && self.autoChineseSelectedTitle.length) { + if (EZI18nHelper.shared.isSimplifiedChineseLocalize && self.autoChineseSelectedTitle.length) { languageName = self.autoChineseSelectedTitle; } } @@ -171,7 +171,7 @@ - (void)setSelectedLanguage:(EZLanguage)selectedLanguage { NSString *toolTip = nil; if ([selectedLanguage isEqualToString:EZLanguageAuto]) { - if ([languageManager isSystemChineseFirstLanguage] && self.autoChineseSelectedTitle.length) { + if (EZI18nHelper.shared.isSimplifiedChineseLocalize && self.autoChineseSelectedTitle.length) { languageName = self.autoChineseSelectedTitle; } languageFlag = [languageManager languageFlagEmoji:self.autoSelectedLanguage]; diff --git a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m index 11181a029..ff6bcbdad 100644 --- a/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m +++ b/Easydict/Feature/ViewController/Window/BaseQueryWindow/EZBaseQueryViewController.m @@ -192,6 +192,12 @@ - (void)setupUI { [self updateAllResultCellHeight]; }]; }]; + + [defaultCenter addObserver:self selector:@selector(modifyLanduage:) name:NSNotification.languagePreferenceChanged object:nil]; +} + +- (void)modifyLanduage:(NSNotification *)notification { + [self.tableView reloadData]; } diff --git a/Easydict/NewApp/EasydictApp.swift b/Easydict/NewApp/EasydictApp.swift index 55a676234..5d77f14fe 100644 --- a/Easydict/NewApp/EasydictApp.swift +++ b/Easydict/NewApp/EasydictApp.swift @@ -34,6 +34,8 @@ struct EasydictApp: App { if #available(macOS 13, *) { MenuBarExtra(isInserted: $hideMenuBar.toggledValue) { MenuItemView() + .environmentObject(languageState) + .environment(\.locale, .init(identifier: I18nHelper.shared.localizeCode)) } label: { Label { Text("Easydict") @@ -62,7 +64,10 @@ struct EasydictApp: App { .windowResizability(.contentSize) Settings { - SettingView() + SettingView().environmentObject(languageState).environment( + \.locale, + .init(identifier: I18nHelper.shared.localizeCode) + ) } } } @@ -77,6 +82,7 @@ struct EasydictApp: App { private var hideMenuBar = Defaults.Key.hideMenuBarIcon.defaultValue @Default(.selectedMenuBarIcon) private var menuBarIcon + @StateObject private var languageState = LanguageState() } // MARK: - FakeViewToOpenSettingsInSonoma diff --git a/Easydict/NewApp/LanguagePreference/I18nHelper.swift b/Easydict/NewApp/LanguagePreference/I18nHelper.swift new file mode 100644 index 000000000..957bd7790 --- /dev/null +++ b/Easydict/NewApp/LanguagePreference/I18nHelper.swift @@ -0,0 +1,39 @@ +// +// I18nHelper.swift +// Easydict +// +// Created by choykarl on 2024/3/4. +// Copyright © 2024 izual. All rights reserved. +// + +import SwiftUI + +// MARK: - I18nHelper + +@objcMembers +@objc(EZI18nHelper) +class I18nHelper: NSObject { + static let languagePreferenceChangedNotification = "LanguagePreferenceChangedNotification" + static let shared = I18nHelper() + + var localizedBundle: Bundle { + guard let path = Bundle.main.path(forResource: localizeCode, ofType: "lproj"), + let bundle = Bundle(path: path) else { + return .main + } + return bundle + } + + var localizeCode: String { + UserDefaults.standard.string(forKey: languagePreferenceLocalKey) ?? LanguageState.LanguageType + .simplifiedChinese.rawValue + } + + var languageType: LanguageState.LanguageType { + LanguageState.LanguageType(rawValue: localizeCode) ?? .simplifiedChinese + } + + var isSimplifiedChineseLocalize: Bool { + localizeCode == LanguageState.LanguageType.simplifiedChinese.rawValue + } +} diff --git a/Easydict/NewApp/LanguagePreference/LanguageState.swift b/Easydict/NewApp/LanguagePreference/LanguageState.swift new file mode 100644 index 000000000..b780ec730 --- /dev/null +++ b/Easydict/NewApp/LanguagePreference/LanguageState.swift @@ -0,0 +1,41 @@ +// +// LanguageState.swift +// Easydict +// +// Created by choykarl on 2024/3/3. +// Copyright © 2024 izual. All rights reserved. +// + +import SwiftUI + +// MARK: - LanguageState + +let languagePreferenceLocalKey = "LanguagePreferenceLocalKey" + +// MARK: - LanguageState + +class LanguageState: ObservableObject { + enum LanguageType: String, CaseIterable { + case english = "en" + case simplifiedChinese = "zh-Hans" + + // MARK: Internal + + var name: String { + switch self { + case .english: + "English" + case .simplifiedChinese: + "简体中文" + } + } + } + + @AppStorage(languagePreferenceLocalKey) var language: LanguageType = (.init( + rawValue: Locale.current.identifier + ) ?? .simplifiedChinese) { + didSet { + NotificationCenter.default.post(name: .languagePreferenceChanged, object: nil) + } + } +} diff --git a/Easydict/NewApp/LanguagePreference/LocalizedBundle.swift b/Easydict/NewApp/LanguagePreference/LocalizedBundle.swift new file mode 100644 index 000000000..8e5e3b23d --- /dev/null +++ b/Easydict/NewApp/LanguagePreference/LocalizedBundle.swift @@ -0,0 +1,16 @@ +// +// LocalizedBundle.swift +// Easydict +// +// Created by choykarl on 2024/3/23. +// Copyright © 2024 izual. All rights reserved. +// + +import Foundation + +@objc(EZLocalizedBundle) +class LocalizedBundle: Bundle { + override func localizedString(forKey key: String, value: String?, table tableName: String?) -> String { + I18nHelper.shared.localizedBundle.localizedString(forKey: key, value: value, table: tableName) + } +} diff --git a/Easydict/NewApp/LanguagePreference/NSBundle+LanguagePreference.m b/Easydict/NewApp/LanguagePreference/NSBundle+LanguagePreference.m new file mode 100644 index 000000000..a6faed7c5 --- /dev/null +++ b/Easydict/NewApp/LanguagePreference/NSBundle+LanguagePreference.m @@ -0,0 +1,20 @@ +// +// NSBundle+LanguagePreference.m +// Easydict +// +// Created by choykarl on 2024/3/23. +// Copyright © 2024 izual. All rights reserved. +// + +#import + +@implementation NSBundle (LanguagePreference) + ++ (void)load { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + object_setClass([NSBundle mainBundle], [EZLocalizedBundle class]); + }); +} + +@end diff --git a/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift b/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift index 48ab3358b..1aae6f356 100644 --- a/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift +++ b/Easydict/NewApp/Utility/Extensions/LanguageExtensions.swift @@ -55,7 +55,7 @@ extension Language { } public var localizedName: String { - if EZLanguageManager.shared().isSystemChineseFirstLanguage() { + if I18nHelper.shared.isSimplifiedChineseLocalize { chineseName } else { if self == .auto { diff --git a/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift b/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift index 1a1554198..e4674ff4f 100644 --- a/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift +++ b/Easydict/NewApp/View/SettingView/Tabs/GeneralTab.swift @@ -212,6 +212,12 @@ struct GeneralTab: View { .foregroundStyle(.primary) } } + Picker("language_preference", selection: $languageState.language) { + ForEach(LanguageState.LanguageType.allCases, id: \.rawValue) { language in + Text(language.name) + .tag(language) + } + } } header: { Text("setting.general.other.header") } @@ -246,6 +252,7 @@ struct GeneralTab: View { // MARK: Private + @EnvironmentObject private var languageState: LanguageState @Default(.autoSelectText) private var autoSelectText @Default(.forceAutoGetSelectedText) private var forceAutoGetSelectedText @Default(.clickQuery) private var clickQuery