diff --git a/McBopomofo.xcodeproj/project.pbxproj b/McBopomofo.xcodeproj/project.pbxproj index c2f91888..bda66af3 100644 --- a/McBopomofo.xcodeproj/project.pbxproj +++ b/McBopomofo.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ D427F7B4279086DC004A2160 /* InputSourceHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7B3279086DC004A2160 /* InputSourceHelper */; }; D427F7B6279086F6004A2160 /* InputSourceHelper in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7B5279086F6004A2160 /* InputSourceHelper */; }; D427F7C127908EFC004A2160 /* OpenCCBridge in Frameworks */ = {isa = PBXBuildFile; productRef = D427F7C027908EFC004A2160 /* OpenCCBridge */; }; + D43FC40B2B23788400ED5A1C /* InputMacro.swift in Sources */ = {isa = PBXBuildFile; fileRef = D43FC40A2B23788400ED5A1C /* InputMacro.swift */; }; D44FB74527915565003C80A6 /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74427915555003C80A6 /* Preferences.swift */; }; D44FB74A2791B829003C80A6 /* VXHanConvert in Frameworks */ = {isa = PBXBuildFile; productRef = D44FB7492791B829003C80A6 /* VXHanConvert */; }; D44FB74D2792189A003C80A6 /* PhraseReplacementMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */; }; @@ -167,6 +168,7 @@ D427F7AC27907B7E004A2160 /* NotifierUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = NotifierUI; path = Packages/NotifierUI; sourceTree = ""; }; D427F7B2279086B5004A2160 /* InputSourceHelper */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = InputSourceHelper; path = Packages/InputSourceHelper; sourceTree = ""; }; D427F7BF27908EAC004A2160 /* OpenCCBridge */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = OpenCCBridge; path = Packages/OpenCCBridge; sourceTree = ""; }; + D43FC40A2B23788400ED5A1C /* InputMacro.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InputMacro.swift; sourceTree = ""; }; D44FB74427915555003C80A6 /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; D44FB7482791B346003C80A6 /* VXHanConvert */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = VXHanConvert; path = Packages/VXHanConvert; sourceTree = ""; }; D44FB74B2792189A003C80A6 /* PhraseReplacementMap.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PhraseReplacementMap.cpp; sourceTree = ""; }; @@ -278,6 +280,7 @@ D4E569DB27A34CC100AC2CEF /* KeyHandler.mm */, 6AE215102A2849BB005A6A02 /* UTF8Helper.cpp */, 6AE2150F2A2849BB005A6A02 /* UTF8Helper.h */, + D43FC40A2B23788400ED5A1C /* InputMacro.swift */, D456576D279E4F7B00DF6BC9 /* KeyHandlerInput.swift */, D461B791279DAC010070E734 /* InputState.swift */, D427F76B278CA1BA004A2160 /* AppDelegate.swift */, @@ -671,6 +674,7 @@ 6A0D4F4515FC0EB100ABF4B3 /* Mandarin.cpp in Sources */, 6ACC3D452793701600F1B140 /* ParselessLM.cpp in Sources */, D41355DE278EA3ED005E5CBD /* UserPhrasesLM.cpp in Sources */, + D43FC40B2B23788400ED5A1C /* InputMacro.swift in Sources */, 6AE215112A2849BB005A6A02 /* UTF8Helper.cpp in Sources */, 6ACC3D3F27914F2400F1B140 /* KeyValueBlobReader.cpp in Sources */, B058C5272AC9DF51002EDD66 /* ServiceProvider.swift in Sources */, diff --git a/Source/Data/Macros.txt b/Source/Data/Macros.txt new file mode 100644 index 00000000..3c296164 --- /dev/null +++ b/Source/Data/Macros.txt @@ -0,0 +1,39 @@ +今天日期 ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TODAY_SHORT ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TODAY_MEDIUM ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TODAY_MEDIUM_ROC ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TODAY_MEDIUM_CHINESE ㄐㄧㄣ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +昨天日期 ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_YESTERDAY_SHORT ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_YESTERDAY_MEDIUM ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_YESTERDAY_MEDIUM_ROC ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_YESTERDAY_MEDIUM_CHINESE ㄗㄨㄛˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +明天日期 ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TOMORROW_SHORT ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TOMORROW_MEDIUM ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TOMORROW_MEDIUM_ROC ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +MACRO@DATE_TOMORROW_MEDIUM_CHINESE ㄇㄧㄥˊ-ㄊㄧㄢ-ㄖˋ-ㄑㄧˊ -8 +現在時刻 ㄒㄧㄢˋ-ㄗㄞˋ-ㄕˊ-ㄎㄜˋ -8 +MACRO@TIME_NOW_SHORT ㄒㄧㄢˋ-ㄗㄞˋ-ㄕˊ-ㄎㄜˋ -8 +MACRO@TIME_NOW_MEDIUM ㄒㄧㄢˋ-ㄗㄞˋ-ㄕˊ-ㄎㄜˋ -8 +現在時間 ㄒㄧㄢˋ-ㄗㄞˋ-ㄕˊ-ㄐㄧㄢ -8 +MACRO@TIME_NOW_SHORT ㄒㄧㄢˋ-ㄗㄞˋ-ㄕˊ-ㄐㄧㄢ -8 +MACRO@TIME_NOW_MEDIUM ㄒㄧㄢˋ-ㄗㄞˋ-ㄕˊ-ㄐㄧㄢ -8 +目前時區 ㄇㄨˋ-ㄑㄧㄢˊ-ㄕˊ-ㄑㄩ -8 +MACRO@TIMEZONE_STANDARD ㄇㄨˋ-ㄑㄧㄢˊ-ㄕˊ-ㄑㄩ -8 +MACRO@TIMEZONE_GENERIC_SHORT ㄇㄨˋ-ㄑㄧㄢˊ-ㄕˊ-ㄑㄩ -8 +所在時區 ㄙㄨㄛˇ-ㄗㄞˋ-ㄕˊ-ㄑㄩ -8 +MACRO@TIMEZONE_STANDARD ㄙㄨㄛˇ-ㄗㄞˋ-ㄕˊ-ㄑㄩ -8 +MACRO@TIMEZONE_GENERIC_SHORT ㄙㄨㄛˇ-ㄗㄞˋ-ㄕˊ-ㄑㄩ -8 +今年干支 ㄐㄧㄣ-ㄋㄧㄢˊ-ㄍㄢ-ㄓ -8 +MACRO@THIS_YEAR_GANZHI ㄐㄧㄣ-ㄋㄧㄢˊ-ㄍㄢ-ㄓ -8 +去年干支 ㄑㄩˋ-ㄋㄧㄢˊ-ㄍㄢ-ㄓ -8 +MACRO@LAST_YEAR_GANZHI ㄑㄩˋ-ㄋㄧㄢˊ-ㄍㄢ-ㄓ -8 +明年干支 ㄇㄧㄥˊ-ㄋㄧㄢˊ-ㄍㄢ-ㄓ -8 +MACRO@NEXT_YEAR_GANZHI ㄇㄧㄥˊ-ㄋㄧㄢˊ-ㄍㄢ-ㄓ -8 +今年生肖 ㄐㄧㄣ-ㄋㄧㄢˊ-ㄕㄥ-ㄒㄧㄠˋ -8 +MACRO@THIS_YEAR_CHINESE_ZODIAC ㄐㄧㄣ-ㄋㄧㄢˊ-ㄕㄥ-ㄒㄧㄠˋ -8 +去年生肖 ㄑㄩˋ-ㄋㄧㄢˊ-ㄕㄥ-ㄒㄧㄠˋ -8 +MACRO@LAST_YEAR_CHINESE_ZODIAC ㄑㄩˋ-ㄋㄧㄢˊ-ㄕㄥ-ㄒㄧㄠˋ -8 +明年生肖 ㄇㄧㄥˊ-ㄋㄧㄢˊ-ㄕㄥ-ㄒㄧㄠˋ -8 +MACRO@NEXT_YEAR_CHINESE_ZODIAC ㄇㄧㄥˊ-ㄋㄧㄢˊ-ㄕㄥ-ㄒㄧㄠˋ -8 \ No newline at end of file diff --git a/Source/Data/Makefile b/Source/Data/Makefile index 15f2dc33..875f7093 100644 --- a/Source/Data/Makefile +++ b/Source/Data/Makefile @@ -8,9 +8,9 @@ data-plain-bpmf.txt: bin/cook-plain-bpmf.py BPMFBase.txt BPMFPunctuations.txt bin/cook-plain-bpmf.py BPMFBase.txt BPMFPunctuations.txt data-plain-bpmf.txt data.txt: bin/cook.py BPMFBase.txt BPMFMappings.txt BPMFPunctuations.txt \ - PhraseFreq.txt phrase.occ Symbols.txt \ - heterophony1.list heterophony2.list heterophony3.list - bin/cook.py PhraseFreq.txt BPMFMappings.txt BPMFBase.txt BPMFPunctuations.txt Symbols.txt $@ + PhraseFreq.txt phrase.occ Symbols.txt Macros.txt\ + heterophony1.list heterophony2.list heterophony3.list + bin/cook.py PhraseFreq.txt BPMFMappings.txt BPMFBase.txt BPMFPunctuations.txt Symbols.txt Macros.txt $@ PhraseFreq.txt: bin/buildFreq.py phrase.occ exclusion.txt bin/buildFreq.py diff --git a/Source/Data/Symbols.txt b/Source/Data/Symbols.txt index ae4abfd6..30a2f100 100644 --- a/Source/Data/Symbols.txt +++ b/Source/Data/Symbols.txt @@ -18,8 +18,8 @@ ☢ ㄈㄨˊ-ㄕㄜˋ-ㄒㄧㄥˋ -8 ≥ ㄉㄚˋ-ㄩˊ-ㄉㄥˇ-ㄩˊ -8 🉐 ㄉㄜˊ -8 -▼ ㄉㄠˋ-ㄙㄢ-ㄐㄧㄠˇ -8 ℡ ㄉㄧㄢˋ-ㄏㄨㄚˋ -8 +▼ ㄉㄠˋ-ㄙㄢ-ㄐㄧㄠˇ -8 ㏒ ㄉㄨㄟˋ-ㄕㄨˋ -8 ♏ ㄊㄧㄢ-ㄒㄧㄝ -8 ♎ ㄊㄧㄢ-ㄔㄥˋ -8 diff --git a/Source/Data/bin/cook.py b/Source/Data/bin/cook.py index ae1f710c..25170302 100755 --- a/Source/Data/bin/cook.py +++ b/Source/Data/bin/cook.py @@ -200,6 +200,12 @@ assert len(row) == 3, row output.append(tuple(row)) + with open(sys.argv[6]) as macro_file: + for line in macro_file: + row = line.rstrip().split(" ") + assert len(row) == 3, row + output.append(tuple(row)) + output = convert_vks_rows_to_sorted_kvs_rows(output) with open(sys.argv[-1], "w") as fout: fout.write(HEADER) diff --git a/Source/Engine/McBopomofoLM.cpp b/Source/Engine/McBopomofoLM.cpp index f3e07871..58af8d75 100644 --- a/Source/Engine/McBopomofoLM.cpp +++ b/Source/Engine/McBopomofoLM.cpp @@ -176,6 +176,10 @@ void McBopomofoLM::setExternalConverter(std::function m_externalConverter = externalConverter; } +void McBopomofoLM::setMacroConverter(std::function macroConverter) { + m_macroConverter = macroConverter; +} + std::vector McBopomofoLM::filterAndTransformUnigrams(const std::vector unigrams, const std::unordered_set& excludedValues, std::unordered_set& insertedValues) { std::vector results; @@ -189,17 +193,22 @@ std::vector McBopomofoLM::filterA } std::string value = originalValue; + if (m_phraseReplacementEnabled) { std::string replacement = m_phraseReplacement.valueForKey(value); if (!replacement.empty()) { value = replacement; } } + if (m_macroConverter) { + std::string replacement = m_macroConverter(value); + value = replacement; + } if (m_externalConverterEnabled && m_externalConverter) { std::string replacement = m_externalConverter(value); value = replacement; } - if (insertedValues.find(value) == insertedValues.end()) { + if (!value.empty() && insertedValues.find(value) == insertedValues.end()) { results.emplace_back(value, unigram.score()); insertedValues.insert(value); } diff --git a/Source/Engine/McBopomofoLM.h b/Source/Engine/McBopomofoLM.h index 80ab5e15..917137da 100644 --- a/Source/Engine/McBopomofoLM.h +++ b/Source/Engine/McBopomofoLM.h @@ -101,6 +101,8 @@ class McBopomofoLM : public Formosa::Gramambular2::LanguageModel { bool externalConverterEnabled() const; /// Sets a lambda to let the values of unigrams could be converted by it. void setExternalConverter(std::function externalConverter); + /// Sets a lambda to convert the macros. + void setMacroConverter(std::function macroConverter); const std::vector associatedPhrasesForKey(const std::string& key); bool hasAssociatedPhrasesForKey(const std::string& key); @@ -128,6 +130,7 @@ class McBopomofoLM : public Formosa::Gramambular2::LanguageModel { bool m_phraseReplacementEnabled; bool m_externalConverterEnabled; std::function m_externalConverter; + std::function m_macroConverter; }; }; diff --git a/Source/InputMacro.swift b/Source/InputMacro.swift new file mode 100644 index 00000000..036bcd7a --- /dev/null +++ b/Source/InputMacro.swift @@ -0,0 +1,331 @@ +// Copyright (c) 2022 and onwards The McBopomofo Authors. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. + +import Foundation + +protocol InputMacro { + var name: String { get } + var replacement: String { get } +} + +// MARK: - Date + +fileprivate func formatDate(date: Date, style: DateFormatter.Style, calendar: Calendar = Calendar(identifier: .gregorian)) -> String { + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = style + dateFormatter.timeStyle = .none + dateFormatter.locale = Locale(identifier: "zh_Hant_TW") + dateFormatter.calendar = calendar + return dateFormatter.string(from: date) +} + +fileprivate struct InputMacroDateTodayShort: InputMacro { + var name: String { "MACRO@DATE_TODAY_SHORT" } + + var replacement: String { + let date = Date() + return formatDate(date: date, style: .short) + } +} + +fileprivate struct InputMacroDateYesterdayShort: InputMacro { + var name: String { "MACRO@DATE_YESTERDAY_SHORT" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * -24) + return formatDate(date: date, style: .short) + } +} + +fileprivate struct InputMacroDateTomorrowShort: InputMacro { + var name: String { "MACRO@DATE_TOMORROW_SHORT" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * 24) + return formatDate(date: date, style: .short) + } +} + +fileprivate struct InputMacroDateTodayMedium: InputMacro { + var name: String { "MACRO@DATE_TODAY_MEDIUM" } + + var replacement: String { + let date = Date() + return formatDate(date: date, style: .medium) + } +} + +fileprivate struct InputMacroDateYesterdayMedium: InputMacro { + var name: String { "MACRO@DATE_YESTERDAY_MEDIUM" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * -24) + return formatDate(date: date, style: .medium) + } +} + +fileprivate struct InputMacroDateTomorrowMedium: InputMacro { + var name: String { "MACRO@DATE_TOMORROW_MEDIUM" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * 24) + return formatDate(date: date, style: .medium) + } +} + +fileprivate struct InputMacroDateTodayMediumROC: InputMacro { + var name: String { "MACRO@DATE_TODAY_MEDIUM_ROC" } + + var replacement: String { + let date = Date() + return formatDate(date: date, style: .medium, calendar: Calendar(identifier: .republicOfChina)) + } +} + +fileprivate struct InputMacroDateYesterdayMediumROC: InputMacro { + var name: String { "MACRO@DATE_YESTERDAY_MEDIUM_ROC" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * -24) + return formatDate(date: date, style: .medium, calendar: Calendar(identifier: .republicOfChina)) + } +} + +fileprivate struct InputMacroDateTomorrowMediumROC: InputMacro { + var name: String { "MACRO@DATE_TOMORROW_MEDIUM_ROC" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * 24) + return formatDate(date: date, style: .medium, calendar: Calendar(identifier: .republicOfChina)) + } +} + + +fileprivate struct InputMacroDateTodayMediumChinese: InputMacro { + var name: String { "MACRO@DATE_TODAY_MEDIUM_CHINESE" } + + var replacement: String { + let date = Date() + return formatDate(date: date, style: .medium, calendar: Calendar(identifier: .chinese)) + } +} + +fileprivate struct InputMacroDateYesterdayMediumChinese: InputMacro { + var name: String { "MACRO@DATE_YESTERDAY_MEDIUM_CHINESE" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * -24) + return formatDate(date: date, style: .medium, calendar: Calendar(identifier: .chinese)) + } +} + +fileprivate struct InputMacroDateTomorrowMediumChinese: InputMacro { + var name: String { "MACRO@DATE_TOMORROW_MEDIUM_CHINESE" } + + var replacement: String { + let date = Date().addingTimeInterval(60 * 60 * 24) + return formatDate(date: date, style: .medium, calendar: Calendar(identifier: .chinese)) + } +} + +// MARK: - Time + +fileprivate struct InputMacroTimeNowShort: InputMacro { + var name: String { "MACRO@TIME_NOW_SHORT" } + + var replacement: String { + let date = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .none + dateFormatter.timeStyle = .short + dateFormatter.locale = Locale(identifier: "zh_Hant_TW") + return dateFormatter.string(from: date) + } +} + +fileprivate struct InputMacroTimeNowMedium: InputMacro { + var name: String { "MACRO@TIME_NOW_MEDIUM" } + + var replacement: String { + let date = Date() + let dateFormatter = DateFormatter() + dateFormatter.dateStyle = .none + dateFormatter.timeStyle = .medium + dateFormatter.locale = Locale(identifier: "zh_Hant_TW") + return dateFormatter.string(from: date) + } +} + +// MARK: - Time Zone + +fileprivate struct InputMacroTimeZoneStandard: InputMacro { + var name: String { "MACRO@TIMEZONE_STANDARD" } + + var replacement: String { + let timezone = TimeZone.current + let locale = Locale(identifier: "zh_Hant_TW") + return timezone.localizedName(for: .standard, locale: locale) ?? "" + } +} + +fileprivate struct InputMacroTimeZoneShortGeneric: InputMacro { + var name: String { "MACRO@TIMEZONE_GENERIC_SHORT" } + + var replacement: String { + let timezone = TimeZone.current + let locale = Locale(identifier: "zh_Hant_TW") + return timezone.localizedName(for: .shortGeneric, locale: locale) ?? "" + } +} + +// MARK: - Ganzhi + +fileprivate func getCurrentYear() -> Int { + let date = Date() + let calendar = Calendar(identifier: .gregorian) + let year = calendar.component(.year, from: date) + return year +} + +fileprivate func getYearBase(year: Int) -> (Int, Int) { + let base: Int = year < 4 ? 60 - ((year * -1) + 2) % 60 : (year - 3) % 60 + return (base % 10, base % 12) +} + +fileprivate func ganzhi(year: Int) -> String { + let gan = ["癸", "甲", "乙", "丙", "丁", "戊", "己", "庚", "辛", "壬"] + let zhi = ["亥", "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌"] + let (ganBase, zhiBase) = getYearBase(year: year) + return gan[ganBase] + zhi[zhiBase] + "年" +} + +fileprivate func chineseZodiac(year: Int) -> String { + let gan = ["水", "木", "木", "火", "火", "土", "土", "金", "金", "水"] + let zhi = ["豬", "鼠", "牛", "虎", "兔", "龍", "蛇", "馬", "羊", "猴", "雞", "狗"] + let (ganBase, zhiBase) = getYearBase(year: year) + return gan[ganBase] + zhi[zhiBase] + "年" +} + +fileprivate struct InputMacroThisYearGanZhi: InputMacro { + var name: String { "MACRO@THIS_YEAR_GANZHI" } + + var replacement: String { + let year = getCurrentYear() + return ganzhi(year: year) + } +} + +fileprivate struct InputMacroLastYearGanZhi: InputMacro { + var name: String { "MACRO@LAST_YEAR_GANZHI" } + + var replacement: String { + let year = getCurrentYear() + return ganzhi(year: year - 1) + } +} + +fileprivate struct InputMacroNextYearGanZhi: InputMacro { + var name: String { "MACRO@NEXT_YEAR_GANZHI" } + + var replacement: String { + let year = getCurrentYear() + return ganzhi(year: year + 1) + } +} + +fileprivate struct InputMacroThisYearChineseZodiac: InputMacro { + var name: String { "MACRO@THIS_YEAR_CHINESE_ZODIAC" } + + var replacement: String { + let year = getCurrentYear() + return chineseZodiac(year: year) + } +} + +fileprivate struct InputMacroLastYearChineseZodiac: InputMacro { + var name: String { "MACRO@LAST_YEAR_CHINESE_ZODIAC" } + + var replacement: String { + let year = getCurrentYear() + return chineseZodiac(year: year - 1) + } +} + +fileprivate struct InputMacroNextYearChineseZodiac: InputMacro { + var name: String { "MACRO@NEXT_YEAR_CHINESE_ZODIAC" } + + var replacement: String { + let year = getCurrentYear() + return chineseZodiac(year: year + 1) + } +} + +// MARK: - InputMacroController + +class InputMacroController: NSObject { + @objc + static let shared = InputMacroController() + + private var macros: [String:InputMacro] = { + let macros: [InputMacro] = [ + InputMacroDateTodayShort(), + InputMacroDateYesterdayShort(), + InputMacroDateTomorrowShort(), + InputMacroDateTodayMedium(), + InputMacroDateYesterdayMedium(), + InputMacroDateTomorrowMedium(), + InputMacroDateTodayMediumROC(), + InputMacroDateYesterdayMediumROC(), + InputMacroDateTomorrowMediumROC(), + InputMacroDateTodayMediumChinese(), + InputMacroDateYesterdayMediumChinese(), + InputMacroDateTomorrowMediumChinese(), + InputMacroTimeNowShort(), + InputMacroTimeNowMedium(), + InputMacroTimeZoneStandard(), + InputMacroTimeZoneShortGeneric(), + InputMacroThisYearGanZhi(), + InputMacroLastYearGanZhi(), + InputMacroNextYearGanZhi(), + InputMacroThisYearChineseZodiac(), + InputMacroLastYearChineseZodiac(), + InputMacroNextYearChineseZodiac(), + ] + var map:[String:InputMacro] = [:] + macros.forEach { macro in + map[macro.name] = macro + } + return map + } () + + + @objc + func handle(_ input: String) -> String { + if let macro = macros[input] { + return macro.replacement + } + return input + } + +} + diff --git a/Source/LanguageModelManager.mm b/Source/LanguageModelManager.mm index 4000d9ac..097cdaca 100644 --- a/Source/LanguageModelManager.mm +++ b/Source/LanguageModelManager.mm @@ -101,6 +101,12 @@ + (void)loadUserPhraseReplacement + (void)setupDataModelValueConverter { + auto macroConverter = [](std::string input) { + NSString *inputText = [NSString stringWithUTF8String:input.c_str()]; + NSString *handled = [[InputMacroController shared] handle:inputText]; + return std::string(handled.UTF8String); + }; + auto converter = [](std::string input) { if (!Preferences.chineseConversionEnabled) { return input; @@ -119,6 +125,7 @@ + (void)setupDataModelValueConverter return std::string(text.UTF8String); }; + gLanguageModelMcBopomofo.setMacroConverter(macroConverter); gLanguageModelMcBopomofo.setExternalConverter(converter); gLanguageModelPlainBopomofo.setExternalConverter(converter); }