498f407585
Introduce optional Apple account-backed credits with scoped gateway access while preserving local and BYOK paths. Refresh assistant behavior, tests, privacy disclosures, docs, and the website for the 2.0 experience.
117 lines
5.0 KiB
Swift
117 lines
5.0 KiB
Swift
// TranslationLanguage.swift
|
|
// OSGKeyboard · Shared
|
|
//
|
|
// Catalog of target languages the translation feature can produce.
|
|
//
|
|
// Kept deliberately small (~10 entries) to match the kind of choices
|
|
// the user makes in the Settings picker / keyboard menu. We don't try
|
|
// to expose every BCP-47 locale — the prompt just needs a target
|
|
// language name, and a curated list reads better than a 100-row scroll.
|
|
//
|
|
// `id` is what gets persisted to the App Group. `promptLanguageName`
|
|
// is the human-readable target name injected into the prompt (e.g.
|
|
// the LLM sees "English", not "en"). `nativeName` is the endonym we
|
|
// show in the picker UI ("日本語" instead of "Japanese").
|
|
|
|
import Foundation
|
|
|
|
public struct TranslationLanguage: Identifiable, Hashable, Sendable {
|
|
public let id: String
|
|
public let promptLanguageName: String
|
|
public let nativeName: String
|
|
|
|
public init(id: String, promptLanguageName: String, nativeName: String) {
|
|
self.id = id
|
|
self.promptLanguageName = promptLanguageName
|
|
self.nativeName = nativeName
|
|
}
|
|
|
|
/// One-character Chinese token for compact chips such as「中译英」.
|
|
public var chineseShort: String {
|
|
switch id {
|
|
case "en": return "英"
|
|
case "zh-Hans": return "中"
|
|
case "zh-Hant": return "繁"
|
|
case "ja": return "日"
|
|
case "ko": return "韩"
|
|
case "fr": return "法"
|
|
case "de": return "德"
|
|
case "es": return "西"
|
|
case "ru": return "俄"
|
|
case "pt": return "葡"
|
|
default: return nativeName
|
|
}
|
|
}
|
|
|
|
/// Country-style English code for compact chips such as「To JP」.
|
|
public var englishShort: String {
|
|
switch id {
|
|
case "en": return "EN"
|
|
case "zh-Hans": return "CN"
|
|
case "zh-Hant": return "TW"
|
|
case "ja": return "JP"
|
|
case "ko": return "KR"
|
|
case "fr": return "FR"
|
|
case "de": return "DE"
|
|
case "es": return "ES"
|
|
case "ru": return "RU"
|
|
case "pt": return "PT"
|
|
default: return id.uppercased()
|
|
}
|
|
}
|
|
|
|
/// True when this target is a Chinese script (简体 or 繁體).
|
|
public var isChineseScript: Bool {
|
|
id == "zh-Hans" || id == "zh-Hant"
|
|
}
|
|
}
|
|
|
|
public enum TranslationLanguageCatalog {
|
|
/// Sentinel id for "don't translate" — the default selection in the
|
|
/// picker. Picked over an `Optional<TranslationLanguage>` so the
|
|
/// single-row `Picker` binding and keyboard menu stay a plain `String`.
|
|
public static let offLocaleId = "off"
|
|
/// Default target language id used on fresh installs when translation
|
|
/// is enabled. The picker still defaults to `offLocaleId` — this is
|
|
/// only the language we'd fall back to if a stale "on" state is
|
|
/// recovered without a remembered target.
|
|
public static let defaultLocaleId = "en"
|
|
|
|
/// Curated set. Order matters — the picker / menu render top-to-
|
|
/// bottom, with `offLocaleId` ("不翻译") at the very top so the
|
|
/// "turn off" action is one tap away from any enabled state.
|
|
public static let all: [TranslationLanguage] = [
|
|
TranslationLanguage(id: offLocaleId, promptLanguageName: "", nativeName: ""),
|
|
TranslationLanguage(id: "en", promptLanguageName: "English", nativeName: "English"),
|
|
TranslationLanguage(id: "zh-Hans", promptLanguageName: "Simplified Chinese", nativeName: "简体中文"),
|
|
TranslationLanguage(id: "zh-Hant", promptLanguageName: "Traditional Chinese", nativeName: "繁體中文"),
|
|
TranslationLanguage(id: "ja", promptLanguageName: "Japanese", nativeName: "日本語"),
|
|
TranslationLanguage(id: "ko", promptLanguageName: "Korean", nativeName: "한국어"),
|
|
TranslationLanguage(id: "fr", promptLanguageName: "French", nativeName: "Français"),
|
|
TranslationLanguage(id: "de", promptLanguageName: "German", nativeName: "Deutsch"),
|
|
TranslationLanguage(id: "es", promptLanguageName: "Spanish", nativeName: "Español"),
|
|
TranslationLanguage(id: "ru", promptLanguageName: "Russian", nativeName: "Русский"),
|
|
TranslationLanguage(id: "pt", promptLanguageName: "Portuguese", nativeName: "Português")
|
|
]
|
|
|
|
/// True when the given id is the "off" sentinel. Used by the picker
|
|
/// to flip `translationEnabled` and by the pipeline to skip the
|
|
/// translate prompt.
|
|
public static func isOff(_ id: String) -> Bool {
|
|
id == offLocaleId
|
|
}
|
|
|
|
/// Resolve a stored locale id to its catalog entry. Falls back to
|
|
/// `offLocaleId` (the picker default) when the id is missing or
|
|
/// unknown — matches the pattern used elsewhere (e.g.
|
|
/// `ASRLocaleLabels`) so the keyboard never crashes on a stale
|
|
/// persisted value, and the picker lands on the safe "off" state
|
|
/// instead of an arbitrary language.
|
|
public static func resolve(_ id: String) -> TranslationLanguage {
|
|
if let match = all.first(where: { $0.id == id }) {
|
|
return match
|
|
}
|
|
return all.first { $0.id == offLocaleId } ?? all[0]
|
|
}
|
|
}
|