diff --git a/Packages/vChewing_Shared/Sources/Shared/Shared.swift b/Packages/vChewing_Shared/Sources/Shared/Shared.swift index 785ae131..6e781926 100644 --- a/Packages/vChewing_Shared/Sources/Shared/Shared.swift +++ b/Packages/vChewing_Shared/Sources/Shared/Shared.swift @@ -192,58 +192,56 @@ public enum CandidateKey { "123456789", "234567890", "QWERTYUIO", "QWERTASDF", "ASDFGHJKL", "ASDFZXCVB", ] - public enum ErrorType: Error, LocalizedError { - case empty + /// 僅列舉那些需要專門檢查才能發現的那種無法自動排除的錯誤。 + public enum ValidationError { + case noError case invalidCharacters - case containSpace - case duplicatedCharacters - case tooShort - case tooLong + case countMismatch - public var errorDescription: String { + public var description: String { switch self { - case .empty: - return NSLocalizedString("Candidates keys cannot be empty.", comment: "") case .invalidCharacters: - return NSLocalizedString( - "Candidate keys can only contain ASCII characters like alphanumericals.", - comment: "" - ) - case .containSpace: - return NSLocalizedString("Candidate keys cannot contain space.", comment: "") - case .duplicatedCharacters: - return NSLocalizedString("There should not be duplicated keys.", comment: "") - case .tooShort: - return NSLocalizedString( - "Please specify at least 6 candidate keys.", comment: "" - ) - case .tooLong: - return NSLocalizedString("Maximum 9 candidate keys allowed.", comment: "") + return "- " + + NSLocalizedString( + "Candidate keys can only contain printable ASCII characters like alphanumericals.", + comment: "" + ) + "\n" + "- " + NSLocalizedString("Candidate keys cannot contain space.", comment: "") + case .countMismatch: + return "- " + + NSLocalizedString( + "Minimum 6 candidate keys allowed.", comment: "" + ) + "\n" + "- " + NSLocalizedString("Maximum 9 candidate keys allowed.", comment: "") + case .noError: + return "" } } } - public static func validate(keys candidateKeys: String) throws { - let trimmed = candidateKeys.trimmingCharacters(in: .whitespacesAndNewlines) - if trimmed.isEmpty { - throw CandidateKey.ErrorType.empty + /// 校驗選字鍵參數資料值的合法性。 + /// - Remark: 傳入的參數值得事先做過下述處理: + /// ``` + /// .trimmingCharacters(in: .whitespacesAndNewlines).deduplicated + /// ``` + /// - Parameter candidateKeys: 傳入的參數值 + /// - Returns: 返回 nil 的話,證明沒有錯誤;否則會返回錯誤描述訊息。 + public static func validate(keys candidateKeys: String) -> String? { + var result = ValidationError.noError + charValidityCheck: for neta in candidateKeys { + if String(neta) == " " { + result = CandidateKey.ValidationError.invalidCharacters + break charValidityCheck + } + for subNeta in neta.unicodeScalars { + if !subNeta.isPrintableASCII { + result = CandidateKey.ValidationError.invalidCharacters + break charValidityCheck + } + } } - if !trimmed.canBeConverted(to: .ascii) { - throw CandidateKey.ErrorType.invalidCharacters - } - if trimmed.contains(" ") { - throw CandidateKey.ErrorType.containSpace - } - if trimmed.count < 6 { - throw CandidateKey.ErrorType.tooShort - } - if trimmed.count > 9 { - throw CandidateKey.ErrorType.tooLong - } - let set = Set(Array(trimmed)) - if set.count != trimmed.count { - throw CandidateKey.ErrorType.duplicatedCharacters + if !(6...9).contains(candidateKeys.count) { + result = CandidateKey.ValidationError.countMismatch } + return result == ValidationError.noError ? nil : result.description } } diff --git a/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift b/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift index fabe4640..97edb83a 100644 --- a/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift +++ b/Packages/vChewing_SwiftExtension/Sources/SwiftExtension/SwiftExtension.swift @@ -72,6 +72,12 @@ extension UniChar { } } +extension Unicode.Scalar { + public var isPrintableASCII: Bool { + (32...126).contains(value) + } +} + // MARK: - Stable Sort Extension // Ref: https://stackoverflow.com/a/50545761/4162914 diff --git a/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift b/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift index 58aaa9f4..9b4c3596 100644 --- a/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift +++ b/Source/Modules/UIModules/PrefUI/suiPrefPaneKeyboard.swift @@ -57,20 +57,24 @@ struct suiPrefPaneKeyboard: View { text: $selSelectionKeys.onChange { let value = selSelectionKeys let keys: String = value.trimmingCharacters(in: .whitespacesAndNewlines).deduplicated - do { - try CandidateKey.validate(keys: keys) - PrefMgr.shared.candidateKeys = keys + if keys.isEmpty { selSelectionKeys = PrefMgr.shared.candidateKeys - } catch CandidateKey.ErrorType.empty { - selSelectionKeys = PrefMgr.shared.candidateKeys - } catch { - if let window = ctlPrefUI.shared.controller.window, let error = error as? CandidateKey.ErrorType { - let alert = NSAlert(error: error.errorDescription) + return + } + // Start Error Handling. + if let errorResult = CandidateKey.validate(keys: keys) { + if let window = ctlPrefUI.shared.controller.window { + let alert = NSAlert(error: NSLocalizedString("Invalid Selection Keys.", comment: "")) + alert.informativeText = errorResult alert.beginSheetModal(for: window) { _ in selSelectionKeys = PrefMgr.shared.candidateKeys } IMEApp.buzz() } + } else { + PrefMgr.shared.candidateKeys = keys + selSelectionKeys = PrefMgr.shared.candidateKeys + return } } ).frame(width: 180).disabled(PrefMgr.shared.useIMKCandidateWindow) diff --git a/Source/Modules/WindowControllers/ctlPrefWindow.swift b/Source/Modules/WindowControllers/ctlPrefWindow.swift index 17afd11f..e37f5b51 100644 --- a/Source/Modules/WindowControllers/ctlPrefWindow.swift +++ b/Source/Modules/WindowControllers/ctlPrefWindow.swift @@ -202,22 +202,21 @@ class ctlPrefWindow: NSWindowController { ) .deduplicated else { + selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys return } - do { - try CandidateKey.validate(keys: keys) + guard let errorResult = CandidateKey.validate(keys: keys) else { PrefMgr.shared.candidateKeys = keys selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys - } catch CandidateKey.ErrorType.empty { - selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys - } catch { - if let window = window, let error = error as? CandidateKey.ErrorType { - let alert = NSAlert(error: error.errorDescription) - alert.beginSheetModal(for: window) { _ in - self.selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys - } - IMEApp.buzz() + return + } + if let window = window { + let alert = NSAlert(error: NSLocalizedString("Invalid Selection Keys.", comment: "")) + alert.informativeText = errorResult + alert.beginSheetModal(for: window) { _ in + self.selectionKeyComboBox.stringValue = PrefMgr.shared.candidateKeys } + IMEApp.buzz() } } diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index 3ff8e229..d1ee61a5 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -1,4 +1,5 @@ "vChewing" = "vChewing"; +"Invalid Selection Keys." = "Invalid Selection Keys."; "Alphanumerical Input Mode" = "Alphanumerical Input Mode"; "Chinese Input Mode" = "Chinese Input Mode"; "Please enter the client app bundle identifier(s) you want to register." = "Please enter the client app bundle identifier(s) you want to register."; @@ -51,10 +52,9 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude."; "Edit Phrase Replacement Table…" = "Edit Phrase Replacement Table…"; "Use Phrase Replacement" = "Use Phrase Replacement"; -"Candidate keys can only contain ASCII characters like alphanumericals." = "Candidate keys can only contain ASCII characters like alphanumericals."; +"Candidate keys can only contain printable ASCII characters like alphanumericals." = "Candidate keys can only contain printable ASCII characters like alphanumericals."; "Candidate keys cannot contain space." = "Candidate keys cannot contain space."; -"There should not be duplicated keys." = "There should not be duplicated keys."; -"Please specify at least 6 candidate keys." = "Please specify at least 6 candidate keys."; +"Minimum 6 candidate keys allowed." = "Minimum 6 candidate keys allowed."; "Maximum 9 candidate keys allowed." = "Maximum 9 candidate keys allowed."; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match."; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index 3ff8e229..d1ee61a5 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -1,4 +1,5 @@ "vChewing" = "vChewing"; +"Invalid Selection Keys." = "Invalid Selection Keys."; "Alphanumerical Input Mode" = "Alphanumerical Input Mode"; "Chinese Input Mode" = "Chinese Input Mode"; "Please enter the client app bundle identifier(s) you want to register." = "Please enter the client app bundle identifier(s) you want to register."; @@ -51,10 +52,9 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude."; "Edit Phrase Replacement Table…" = "Edit Phrase Replacement Table…"; "Use Phrase Replacement" = "Use Phrase Replacement"; -"Candidate keys can only contain ASCII characters like alphanumericals." = "Candidate keys can only contain ASCII characters like alphanumericals."; +"Candidate keys can only contain printable ASCII characters like alphanumericals." = "Candidate keys can only contain printable ASCII characters like alphanumericals."; "Candidate keys cannot contain space." = "Candidate keys cannot contain space."; -"There should not be duplicated keys." = "There should not be duplicated keys."; -"Please specify at least 6 candidate keys." = "Please specify at least 6 candidate keys."; +"Minimum 6 candidate keys allowed." = "Minimum 6 candidate keys allowed."; "Maximum 9 candidate keys allowed." = "Maximum 9 candidate keys allowed."; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ Phrase replacement mode enabled, interfering user phrase entry."; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match."; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index d5db373f..6c44486b 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -1,4 +1,5 @@ "vChewing" = "威注音入力アプリ"; +"Invalid Selection Keys." = "候補用キーによる不具合。"; "Alphanumerical Input Mode" = "英數入力モード"; "Chinese Input Mode" = "漢語入力モード"; "Please enter the client app bundle identifier(s) you want to register." = "登録したい客体アプリの唯一識別子(Bundle Identifier)をご入力ください。"; @@ -51,10 +52,9 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」は既存語彙:\n ENTER で最優先にし、SHIFT+COMMAND+ENTER で優先順位を下げる;\n BackSpace 或いは Delete で排除。"; "Edit Phrase Replacement Table…" = "言葉置換表を編集…"; "Use Phrase Replacement" = "言葉置換機能"; -"Candidate keys can only contain ASCII characters like alphanumericals." = "言選り用キー陣列にはASCII文字だけをご登録ください(英数など)。"; +"Candidate keys can only contain printable ASCII characters like alphanumericals." = "言選り用キー陣列にはプリントできるASCII文字だけをご登録ください(英数など)。"; "Candidate keys cannot contain space." = "言選り用キー陣列にスペースキーは登録できません。"; -"There should not be duplicated keys." = "言選り用キー陣列に同じキーの重複登録はできません。"; -"Please specify at least 6 candidate keys." = "言選り用キー陣列に少なくとも6つのキーをご登録ください。"; +"Minimum 6 candidate keys allowed." = "言選り用キー陣列に少なくとも6つのキーをご登録ください。"; "Maximum 9 candidate keys allowed." = "言選り用キー陣列には最多9つキー登録できます。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 言葉置換機能稼働中、新添付言葉にも影響。"; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ 対処不可:緩衝列の字数は読みの数と不同等。"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 4b028adc..013c2a2b 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -1,4 +1,5 @@ "vChewing" = "威注音输入法"; +"Invalid Selection Keys." = "选字键参数资料值不规范。"; "Alphanumerical Input Mode" = "英数输入模式"; "Chinese Input Mode" = "中文输入模式"; "Please enter the client app bundle identifier(s) you want to register." = "请键入您要登记的客体应用的唯一标帜(Bundle Identifier)。"; @@ -51,10 +52,9 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」已存在:\n 敲 Enter 以升权、敲 Shift+Command+Enter 以降权;\n 敲 BackSpace 或 Delete 以排除。"; "Edit Phrase Replacement Table…" = "编辑语汇置换表…"; "Use Phrase Replacement" = "使用语汇置换"; -"Candidate keys can only contain ASCII characters like alphanumericals." = "选字键只能是英数等 ASCII 字符。"; +"Candidate keys can only contain printable ASCII characters like alphanumericals." = "选字键只能是英数等 ASCII 可列印字符。"; "Candidate keys cannot contain space." = "选字键不得包含空格。"; -"There should not be duplicated keys." = "选字键不得重复。"; -"Please specify at least 6 candidate keys." = "请至少指定 6 个选字键。"; +"Minimum 6 candidate keys allowed." = "请至少指定 6 个选字键。"; "Maximum 9 candidate keys allowed." = "选字键最多只能指定 9 个。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 语汇置换功能已启用,会波及语汇自订。"; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ 无法处理:组字区字数与读音数不对应。"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index 8223fdc6..614d4074 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -1,4 +1,5 @@ "vChewing" = "威注音輸入法"; +"Invalid Selection Keys." = "選字鍵參數資料值不規範。"; "Alphanumerical Input Mode" = "英數輸入模式"; "Chinese Input Mode" = "中文輸入模式"; "Please enter the client app bundle identifier(s) you want to register." = "請鍵入您要登記的客體應用的唯一標幟(Bundle Identifier)。"; @@ -51,10 +52,9 @@ "\"%@\" already exists:\n ENTER to boost, SHIFT+COMMAND+ENTER to nerf, \n BackSpace or Delete key to exclude." = "「%@」已存在:\n 敲 Enter 以升權、敲 Shift+Command+Enter 以降權;\n 敲 BackSpace 或 Delete 以排除。"; "Edit Phrase Replacement Table…" = "編輯語彙置換表…"; "Use Phrase Replacement" = "使用語彙置換"; -"Candidate keys can only contain ASCII characters like alphanumericals." = "選字鍵只能是英數等 ASCII 字符。"; +"Candidate keys can only contain printable ASCII characters like alphanumericals." = "選字鍵只能是英數等 ASCII 可列印字符。"; "Candidate keys cannot contain space." = "選字鍵不得包含空格。"; -"There should not be duplicated keys." = "選字鍵不得重複。"; -"Please specify at least 6 candidate keys." = "請至少指定 6 個選字鍵。"; +"Minimum 6 candidate keys allowed." = "請至少指定 6 個選字鍵。"; "Maximum 9 candidate keys allowed." = "選字鍵最多只能指定 9 個。"; "⚠︎ Phrase replacement mode enabled, interfering user phrase entry." = "⚠︎ 語彙置換功能已啟用,會波及語彙自訂。"; "⚠︎ Unhandlable: Chars and Readings in buffer doesn't match." = "⚠︎ 無法處理:組字區字數與讀音數不對應。";