From 0db3f071fbe3994ec18fa10fc498c47d99c2557e Mon Sep 17 00:00:00 2001 From: Yukihiro Shinoda Date: Sat, 5 Sep 2026 12:38:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(input):=20=E6=97=A5=E4=BB=98=E5=80=99?= =?UTF-8?q?=E8=A3=9C=E3=81=AE=E5=84=AA=E5=85=88=E6=9B=B8=E5=BC=8F=E3=82=92?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ConverterServer+Settings.swift | 17 +++ .../Core/Configs/DateFormatPreference.swift | 15 ++ .../InputUtils/DateCandidatePreference.swift | 124 ++++++++++++++++ .../Core/InputUtils/SegmentsManager.swift | 22 ++- .../DateCandidatePreferenceTests.swift | 133 ++++++++++++++++++ README.md | 7 + azooKeyMac/Windows/ConfigWindow.swift | 1 + 7 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 Core/Sources/Core/Configs/DateFormatPreference.swift create mode 100644 Core/Sources/Core/InputUtils/DateCandidatePreference.swift create mode 100644 Core/Tests/CoreTests/InputUtilsTests/DateCandidatePreferenceTests.swift diff --git a/Core/Sources/ConverterServer/ConverterServer+Settings.swift b/Core/Sources/ConverterServer/ConverterServer+Settings.swift index e5229eff..bf1c2dfc 100644 --- a/Core/Sources/ConverterServer/ConverterServer+Settings.swift +++ b/Core/Sources/ConverterServer/ConverterServer+Settings.swift @@ -57,6 +57,17 @@ extension ConverterServer { kind: .toggle, value: .bool(Config.TypeHalfSpace().value) ), + descriptor( + key: Config.DateFormatPreference.key, + title: "日付候補の優先書式", + section: "入力オプション", + kind: .selector(options: [ + .init(title: "標準", value: .string(Config.DateFormatPreference.Value.standard.rawValue)), + .init(title: "MM/DDを優先", value: .string(Config.DateFormatPreference.Value.monthDay.rawValue)), + .init(title: "曜日付きを優先", value: .string(Config.DateFormatPreference.Value.weekday.rawValue)) + ]), + value: .string(Config.DateFormatPreference().value.rawValue) + ), descriptor( key: Config.OptionDirectFullWidthInput.key, title: "Optionキーで直接全角英数を入力", @@ -233,6 +244,12 @@ extension ConverterServer { Config.TypeBackSlash().value = try boolSettingValue(value, key: key) case Config.TypeHalfSpace.key: Config.TypeHalfSpace().value = try boolSettingValue(value, key: key) + case Config.DateFormatPreference.key: + guard case .string(let rawValue) = value, + let preference = Config.DateFormatPreference.Value(rawValue: rawValue) else { + throw ConverterServerError.invalidSettingValue(key) + } + Config.DateFormatPreference().value = preference case Config.OptionDirectFullWidthInput.key: Config.OptionDirectFullWidthInput().value = try boolSettingValue(value, key: key) case Config.PunctuationStyle.key: diff --git a/Core/Sources/Core/Configs/DateFormatPreference.swift b/Core/Sources/Core/Configs/DateFormatPreference.swift new file mode 100644 index 00000000..6b426d38 --- /dev/null +++ b/Core/Sources/Core/Configs/DateFormatPreference.swift @@ -0,0 +1,15 @@ +import Foundation + +extension Config { + public struct DateFormatPreference: CustomCodableConfigItem { + public enum Value: String, Codable, Equatable, Hashable, Sendable { + case standard + case monthDay + case weekday + } + + public init() {} + public static let `default`: Value = .standard + public static let key = "dev.ensan.inputmethod.azooKeyMac.preference.dateFormatPreference" + } +} diff --git a/Core/Sources/Core/InputUtils/DateCandidatePreference.swift b/Core/Sources/Core/InputUtils/DateCandidatePreference.swift new file mode 100644 index 00000000..ba9b526f --- /dev/null +++ b/Core/Sources/Core/InputUtils/DateCandidatePreference.swift @@ -0,0 +1,124 @@ +import Foundation +import KanaKanjiConverterModuleWithDefaultDictionary + +/// 日付の生成元が明示した候補だけを並べ替える。数字や分数の見た目だけでは判定しない。 +enum DateCandidatePreference { + static func apply( + to result: inout ConversionResult, + dateEntries: [DicdataElement], + readingCount: Int, + preference: Config.DateFormatPreference.Value + ) { + result.mainResults = applying(to: result.mainResults, dateEntries: dateEntries, + readingCount: readingCount, preference: preference) + result.firstClauseResults = reordered(result.firstClauseResults, dateWords: Set(dateEntries.map(\.word)), + preference: preference) + } + + static func applying( + to candidates: [Candidate], dateEntries: [DicdataElement], readingCount: Int, + preference: Config.DateFormatPreference.Value, + now: Date = Date(), calendar: Calendar = .current + ) -> [Candidate] { + guard preference != .standard, !dateEntries.isEmpty else { + return candidates + } + var entries = dateEntries + if preference == .monthDay { + for entry in dateEntries { + guard let padded = paddedMonthDay(entry.word), padded != entry.word else { continue } + entries.append(.init(word: padded, ruby: entry.ruby, cid: CIDData.固有名詞.cid, + mid: MIDData.一般.mid, value: entry.value())) + } + } + if preference == .weekday { + for entry in dateEntries { + guard let text = numericWeekday(entry, now: now, calendar: calendar) else { continue } + entries.append(.init(word: text, ruby: entry.ruby, cid: CIDData.固有名詞.cid, + mid: MIDData.一般.mid, value: entry.value())) + } + } + let words = Set(entries.map(\.word)) + // 希望した書式がエンジンの候補数制限で落ちても、元の候補を残して補完する。 + var seen = Set(candidates.map(\.text)) + let missing = entries.filter { isPreferred($0.word, preference: preference) && seen.insert($0.word).inserted } + let additions = missing.map { entry in + Candidate(text: entry.word, value: entry.value(), composingCount: .surfaceCount(readingCount), + lastMid: MIDData.一般.mid, data: [entry], isLearningTarget: false) + } + var result = candidates + result.insert(contentsOf: additions, at: min(5, result.count)) + return reordered(result, dateWords: words, preference: preference) + } + + static func reordered( + _ candidates: [Candidate], dateWords: Set, preference: Config.DateFormatPreference.Value + ) -> [Candidate] { + guard preference != .standard else { + return candidates + } + let indices = candidates.indices.filter { dateWords.contains(candidates[$0].text) } + let dates = indices.map { candidates[$0] } + let ordered = dates.filter { isPreferred($0.text, preference: preference) } + + dates.filter { !isPreferred($0.text, preference: preference) } + var result = candidates + for (index, candidate) in zip(indices, ordered) { result[index] = candidate } + return result + } + + static func paddedMonthDay(_ text: String) -> String? { + guard text.range(of: #"^[0-9]{1,2}/[0-9]{1,2}$"#, options: .regularExpression) != nil else { + return nil + } + let parts = text.split(separator: "/").compactMap { Int($0) } + guard parts.count == 2, (1...12).contains(parts[0]) else { + return nil + } + let monthLengths = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] + guard (1...monthLengths[parts[0] - 1]).contains(parts[1]) else { + return nil + } + return String(format: "%02d/%02d", parts[0], parts[1]) + } + + /// 年のない4桁入力だけを今年の月日として解釈する。相対日付の年は変更しない。 + static func numericWeekday(_ entry: DicdataElement, now: Date, calendar: Calendar) -> String? { + let digits = entry.ruby.unicodeScalars.compactMap { scalar -> Int? in + switch scalar.value { + case 0x30...0x39: Int(scalar.value - 0x30) + case 0xFF10...0xFF19: Int(scalar.value - 0xFF10) + default: nil + } + } + guard entry.ruby.unicodeScalars.count == 4, digits.count == 4, + paddedMonthDay(entry.word) == "\(digits[0])\(digits[1])/\(digits[2])\(digits[3])" else { + return nil + } + var gregorian = Calendar(identifier: .gregorian) + gregorian.timeZone = calendar.timeZone + let year = gregorian.component(.year, from: now) + let month = digits[0] * 10 + digits[1] + let day = digits[2] * 10 + digits[3] + guard let date = gregorian.date(from: DateComponents(year: year, month: month, day: day, hour: 12)), + gregorian.component(.year, from: date) == year, + gregorian.component(.month, from: date) == month, + gregorian.component(.day, from: date) == day else { + return nil + } + let formatter = DateFormatter() + formatter.locale = Locale(identifier: "ja_JP") + formatter.calendar = gregorian + formatter.timeZone = gregorian.timeZone + formatter.dateFormat = "M月d日(E)" + return formatter.string(from: date) + } + + private static func isPreferred(_ text: String, preference: Config.DateFormatPreference.Value) -> Bool { + switch preference { + case .standard: false + case .monthDay: paddedMonthDay(text) == text + case .weekday: + text.range(of: #"[((][日月火水木金土][))]"#, options: .regularExpression) != nil + } + } +} diff --git a/Core/Sources/Core/InputUtils/SegmentsManager.swift b/Core/Sources/Core/InputUtils/SegmentsManager.swift index 3188ca36..6b832229 100644 --- a/Core/Sources/Core/InputUtils/SegmentsManager.swift +++ b/Core/Sources/Core/InputUtils/SegmentsManager.swift @@ -17,13 +17,16 @@ public final class SegmentsManager { /// テストなどの設定注入のための型。外部には設定を露出させない。 public struct Context { public init() {} - public init(useZenzai: Bool, resourcesDirectoryURL: URL? = nil) { + public init(useZenzai: Bool, resourcesDirectoryURL: URL? = nil, + dateFormatPreference: Config.DateFormatPreference.Value? = nil) { self.useZenzai = useZenzai self.resourcesDirectoryURL = resourcesDirectoryURL + self.dateFormatPreference = dateFormatPreference } var useZenzai: Bool = true var resourcesDirectoryURL: URL? + var dateFormatPreference: Config.DateFormatPreference.Value? } public weak var delegate: (any SegmentManagerDelegate)? @@ -564,7 +567,7 @@ public final class SegmentsManager { let leftSideContext = forcedLeftSideContext ?? self.getCleanLeftSideContext(maxCount: ContextLength.conversion) let rightSideContext = forcedRightSideContext ?? self.getCleanRightSideContext(maxCount: ContextLength.conversion) - let result = self.kanaKanjiConverter.requestCandidates( + var result = self.kanaKanjiConverter.requestCandidates( self.composingText, options: options( leftSideContext: leftSideContext, @@ -574,6 +577,21 @@ public final class SegmentsManager { requireEnglishPrediction: Config.DebugPredictiveTyping().value ? .manualMix : .disabled ) ) + let dateEntries = dynamicShortcuts.compactMap { entry -> DicdataElement? in + guard entry.ruby == self.composingText.convertTarget.toKatakana(), + entry.word.hasPrefix(" DicdataElement { + .init(word: word, ruby: "キョウ", cid: CIDData.固有名詞.cid, mid: MIDData.一般.mid, value: -18) + } + + private func candidate(_ word: String) -> Candidate { + .init(text: word, value: -18, composingCount: .surfaceCount(3), lastMid: MIDData.一般.mid, + data: [entry(word)], isLearningTarget: false) + } + + @Test func standardPreservesEveryCandidateAndAddsNothing() { + let original = ["今日", "2026/09/05", "9/5", "9月5日(土)"].map(candidate) + let result = DateCandidatePreference.applying(to: original, dateEntries: [entry("9/5")], + readingCount: 3, preference: .standard) + #expect(result.map(\.text) == original.map(\.text)) + #expect(Config.DateFormatPreference.default == .standard) + } + + @Test func preferenceOnlySwapsDateSlotsAndIsStable() { + let original = ["今日", "2026/09/05", "教", "9月5日(土)", "京", "09/05", "9/5"].map(candidate) + let words: Set = ["2026/09/05", "9月5日(土)", "09/05", "9/5"] + let result = DateCandidatePreference.reordered(original, dateWords: words, preference: .weekday) + #expect(result.map(\.text) == ["今日", "9月5日(土)", "教", "2026/09/05", "京", "09/05", "9/5"]) + let monthDay = DateCandidatePreference.reordered(original, dateWords: words, preference: .monthDay) + #expect(monthDay.map(\.text) == ["今日", "09/05", "教", "2026/09/05", "京", "9月5日(土)", "9/5"]) + } + + @Test func missingPaddedDateIsAddedWithoutReplacingOriginalOrDuplicates() { + let original = ["今日", "9/5", "教"].map(candidate) + let entries = [entry("9/5"), entry("9/5")] + let result = DateCandidatePreference.applying(to: original, dateEntries: entries, + readingCount: 3, preference: .monthDay) + #expect(result.map(\.text) == ["今日", "09/05", "教", "9/5"]) + #expect(result.filter { $0.text == "09/05" }.count == 1) + #expect(result.first { $0.text == "09/05" }?.isLearningTarget == false) + var text = ComposingText() + text.insertAtCursorPosition("きょう", inputStyle: .direct) + for candidate in result { + var remaining = text + remaining.prefixComplete(composingCount: candidate.composingCount) + #expect(remaining.convertTarget.isEmpty) + } + let repeated = DateCandidatePreference.applying(to: result, dateEntries: entries, + readingCount: 3, preference: .monthDay) + #expect(repeated.map(\.text) == result.map(\.text)) + } + + @Test func unregisteredFractionAndWeekdayWordsRemainUntouched() { + let original = ["1/2", "土曜日", "9月5日(土)", "今日", "9/5"].map(candidate) + let result = DateCandidatePreference.applying(to: original, dateEntries: [entry("9/5")], + readingCount: 3, preference: .monthDay) + #expect(result.map(\.text) == ["1/2", "土曜日", "9月5日(土)", "今日", "09/05", "9/5"]) + let noDates = DateCandidatePreference.applying(to: original, dateEntries: [], + readingCount: 3, preference: .weekday) + #expect(noDates.map(\.text) == original.map(\.text)) + } + + @Test func preferredDateCanBeRecoveredWhenEngineOmitsIt() { + let original = ["今日", "教", "京", "強", "きょう", "キョウ"].map(candidate) + let result = DateCandidatePreference.applying(to: original, dateEntries: [entry("9月5日(土)")], + readingCount: 3, preference: .weekday) + #expect(result.map(\.text) == ["今日", "教", "京", "強", "きょう", "9月5日(土)", "キョウ"]) + } + + @Test(arguments: [("9/5", "09/05"), ("02/29", "02/29"), ("12/31", "12/31")]) + func validMonthDay(input: String, expected: String) { + #expect(DateCandidatePreference.paddedMonthDay(input) == expected) + } + + @Test(arguments: ["0/1", "13/1", "2/30", "4/31", "9/0", "2026/9/5", "9/5", "9/5です", "1/2/3"]) + func rejectsOtherFormats(input: String) { + #expect(DateCandidatePreference.paddedMonthDay(input) == nil) + } + + @MainActor + @Test(arguments: [Config.DateFormatPreference.Value.monthDay, .weekday]) + func actualConversionOffersPreferredDateWithoutChangingSettings(preference: Config.DateFormatPreference.Value) throws { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: directory) } + let manager = SegmentsManager(kanaKanjiConverter: .withDefaultDictionary(), + applicationDirectoryURL: directory, containerURL: nil, + context: .init(useZenzai: false, dateFormatPreference: preference)) + manager.insertAtCursorPosition("きょう", inputStyle: .direct) + for rich in [false, true] { + manager.update(requestRichCandidates: rich) + manager.requestSetCandidateWindowState(visible: true) + guard case .selecting(let candidates, _) = manager.getCurrentCandidateWindow(inputState: .selecting) else { + Issue.record("候補欄が表示されません") + return + } + let pattern = preference == .monthDay ? #"^[0-9]{2}/[0-9]{2}$"# : #"^[0-9]+月[0-9]+日([日月火水木金土])$"# + #expect(candidates.contains { $0.text.range(of: pattern, options: .regularExpression) != nil }) + #expect(candidates.contains { $0.text == "今日" }) + } + } + + @Test func numericInputGetsThisYearsWeekdayAndKeepsOriginal() throws { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = try #require(TimeZone(identifier: "Asia/Tokyo")) + let now = try #require(calendar.date(from: DateComponents(year: 2026, month: 9, day: 5))) + for ruby in ["1111", "1111"] { + let dateEntry = DicdataElement(word: "11/11", ruby: ruby, cid: CIDData.固有名詞.cid, mid: MIDData.一般.mid, value: -18) + let original = [candidate("1111"), candidate("11/11")] + let result = DateCandidatePreference.applying(to: original, dateEntries: [dateEntry], readingCount: 4, + preference: .weekday, now: now, calendar: calendar) + #expect(result.map(\.text) == ["1111", "11月11日(水)", "11/11"]) + let added = try #require(result.first { $0.text == "11月11日(水)" }) + #expect(!added.isLearningTarget) + var composing = ComposingText() + composing.insertAtCursorPosition(ruby, inputStyle: .direct) + composing.prefixComplete(composingCount: added.composingCount) + #expect(composing.isEmpty) + } + } + + @Test func numericLeapDayOnlyGetsWeekdayInLeapYear() throws { + var calendar = Calendar(identifier: .gregorian) + calendar.timeZone = try #require(TimeZone(identifier: "Asia/Tokyo")) + let entry = DicdataElement(word: "02/29", ruby: "0229", cid: CIDData.固有名詞.cid, mid: MIDData.一般.mid, value: -18) + let commonYear = try #require(calendar.date(from: DateComponents(year: 2026, month: 1, day: 1))) + let leapYear = try #require(calendar.date(from: DateComponents(year: 2028, month: 1, day: 1))) + #expect(DateCandidatePreference.numericWeekday(entry, now: commonYear, calendar: calendar) == nil) + #expect(DateCandidatePreference.numericWeekday(entry, now: leapYear, calendar: calendar) == "2月29日(火)") + #expect(DateCandidatePreference.numericWeekday(self.entry("11/11"), now: commonYear, calendar: calendar) == nil) + } + +} diff --git a/README.md b/README.md index e710c7a7..ac60b113 100644 --- a/README.md +++ b/README.md @@ -151,3 +151,10 @@ Thanks to authors!! ## Acknowledgement 本プロジェクトは情報処理推進機構(IPA)による[2024年度未踏IT人材発掘・育成事業](https://www.ipa.go.jp/jinzai/mitou/it/2024/koubokekka.html)の支援を受けて開発を行いました。 + +### 日付候補の優先書式 + +カスタマイズの「日付候補の優先書式」で「標準」「MM/DDを優先」「曜日付きを優先」を選べます。 +「きょう」などの日付候補について、希望する書式を日付候補内で優先します。例えば「MM/DDを優先」では `09/05`、「曜日付きを優先」では `9月5日(土)` を選びやすくします。元の書式と通常の変換候補も残り、標準では従来の順番を維持します。 + +4桁の月日変換と組み合わせた場合、「曜日付き」では今年の月日として曜日候補を補います(例:2026年の`1111` → `11月11日(水)`)。今年存在しない月日には曜日を付けません。年なしの`02/29`候補は維持します。 diff --git a/azooKeyMac/Windows/ConfigWindow.swift b/azooKeyMac/Windows/ConfigWindow.swift index 099d4307..58cb23b5 100644 --- a/azooKeyMac/Windows/ConfigWindow.swift +++ b/azooKeyMac/Windows/ConfigWindow.swift @@ -752,6 +752,7 @@ struct ConfigWindow: View { keys: [ Config.TypeBackSlash.key, Config.TypeHalfSpace.key, + Config.DateFormatPreference.key, Config.OptionDirectFullWidthInput.key, Config.PunctuationStyle.key ] From 101f3706abe8e4cc69b92253fbe98057175a2c73 Mon Sep 17 00:00:00 2001 From: Yukihiro Shinoda Date: Sat, 5 Sep 2026 13:03:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(input):=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E6=8C=87=E6=91=98=E3=81=AB=E5=AF=BE=E5=BF=9C=E3=81=97?= =?UTF-8?q?=E5=A4=89=E6=8F=9B=E7=AF=84=E5=9B=B2=E3=81=A8=E5=80=99=E8=A3=9C?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InputUtils/DateCandidatePreference.swift | 42 +----------- .../Core/InputUtils/SegmentsManager.swift | 29 ++++---- .../DateCandidatePreferenceTests.swift | 67 ++++++++++++------- README.md | 2 - 4 files changed, 58 insertions(+), 82 deletions(-) diff --git a/Core/Sources/Core/InputUtils/DateCandidatePreference.swift b/Core/Sources/Core/InputUtils/DateCandidatePreference.swift index ba9b526f..c4219edd 100644 --- a/Core/Sources/Core/InputUtils/DateCandidatePreference.swift +++ b/Core/Sources/Core/InputUtils/DateCandidatePreference.swift @@ -17,8 +17,7 @@ enum DateCandidatePreference { static func applying( to candidates: [Candidate], dateEntries: [DicdataElement], readingCount: Int, - preference: Config.DateFormatPreference.Value, - now: Date = Date(), calendar: Calendar = .current + preference: Config.DateFormatPreference.Value ) -> [Candidate] { guard preference != .standard, !dateEntries.isEmpty else { return candidates @@ -31,13 +30,6 @@ enum DateCandidatePreference { mid: MIDData.一般.mid, value: entry.value())) } } - if preference == .weekday { - for entry in dateEntries { - guard let text = numericWeekday(entry, now: now, calendar: calendar) else { continue } - entries.append(.init(word: text, ruby: entry.ruby, cid: CIDData.固有名詞.cid, - mid: MIDData.一般.mid, value: entry.value())) - } - } let words = Set(entries.map(\.word)) // 希望した書式がエンジンの候補数制限で落ちても、元の候補を残して補完する。 var seen = Set(candidates.map(\.text)) @@ -81,38 +73,6 @@ enum DateCandidatePreference { return String(format: "%02d/%02d", parts[0], parts[1]) } - /// 年のない4桁入力だけを今年の月日として解釈する。相対日付の年は変更しない。 - static func numericWeekday(_ entry: DicdataElement, now: Date, calendar: Calendar) -> String? { - let digits = entry.ruby.unicodeScalars.compactMap { scalar -> Int? in - switch scalar.value { - case 0x30...0x39: Int(scalar.value - 0x30) - case 0xFF10...0xFF19: Int(scalar.value - 0xFF10) - default: nil - } - } - guard entry.ruby.unicodeScalars.count == 4, digits.count == 4, - paddedMonthDay(entry.word) == "\(digits[0])\(digits[1])/\(digits[2])\(digits[3])" else { - return nil - } - var gregorian = Calendar(identifier: .gregorian) - gregorian.timeZone = calendar.timeZone - let year = gregorian.component(.year, from: now) - let month = digits[0] * 10 + digits[1] - let day = digits[2] * 10 + digits[3] - guard let date = gregorian.date(from: DateComponents(year: year, month: month, day: day, hour: 12)), - gregorian.component(.year, from: date) == year, - gregorian.component(.month, from: date) == month, - gregorian.component(.day, from: date) == day else { - return nil - } - let formatter = DateFormatter() - formatter.locale = Locale(identifier: "ja_JP") - formatter.calendar = gregorian - formatter.timeZone = gregorian.timeZone - formatter.dateFormat = "M月d日(E)" - return formatter.string(from: date) - } - private static func isPreferred(_ text: String, preference: Config.DateFormatPreference.Value) -> Bool { switch preference { case .standard: false diff --git a/Core/Sources/Core/InputUtils/SegmentsManager.swift b/Core/Sources/Core/InputUtils/SegmentsManager.swift index 6b832229..26b3c66a 100644 --- a/Core/Sources/Core/InputUtils/SegmentsManager.swift +++ b/Core/Sources/Core/InputUtils/SegmentsManager.swift @@ -577,21 +577,24 @@ public final class SegmentsManager { requireEnglishPrediction: Config.DebugPredictiveTyping().value ? .manualMix : .disabled ) ) - let dateEntries = dynamicShortcuts.compactMap { entry -> DicdataElement? in - guard entry.ruby == self.composingText.convertTarget.toKatakana(), - entry.word.hasPrefix(" DicdataElement? in + guard entry.ruby == self.composingText.convertTarget.toKatakana(), + entry.word.hasPrefix("