import AppKit import Foundation import NaturalLanguage public protocol WordKnowledge { /// A common word in Dutch or English (case-insensitive). Rare words, names and product names are not. func isWord(_ word: String) -> Bool /// A very frequent function word ("in", "de", "the", "of"). func isFunctionWord(_ word: String) -> Bool } /// Splitting and normalising the left-hand side of an entry, shared by the engine and the warnings. enum EntryKey { static func parts(_ source: String) -> [String] { source.split(whereSeparator: { $0.isWhitespace || "-‐‑–".contains($0) }).map(String.init) } /// Spacing, hyphens and case do not matter: "Cloud-Code", "cloud code" and "cloudcode" are the same source. static func key(_ source: String) -> String { parts(source).joined().lowercased() } } /// The most frequent Dutch and English function words. enum CommonWords { static let english: Set = [ "a", "about", "after", "all", "also", "an", "and", "any", "are", "as", "at", "be", "because", "been", "but", "by", "can", "could", "did", "do", "does", "for", "from", "had", "has", "have", "he", "her", "here", "him", "his", "how", "i", "if", "in", "into", "is", "it", "its", "just", "me", "more", "most", "my", "no", "not", "now", "of", "on", "one", "only", "or", "other", "our", "out", "over", "she", "so", "some", "such", "than", "that", "the", "their", "them", "then", "there", "these", "they", "this", "those", "to", "up", "us", "very", "was", "we", "were", "what", "when", "which", "who", "will", "with", "would", "you", "your", ] static let dutch: Set = [ "aan", "al", "als", "ben", "bij", "dan", "dat", "de", "den", "der", "deze", "die", "dit", "doen", "door", "dus", "een", "en", "er", "ga", "had", "heb", "heeft", "hem", "het", "hier", "hij", "hoe", "ik", "in", "is", "ja", "je", "jij", "kan", "kon", "maar", "me", "mij", "mijn", "moet", "na", "naar", "niet", "nog", "nu", "of", "om", "ons", "onze", "ook", "op", "over", "te", "ten", "ter", "toch", "toen", "tot", "u", "uit", "van", "veel", "voor", "was", "wat", "we", "wel", "werd", "wij", "wil", "worden", "wordt", "zal", "ze", "zei", "zich", "zij", "zijn", "zo", "zou", ] static let all: Set = english.union(dutch) } /// English: the on-device word list of `NaturalLanguage` (common words only, so "cloud" and "reef" are in, "anthropic" and "vercel" are not). /// Dutch has no such list, so the spell checker decides. Function words come from the built-in list. public final class SystemWordKnowledge: WordKnowledge, @unchecked Sendable { private let englishWords = NLEmbedding.wordEmbedding(for: .english) public init() {} public func isWord(_ word: String) -> Bool { let lowered = word.lowercased() guard !lowered.isEmpty else { return false } if let englishWords { if englishWords.contains(lowered) { return true } } else if spellChecks(lowered, language: "en") { return true } return spellChecks(lowered, language: "nl") } public func isFunctionWord(_ word: String) -> Bool { CommonWords.all.contains(word.lowercased()) } private func spellChecks(_ word: String, language: String) -> Bool { NSSpellChecker.shared.checkSpelling( of: word, startingAt: 0, language: language, wrap: false, inSpellDocumentWithTag: 0, wordCount: nil).location == NSNotFound } }