diff --git a/Packages/vChewing_PhraseEditorUI/Package.swift b/Packages/vChewing_PhraseEditorUI/Package.swift new file mode 100644 index 00000000..c76458ae --- /dev/null +++ b/Packages/vChewing_PhraseEditorUI/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.3 +import PackageDescription + +let package = Package( + name: "PhraseEditorUI", + platforms: [ + .macOS(.v10_11) + ], + products: [ + .library( + name: "PhraseEditorUI", + targets: ["PhraseEditorUI"] + ) + ], + dependencies: [ + .package(path: "../vChewing_LangModelAssembly"), + .package(path: "../vChewing_Shared"), + .package(path: "../ShapsBenkau_SwiftUIBackports"), + ], + targets: [ + .target( + name: "PhraseEditorUI", + dependencies: [ + .product(name: "LangModelAssembly", package: "vChewing_LangModelAssembly"), + .product(name: "SwiftUIBackports", package: "ShapsBenkau_SwiftUIBackports"), + .product(name: "Shared", package: "vChewing_Shared"), + ] + ) + ] +) diff --git a/Packages/vChewing_PhraseEditorUI/README.md b/Packages/vChewing_PhraseEditorUI/README.md new file mode 100644 index 00000000..6b242fa6 --- /dev/null +++ b/Packages/vChewing_PhraseEditorUI/README.md @@ -0,0 +1,13 @@ +# PhraseEditorUI + +威注音語彙編輯器。 + +``` +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. +``` diff --git a/Packages/vChewing_PhraseEditorUI/Sources/PhraseEditorUI/PhraseEditorDelegate.swift b/Packages/vChewing_PhraseEditorUI/Sources/PhraseEditorUI/PhraseEditorDelegate.swift new file mode 100644 index 00000000..2e75c5ab --- /dev/null +++ b/Packages/vChewing_PhraseEditorUI/Sources/PhraseEditorUI/PhraseEditorDelegate.swift @@ -0,0 +1,21 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. + +import Foundation +import LangModelAssembly +import Shared + +public protocol PhraseEditorDelegate { + var currentInputMode: Shared.InputMode { get } + func retrieveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType) -> String + @discardableResult func saveData(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, data: String) + -> String + func checkIfUserPhraseExist(userPhrase: String, mode: Shared.InputMode, key unigramKey: String) -> Bool + func consolidate(text strProcessed: inout String, pragma shouldCheckPragma: Bool) + func openPhraseFile(mode: Shared.InputMode, type: vChewingLM.ReplacableUserDataType, app: String) +} diff --git a/Packages/vChewing_PhraseEditorUI/Sources/PhraseEditorUI/PhraseEditorUI.swift b/Packages/vChewing_PhraseEditorUI/Sources/PhraseEditorUI/PhraseEditorUI.swift new file mode 100644 index 00000000..a8d10e69 --- /dev/null +++ b/Packages/vChewing_PhraseEditorUI/Sources/PhraseEditorUI/PhraseEditorUI.swift @@ -0,0 +1,345 @@ +// (c) 2021 and onwards The vChewing Project (MIT-NTL License). +// ==================== +// This code is released under the MIT license (SPDX-License-Identifier: MIT) +// ... with NTL restriction stating that: +// No trademark license is granted to use the trade names, trademarks, service +// marks, or product names of Contributor, except as required to fulfill notice +// requirements defined in MIT License. + +import Combine +import Foundation +import LangModelAssembly +import Shared +import SwiftExtension +import SwiftUI +import SwiftUIBackports + +private let loc: String = + (UserDefaults.standard.array(forKey: UserDef.kAppleLanguages.rawValue) as? [String] ?? ["auto"])[0] + +@available(macOS 10.15, *) +extension VwrPhraseEditorUI { + @Backport.AppStorage("PhraseEditorAutoReloadExternalModifications") + private static var autoReloadExternalModifications: Bool = true +} + +@available(macOS 10.15, *) +public struct VwrPhraseEditorUI: View { + static var txtContentStorage: String = NSLocalizedString( + "Please select Simplified / Traditional Chinese mode above.", comment: "" + ) + @Binding public var txtContent: String + @ObservedObject public var fileChangeIndicator = FileObserveProject.shared + @State private var selAutoReloadExternalModifications: Bool = UserDefaults.standard.bool( + forKey: UserDef.kPhraseEditorAutoReloadExternalModifications.rawValue) + @State var lblAddPhraseTag1 = UITerms.AddPhrases.locPhrase.localized.0 + @State var lblAddPhraseTag2 = UITerms.AddPhrases.locReadingOrStroke.localized.0 + @State var lblAddPhraseTag3 = UITerms.AddPhrases.locWeight.localized.0 + @State var lblAddPhraseTag4 = UITerms.AddPhrases.locComment.localized.0 + @State var txtAddPhraseField1 = "" + @State var txtAddPhraseField2 = "" + @State var txtAddPhraseField3 = "" + @State var txtAddPhraseField4 = "" + @State public var selInputMode: Shared.InputMode = .imeModeNULL + @State public var selUserDataType: vChewingLM.ReplacableUserDataType = .thePhrases + @State private var isLoading = false + @State private var isSaved = false + @State private var redrawTrigger = false + + public var currentIMEInputMode: Shared.InputMode { + delegate?.currentInputMode ?? selInputMode + } + + public var delegate: PhraseEditorDelegate? { + didSet { + guard let delegate = delegate else { return } + selInputMode = delegate.currentInputMode + update() + } + } + + // MARK: - + + public init(delegate theDelegate: PhraseEditorDelegate? = nil) { + _txtContent = .init( + get: { Self.txtContentStorage }, + set: { newValue, _ in + Self.txtContentStorage.removeAll() + Self.txtContentStorage.append(newValue) + } + ) + guard let theDelegate = theDelegate else { return } + defer { delegate = theDelegate } + } + + public func update() { + guard let delegate = delegate else { return } + updateLabels() + clearAllFields() + isLoading = true + txtContent = NSLocalizedString("Loading…", comment: "") + redrawTrigger.toggle() + DispatchQueue.main.async { + txtContent = delegate.retrieveData(mode: selInputMode, type: selUserDataType) + redrawTrigger.toggle() + isSaved = true + isLoading = false + } + } + + private func updateLabels() { + clearAllFields() + switch selUserDataType { + case .thePhrases: + lblAddPhraseTag1 = UITerms.AddPhrases.locPhrase.localized.0 + lblAddPhraseTag2 = UITerms.AddPhrases.locReadingOrStroke.localized.0 + lblAddPhraseTag3 = UITerms.AddPhrases.locWeight.localized.0 + lblAddPhraseTag4 = UITerms.AddPhrases.locComment.localized.0 + case .theFilter: + lblAddPhraseTag1 = UITerms.AddPhrases.locPhrase.localized.0 + lblAddPhraseTag2 = UITerms.AddPhrases.locReadingOrStroke.localized.0 + lblAddPhraseTag3 = "" + lblAddPhraseTag4 = UITerms.AddPhrases.locComment.localized.0 + case .theReplacements: + lblAddPhraseTag1 = UITerms.AddPhrases.locReplaceTo.localized.0 + lblAddPhraseTag2 = UITerms.AddPhrases.locReplaceTo.localized.1 + lblAddPhraseTag3 = "" + lblAddPhraseTag4 = UITerms.AddPhrases.locComment.localized.0 + case .theAssociates: + lblAddPhraseTag1 = UITerms.AddPhrases.locInitial.localized.0 + lblAddPhraseTag2 = { + let result = UITerms.AddPhrases.locPhrase.localized.0 + return (result == "Phrase") ? "Phrases" : result + }() + lblAddPhraseTag3 = "" + lblAddPhraseTag4 = "" + case .theSymbols: + lblAddPhraseTag1 = UITerms.AddPhrases.locPhrase.localized.0 + lblAddPhraseTag2 = UITerms.AddPhrases.locReadingOrStroke.localized.0 + lblAddPhraseTag3 = "" + lblAddPhraseTag4 = UITerms.AddPhrases.locComment.localized.0 + } + } + + private func insertEntry() { + txtAddPhraseField1.removeAll { "  \t\n\r".contains($0) } + if selUserDataType != .theAssociates { + txtAddPhraseField2.regReplace(pattern: #"( +| +| +|\t+)+"#, replaceWith: "-") + } + txtAddPhraseField2.removeAll { + selUserDataType == .theAssociates ? "\n\r".contains($0) : "  \t\n\r".contains($0) + } + txtAddPhraseField3.removeAll { !"0123456789.-".contains($0) } + txtAddPhraseField4.removeAll { "\n\r".contains($0) } + guard !txtAddPhraseField1.isEmpty, !txtAddPhraseField2.isEmpty else { return } + var arrResult: [String] = [txtAddPhraseField1, txtAddPhraseField2] + if let weightVal = Double(txtAddPhraseField3), weightVal < 0 { + arrResult.append(weightVal.description) + } + if !txtAddPhraseField4.isEmpty { arrResult.append("#" + txtAddPhraseField4) } + if let delegate = delegate, + delegate.checkIfUserPhraseExist( + userPhrase: txtAddPhraseField1, mode: selInputMode, key: txtAddPhraseField2 + ) + { + arrResult.append("\t#𝙾𝚟𝚎𝚛𝚛𝚒𝚍𝚎") + } + if let lastChar = txtContent.last, !"\n".contains(lastChar) { + arrResult.insert("\n", at: 0) + } + txtContent.append(arrResult.joined(separator: " ") + "\n") + isSaved = false + clearAllFields() + } + + private func clearAllFields() { + txtAddPhraseField1 = "" + txtAddPhraseField2 = "" + txtAddPhraseField3 = "" + txtAddPhraseField4 = "" + } + + private func dropDownMenuDidChange() { + update() + } + + private func saveAndReload() { + guard let delegate = delegate, selInputMode != .imeModeNULL, !isSaved else { return } + let toSave = txtContent + isLoading = true + txtContent = NSLocalizedString("Loading…", comment: "") + redrawTrigger.toggle() + let newResult = delegate.saveData(mode: selInputMode, type: selUserDataType, data: toSave) + txtContent = newResult + redrawTrigger.toggle() + isLoading = false + isSaved = true + } + + private func consolidate() { + guard let delegate = delegate, selInputMode != .imeModeNULL else { return } + DispatchQueue.main.async { + isLoading = true + delegate.consolidate(text: &txtContent, pragma: false) // 強制整理 + isLoading = false + isSaved = false + } + } + + private func callExternalAppToOpenPhraseFile() { + delegate?.openPhraseFile(mode: selInputMode, type: selUserDataType, app: "Finder") + } + + // MARK: - Main View. + + public var body: some View { + VStack(spacing: 4) { + HStack { + Picker("", selection: $selInputMode.onChange { dropDownMenuDidChange() }) { + switch currentIMEInputMode { + case .imeModeCHS: + Text(Shared.InputMode.imeModeCHS.localizedDescription).tag(Shared.InputMode.imeModeCHS) + Text(Shared.InputMode.imeModeCHT.localizedDescription).tag(Shared.InputMode.imeModeCHT) + case .imeModeCHT: + Text(Shared.InputMode.imeModeCHT.localizedDescription).tag(Shared.InputMode.imeModeCHT) + Text(Shared.InputMode.imeModeCHS.localizedDescription).tag(Shared.InputMode.imeModeCHS) + case .imeModeNULL: + Text(Shared.InputMode.imeModeNULL.localizedDescription).tag(Shared.InputMode.imeModeNULL) + if loc.contains("Hans") { + Text(Shared.InputMode.imeModeCHS.localizedDescription).tag(Shared.InputMode.imeModeCHS) + Text(Shared.InputMode.imeModeCHT.localizedDescription).tag(Shared.InputMode.imeModeCHT) + } else { + Text(Shared.InputMode.imeModeCHT.localizedDescription).tag(Shared.InputMode.imeModeCHT) + Text(Shared.InputMode.imeModeCHS.localizedDescription).tag(Shared.InputMode.imeModeCHS) + } + } + } + .labelsHidden() + Picker("", selection: $selUserDataType.onChange { dropDownMenuDidChange() }) { + Text(vChewingLM.ReplacableUserDataType.thePhrases.localizedDescription).tag( + vChewingLM.ReplacableUserDataType.thePhrases) + Text(vChewingLM.ReplacableUserDataType.theFilter.localizedDescription).tag( + vChewingLM.ReplacableUserDataType.theFilter) + Text(vChewingLM.ReplacableUserDataType.theReplacements.localizedDescription).tag( + vChewingLM.ReplacableUserDataType.theReplacements) + Text(vChewingLM.ReplacableUserDataType.theAssociates.localizedDescription).tag( + vChewingLM.ReplacableUserDataType.theAssociates) + Text(vChewingLM.ReplacableUserDataType.theSymbols.localizedDescription).tag( + vChewingLM.ReplacableUserDataType.theSymbols) + } + .labelsHidden() + Button("Reload") { + DispatchQueue.main.async { update() } + }.disabled(selInputMode == .imeModeNULL || isLoading) + Button("Consolidate") { + consolidate() + }.disabled(selInputMode == .imeModeNULL || isLoading) + if #available(macOS 11.0, *) { + Button("Save") { + DispatchQueue.main.async { saveAndReload() } + }.keyboardShortcut("s", modifiers: [.command]) + .disabled(isSaved || delegate == nil) + } else { + Button("Save") { + DispatchQueue.main.async { saveAndReload() } + }.disabled(isSaved || delegate == nil) + } + Button("...") { + DispatchQueue.main.async { + saveAndReload() + callExternalAppToOpenPhraseFile() + } + } + } + + TextEditorEX(text: $txtContent.onChange { isSaved = false }) + .disabled(selInputMode == .imeModeNULL || isLoading) + .frame(minWidth: 320, minHeight: 240) + .backport.onChange(of: fileChangeIndicator.id) { _ in + if Self.autoReloadExternalModifications { update() } + } + + VStack(spacing: 4) { + if selUserDataType != .theAssociates { + HStack { + TextField(lblAddPhraseTag4, text: $txtAddPhraseField4) + } + } + HStack { + TextField(lblAddPhraseTag1, text: $txtAddPhraseField1) + TextField(lblAddPhraseTag2, text: $txtAddPhraseField2) + if selUserDataType == .thePhrases { + TextField( + lblAddPhraseTag3, + text: $txtAddPhraseField3.onChange { + guard let weightVal = Double(txtAddPhraseField3) else { return } + if weightVal > 0 { txtAddPhraseField3 = "" } + } + ) + } + Button(UITerms.AddPhrases.locAdd.localized.0) { + DispatchQueue.main.async { insertEntry() } + }.disabled(txtAddPhraseField1.isEmpty || txtAddPhraseField2.isEmpty) + } + }.disabled(selInputMode == Shared.InputMode.imeModeNULL || isLoading) + HStack { + if #available(macOS 12, *) { + Toggle( + LocalizedStringKey("This editor only: Auto-reload modifications happened outside of this editor"), + isOn: $selAutoReloadExternalModifications.onChange { + Self.autoReloadExternalModifications = selAutoReloadExternalModifications + } + ) + .controlSize(.small) + } else { + Text("Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations.") + .font(.system(size: 11.0)).foregroundColor(.secondary) + } + Spacer() + } + }.onDisappear { + selInputMode = .imeModeNULL + selUserDataType = .thePhrases + txtContent = NSLocalizedString("Please select Simplified / Traditional Chinese mode above.", comment: "") + redrawTrigger.toggle() + }.onAppear { + guard let delegate = delegate else { return } + selInputMode = delegate.currentInputMode + update() + } + } +} + +@available(macOS 10.15, *) +struct ContentView_Previews: PreviewProvider { + static var previews: some View { + VwrPhraseEditorUI() + } +} + +extension vChewingLM.ReplacableUserDataType { + public var localizedDescription: String { NSLocalizedString(rawValue, comment: "") } +} + +private enum UITerms { + fileprivate enum AddPhrases: String { + case locPhrase = "Phrase" + case locReadingOrStroke = "Reading/Stroke" + case locWeight = "Weight" + case locComment = "Comment" + case locReplaceTo = "Replace to" + case locAdd = "Add" + case locInitial = "Initial" + + var localized: (String, String) { + if self == .locAdd { + return loc.contains("zh") ? ("添入", "") : loc.contains("ja") ? ("記入", "") : ("Add", "") + } + let rawArray = NSLocalizedString(self.rawValue, comment: "").components(separatedBy: " ") + if rawArray.isEmpty { return ("N/A", "N/A") } + let val1: String = rawArray[0] + let val2: String = (rawArray.count >= 2) ? rawArray[1] : "" + return (val1, val2) + } + } +} diff --git a/Source/Resources/Base.lproj/Localizable.strings b/Source/Resources/Base.lproj/Localizable.strings index 435eaa5d..5d4e8851 100644 --- a/Source/Resources/Base.lproj/Localizable.strings +++ b/Source/Resources/Base.lproj/Localizable.strings @@ -1,4 +1,24 @@ "vChewing" = "vChewing"; +"Initial" = "Initial"; +"Phrase" = "Phrase"; +"Reading/Stroke" = "Reading/Stroke"; +"Weight" = "Weight"; +"Comment" = "Comment"; +"Replace to" = "Replace to"; +"Save" = "Save"; +"Phrase Editor" = "Phrase Editor"; +"thePhrases" = "Phrases"; +"theFilter" = "Filter"; +"theReplacements" = "Replacements"; +"theAssociates" = "Associates"; +"theSymbols" = "Symbols"; +"Please select…" = "Please select…"; +"Loading…" = "Loading…"; +"Consolidate" = "Consolidate"; +"Reload" = "Reload"; +"Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations." = "Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations."; +"This editor only: Auto-reload modifications happened outside of this editor" = "This editor only: Auto-reload modifications happened outside of this editor"; +"Please select Simplified / Traditional Chinese mode above." = "Please select Simplified / Traditional Chinese mode above."; "⚠︎ Failed from boosting a candidate." = "⚠︎ Failed from boosting a candidate."; "⚠︎ Failed from nerfing a candidate." = "⚠︎ Failed from nerfing a candidate."; "⚠︎ Failed from filtering a candidate." = "⚠︎ Failed from filtering a candidate."; diff --git a/Source/Resources/en.lproj/Localizable.strings b/Source/Resources/en.lproj/Localizable.strings index 435eaa5d..5d4e8851 100644 --- a/Source/Resources/en.lproj/Localizable.strings +++ b/Source/Resources/en.lproj/Localizable.strings @@ -1,4 +1,24 @@ "vChewing" = "vChewing"; +"Initial" = "Initial"; +"Phrase" = "Phrase"; +"Reading/Stroke" = "Reading/Stroke"; +"Weight" = "Weight"; +"Comment" = "Comment"; +"Replace to" = "Replace to"; +"Save" = "Save"; +"Phrase Editor" = "Phrase Editor"; +"thePhrases" = "Phrases"; +"theFilter" = "Filter"; +"theReplacements" = "Replacements"; +"theAssociates" = "Associates"; +"theSymbols" = "Symbols"; +"Please select…" = "Please select…"; +"Loading…" = "Loading…"; +"Consolidate" = "Consolidate"; +"Reload" = "Reload"; +"Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations." = "Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations."; +"This editor only: Auto-reload modifications happened outside of this editor" = "This editor only: Auto-reload modifications happened outside of this editor"; +"Please select Simplified / Traditional Chinese mode above." = "Please select Simplified / Traditional Chinese mode above."; "⚠︎ Failed from boosting a candidate." = "⚠︎ Failed from boosting a candidate."; "⚠︎ Failed from nerfing a candidate." = "⚠︎ Failed from nerfing a candidate."; "⚠︎ Failed from filtering a candidate." = "⚠︎ Failed from filtering a candidate."; diff --git a/Source/Resources/ja.lproj/Localizable.strings b/Source/Resources/ja.lproj/Localizable.strings index 531c0ece..fa745e46 100644 --- a/Source/Resources/ja.lproj/Localizable.strings +++ b/Source/Resources/ja.lproj/Localizable.strings @@ -1,4 +1,24 @@ "vChewing" = "威注音入力アプリ"; +"Initial" = "頭文字"; +"Phrase" = "語彙"; +"Reading/Stroke" = "音読/筆画"; +"Weight" = "優先度"; +"Comment" = "メモ"; +"Replace to" = "単語 →"; +"Save" = "セーブ"; +"Phrase Editor" = "辞書編集"; +"thePhrases" = "ユーザー辞書"; +"theFilter" = "排除リスト"; +"theReplacements" = "言葉置換"; +"theAssociates" = "連想語彙"; +"theSymbols" = "絵文字&符号"; +"Please select…" = "お選びを…"; +"Loading…" = "読み込む中…"; +"Consolidate" = "整理"; +"Reload" = "再読込"; +"Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations." = "システム API 制限のため、一部の機能は macOS 10.15 と macOS 11 で提供できません。"; +"This editor only: Auto-reload modifications happened outside of this editor" = "このエディターの外部からの編集結果をこのエディターに自動的に読み込む"; +"Please select Simplified / Traditional Chinese mode above." = "まずは上記のメニューで簡体/繁体中国語モードをお選びください。"; "⚠︎ Failed from boosting a candidate." = "⚠︎ 指定された文字候補の順位は最優先にできませんでした。"; "⚠︎ Failed from nerfing a candidate." = "⚠︎ 指定された文字候補の優先順位を下げることに失敗ししました。"; "⚠︎ Failed from filtering a candidate." = "⚠︎ 指定された文字候補は排除表に登録できませんでした。"; diff --git a/Source/Resources/zh-Hans.lproj/Localizable.strings b/Source/Resources/zh-Hans.lproj/Localizable.strings index 2843f673..f0962e14 100644 --- a/Source/Resources/zh-Hans.lproj/Localizable.strings +++ b/Source/Resources/zh-Hans.lproj/Localizable.strings @@ -1,4 +1,24 @@ "vChewing" = "威注音输入法"; +"Initial" = "首字"; +"Phrase" = "词语"; +"Reading/Stroke" = "读音/字根"; +"Weight" = "权重"; +"Comment" = "注释"; +"Replace to" = "置换 为"; +"Save" = "存档"; +"Phrase Editor" = "语汇编辑器"; +"thePhrases" = "自订语汇"; +"theFilter" = "滤除清单"; +"theReplacements" = "语汇置换"; +"theAssociates" = "联想字词"; +"theSymbols" = "绘文字符号"; +"Please select…" = "请选择…"; +"Loading…" = "正在载入…"; +"Consolidate" = "整理"; +"Reload" = "重新载入"; +"Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations." = "因系统 API 限制,个别功能无法对 macOS 10.15 和 macOS 11 提供。"; +"This editor only: Auto-reload modifications happened outside of this editor" = "仅限该编辑器:自动读入来自该编辑器外部的档案内容修改"; +"Please select Simplified / Traditional Chinese mode above." = "请先借由上方选单选择简繁模式。"; "⚠︎ Failed from boosting a candidate." = "⚠︎ 候选字词提权失败。"; "⚠︎ Failed from nerfing a candidate." = "⚠︎ 候选字词降权失败。"; "⚠︎ Failed from filtering a candidate." = "⚠︎ 候选字词滤除失败。"; diff --git a/Source/Resources/zh-Hant.lproj/Localizable.strings b/Source/Resources/zh-Hant.lproj/Localizable.strings index 6de4d400..6ccd5020 100644 --- a/Source/Resources/zh-Hant.lproj/Localizable.strings +++ b/Source/Resources/zh-Hant.lproj/Localizable.strings @@ -1,4 +1,24 @@ "vChewing" = "威注音輸入法"; +"Initial" = "首字"; +"Phrase" = "詞語"; +"Reading/Stroke" = "讀音/字根"; +"Weight" = "權重"; +"Comment" = "註釋"; +"Replace to" = "置換 為"; +"Save" = "存檔"; +"Phrase Editor" = "語彙編輯器"; +"thePhrases" = "自訂語彙"; +"theFilter" = "濾除清單"; +"theReplacements" = "語彙置換"; +"theAssociates" = "聯想字詞"; +"theSymbols" = "繪文字符號"; +"Please select…" = "請選擇…"; +"Loading…" = "正在載入…"; +"Consolidate" = "整理"; +"Reload" = "重新載入"; +"Some features are unavailable for macOS 10.15 and macOS 11 due to API limitations." = "因系統 API 限制,個別功能無法對 macOS 10.15 和 macOS 11 提供。"; +"This editor only: Auto-reload modifications happened outside of this editor" = "僅限該編輯器:自動讀入來自該編輯器外部的檔案內容修改"; +"Please select Simplified / Traditional Chinese mode above." = "請先藉由上方選單選擇繁簡模式。"; "⚠︎ Failed from boosting a candidate." = "⚠︎ 候選字詞提權失敗。"; "⚠︎ Failed from nerfing a candidate." = "⚠︎ 候選字詞降權失敗。"; "⚠︎ Failed from filtering a candidate." = "⚠︎ 候選字詞濾除失敗。"; diff --git a/vChewing.xcodeproj/project.pbxproj b/vChewing.xcodeproj/project.pbxproj index 833f0fa7..b17012a4 100644 --- a/vChewing.xcodeproj/project.pbxproj +++ b/vChewing.xcodeproj/project.pbxproj @@ -29,6 +29,7 @@ 5B660A8628F64A8800E5E4F6 /* SymbolMenuDefaultData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B660A8528F64A8800E5E4F6 /* SymbolMenuDefaultData.swift */; }; 5B6C141228A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */; }; 5B73FB5E27B2BE1300E9BF49 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5B73FB6027B2BE1300E9BF49 /* InfoPlist.strings */; }; + 5B765F09293A253C00122315 /* PhraseEditorUI in Frameworks */ = {isa = PBXBuildFile; productRef = 5B765F08293A253C00122315 /* PhraseEditorUI */; }; 5B782EC4280C243C007276DE /* InputHandler_HandleCandidate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B782EC3280C243C007276DE /* InputHandler_HandleCandidate.swift */; }; 5B78EE0D28A562B4009456C1 /* VwrPrefPaneDevZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B78EE0C28A562B4009456C1 /* VwrPrefPaneDevZone.swift */; }; 5B7BC4B027AFFBE800F66C24 /* frmPrefWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5B7BC4AE27AFFBE800F66C24 /* frmPrefWindow.xib */; }; @@ -236,6 +237,7 @@ 5B6C141128A9D4B30098ADF8 /* SessionCtl_HandleEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCtl_HandleEvent.swift; sourceTree = ""; }; 5B73FB5427B2BD6900E9BF49 /* PhraseEditor-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "PhraseEditor-Info.plist"; path = "UserPhraseEditor/PhraseEditor-Info.plist"; sourceTree = SOURCE_ROOT; }; 5B73FB5F27B2BE1300E9BF49 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + 5B765F07293A250000122315 /* vChewing_PhraseEditorUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = vChewing_PhraseEditorUI; path = Packages/vChewing_PhraseEditorUI; sourceTree = ""; }; 5B782EC3280C243C007276DE /* InputHandler_HandleCandidate.swift */ = {isa = PBXFileReference; explicitFileType = sourcecode.swift; fileEncoding = 4; indentWidth = 2; lineEnding = 0; path = InputHandler_HandleCandidate.swift; sourceTree = ""; tabWidth = 2; usesTabs = 0; }; 5B78EE0C28A562B4009456C1 /* VwrPrefPaneDevZone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VwrPrefPaneDevZone.swift; sourceTree = ""; }; 5B7BC4AF27AFFBE800F66C24 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/frmPrefWindow.xib; sourceTree = ""; }; @@ -385,6 +387,7 @@ 5B98114828D6198700CBC605 /* PinyinPhonaConverter in Frameworks */, 5BFC63CF28D4ACA3004A77B7 /* LangModelAssembly in Frameworks */, 5BC5E02128DDEFE00094E427 /* TooltipUI in Frameworks */, + 5B765F09293A253C00122315 /* PhraseEditorUI in Frameworks */, 5BDB7A3928D4824A001AC277 /* BookmarkManager in Frameworks */, 5B5C8ED828FC0EA9002C93A5 /* SSPreferences in Frameworks */, 5B5A603028E81CC50001AE8D /* SwiftUIBackports in Frameworks */, @@ -622,6 +625,7 @@ 5BFC63CD28D4AC98004A77B7 /* vChewing_LangModelAssembly */, 5BDB7A3328D47587001AC277 /* vChewing_Megrez */, 5BC5E01C28DDE4270094E427 /* vChewing_NotifierUI */, + 5B765F07293A250000122315 /* vChewing_PhraseEditorUI */, 5B98114628D6198000CBC605 /* vChewing_PinyinPhonaConverter */, 5BC5E02228DE07250094E427 /* vChewing_PopupCompositionBuffer */, 5B963C9E28D5C14600DCEE88 /* vChewing_Shared */, @@ -864,6 +868,7 @@ 5BA8C30228DF0360004C5CC4 /* CandidateWindow */, 5B5A602F28E81CC50001AE8D /* SwiftUIBackports */, 5B5C8ED728FC0EA9002C93A5 /* SSPreferences */, + 5B765F08293A253C00122315 /* PhraseEditorUI */, ); productName = vChewing; productReference = 6A0D4EA215FC0D2D00ABF4B3 /* vChewing.app */; @@ -2034,6 +2039,10 @@ isa = XCSwiftPackageProductDependency; productName = SSPreferences; }; + 5B765F08293A253C00122315 /* PhraseEditorUI */ = { + isa = XCSwiftPackageProductDependency; + productName = PhraseEditorUI; + }; 5B963C9C28D5BFB800DCEE88 /* CocoaExtension */ = { isa = XCSwiftPackageProductDependency; productName = CocoaExtension;