9f308fadd2
Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
132 lines
4.6 KiB
Swift
132 lines
4.6 KiB
Swift
// RimePersonalDictionaryExporter.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// Turns PersonalDictionary into an osg_personal Rime table that the
|
|
// main osg_pinyin dictionary imports. High weight keeps same-code
|
|
// personal hits above the baseline lexicon.
|
|
|
|
import CryptoKit
|
|
import Foundation
|
|
|
|
public enum RimePersonalDictionaryExporter {
|
|
public static let dictionaryName = "osg_personal"
|
|
/// Above any baseline `osg_pinyin` weight so same-code hits pin to top.
|
|
public static let pinWeight = 50_000_000
|
|
|
|
public struct Entry: Equatable, Sendable {
|
|
public let text: String
|
|
public let code: String
|
|
public let weight: Int
|
|
|
|
public init(text: String, code: String, weight: Int = RimePersonalDictionaryExporter.pinWeight) {
|
|
self.text = text
|
|
self.code = code
|
|
self.weight = weight
|
|
}
|
|
}
|
|
|
|
/// Builds Rime rows for Chinese (pinyin-coded), English (latin-coded),
|
|
/// and Latin aliases that should surface the canonical term.
|
|
public static func entries(
|
|
from dictionary: PersonalDictionary,
|
|
annotator: RimePinyinAnnotator
|
|
) -> [Entry] {
|
|
var seen = Set<String>()
|
|
var rows: [Entry] = []
|
|
|
|
func append(text: String, code: String) {
|
|
let key = "\(text)\t\(code)"
|
|
guard seen.insert(key).inserted else { return }
|
|
rows.append(Entry(text: text, code: code))
|
|
}
|
|
|
|
for entry in dictionary.effectiveEntries {
|
|
let term = entry.term.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !term.isEmpty else { continue }
|
|
|
|
if let code = annotator.code(for: term) {
|
|
append(text: term, code: code)
|
|
}
|
|
|
|
// Latin aliases → alternate codes for the canonical term
|
|
// (e.g. brand English → 中文专名). Chinese ASR aliases stay out.
|
|
for alias in entry.aliases {
|
|
let trimmed = alias.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
guard !trimmed.isEmpty else { continue }
|
|
guard !HanScript.containsIdeograph(in: trimmed) else { continue }
|
|
let latin = RimePinyinAnnotator.latinSpellerCode(trimmed)
|
|
guard !latin.isEmpty else { continue }
|
|
append(text: term, code: latin)
|
|
}
|
|
}
|
|
|
|
return rows.sorted {
|
|
if $0.text != $1.text {
|
|
return $0.text.localizedStandardCompare($1.text) == .orderedAscending
|
|
}
|
|
return $0.code < $1.code
|
|
}
|
|
}
|
|
|
|
public static func yaml(
|
|
from dictionary: PersonalDictionary,
|
|
annotator: RimePinyinAnnotator
|
|
) -> String {
|
|
yaml(entries: entries(from: dictionary, annotator: annotator))
|
|
}
|
|
|
|
public static func yaml(entries: [Entry]) -> String {
|
|
var lines: [String] = [
|
|
"# Generated by OSGKeyboard from PersonalDictionary. Do not edit.",
|
|
"---",
|
|
"name: \(dictionaryName)",
|
|
"version: \"1.0\"",
|
|
"sort: by_weight",
|
|
"columns:",
|
|
" - text",
|
|
" - code",
|
|
" - weight",
|
|
"..."
|
|
]
|
|
for entry in entries {
|
|
lines.append("\(entry.text)\t\(entry.code)\t\(entry.weight)")
|
|
}
|
|
lines.append("")
|
|
return lines.joined(separator: "\n")
|
|
}
|
|
|
|
public static func fingerprint(of yaml: String) -> String {
|
|
let digest = SHA256.hash(data: Data(yaml.utf8))
|
|
return digest.map { String(format: "%02x", $0) }.joined()
|
|
}
|
|
|
|
/// Injects `import_tables: [osg_personal]` into a baseline dict header.
|
|
public static func injectingImportTables(into baselineYAML: String) -> String {
|
|
if baselineYAML.contains("import_tables:") {
|
|
return baselineYAML
|
|
}
|
|
let needle = "use_preset_vocabulary: false\n"
|
|
let injection = """
|
|
use_preset_vocabulary: false
|
|
import_tables:
|
|
- \(dictionaryName)
|
|
|
|
"""
|
|
if let range = baselineYAML.range(of: needle) {
|
|
return baselineYAML.replacingCharacters(in: range, with: injection)
|
|
}
|
|
// Fallback: insert after the `---` document start block's name line.
|
|
let nameNeedle = "name: osg_pinyin\n"
|
|
if let range = baselineYAML.range(of: nameNeedle) {
|
|
let injectionAfterName = """
|
|
name: osg_pinyin
|
|
import_tables:
|
|
- \(dictionaryName)
|
|
|
|
"""
|
|
return baselineYAML.replacingCharacters(in: range, with: injectionAfterName)
|
|
}
|
|
return baselineYAML
|
|
}
|
|
}
|