Add language catalog selector

This commit is contained in:
diyaa
2026-09-15 23:20:48 +02:00
parent b43c5416f2
commit c8242a844a
10 changed files with 425 additions and 14 deletions
@@ -535,6 +535,32 @@ public struct LanguageSelection: Codable, Equatable, Identifiable, Sendable {
}
}
public struct LanguageCatalogItem: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
public var familyCategory: String
public var aliases: [String]
public var searchTerms: [String]
public init(
id: String,
name: String,
familyCategory: String,
aliases: [String] = [],
searchTerms: [String] = []
) {
self.id = id
self.name = name
self.familyCategory = familyCategory
self.aliases = aliases
self.searchTerms = searchTerms
}
public var language: LanguageSelection {
LanguageSelection(id: id, name: name)
}
}
public struct DialectSelection: Codable, Equatable, Identifiable, Sendable {
public let id: String
public var name: String
@@ -0,0 +1,172 @@
import Foundation
public struct LocalLanguageCatalog: Sendable {
public let items: [LanguageCatalogItem]
public init(items: [LanguageCatalogItem] = LocalLanguageCatalog.worldwideItems) {
self.items = items
}
public var familyCategories: [String] {
Array(Set(items.map(\.familyCategory)))
.sorted { $0.localizedCaseInsensitiveCompare($1) == .orderedAscending }
}
public func languages(
matching query: String,
familyCategory: String? = nil
) -> [LanguageCatalogItem] {
let searchTokens = Self.searchTokens(for: query)
let selectedFamilyCategory = familyCategory?.trimmingCharacters(in: .whitespacesAndNewlines)
return items
.filter { item in
let matchesSearch = searchTokens.isEmpty || searchTokens.allSatisfy { token in
Self.searchableTerms(for: item).contains { $0.contains(token) }
}
let matchesFamily = selectedFamilyCategory?.isEmpty != false || item.familyCategory == selectedFamilyCategory
return matchesSearch && matchesFamily
}
.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
}
public static let worldwideItems: [LanguageCatalogItem] =
middleEastAndNorthAfrica + europe + southAsia + eastAsia + southeastAsia + africa + americas + oceaniaAndPacific
private static let middleEastAndNorthAfrica: [LanguageCatalogItem] = [
item("arabic", "Arabic", "Middle East & North Africa", aliases: ["العربية"], terms: ["عربي"]),
item("hebrew", "Hebrew", "Middle East & North Africa", aliases: ["עברית"]),
item("persian", "Persian", "Middle East & North Africa", aliases: ["Farsi", "فارسی"]),
item("turkish", "Turkish", "Middle East & North Africa", aliases: ["Türkçe"]),
item("kurdish", "Kurdish", "Middle East & North Africa", aliases: ["Kurdî"]),
item("armenian", "Armenian", "Middle East & North Africa"),
item("azerbaijani", "Azerbaijani", "Middle East & North Africa", aliases: ["Azeri"]),
item("tamazight", "Tamazight", "Middle East & North Africa", aliases: ["Berber"])
]
private static let europe: [LanguageCatalogItem] = [
item("english", "English", "Europe", aliases: ["English language"]),
item("german", "German", "Europe", aliases: ["Deutsch"]),
item("french", "French", "Europe", aliases: ["Français"]),
item("spanish", "Spanish", "Europe", aliases: ["Español"]),
item("portuguese", "Portuguese", "Europe", aliases: ["Português"]),
item("italian", "Italian", "Europe", aliases: ["Italiano"]),
item("russian", "Russian", "Europe", aliases: ["Русский"]),
item("ukrainian", "Ukrainian", "Europe", aliases: ["Українська"]),
item("polish", "Polish", "Europe", aliases: ["Polski"]),
item("dutch", "Dutch", "Europe", aliases: ["Nederlands"]),
item("swedish", "Swedish", "Europe", aliases: ["Svenska"]),
item("norwegian", "Norwegian", "Europe", aliases: ["Norsk"]),
item("danish", "Danish", "Europe", aliases: ["Dansk"]),
item("finnish", "Finnish", "Europe", aliases: ["Suomi"]),
item("greek", "Greek", "Europe", aliases: ["Ελληνικά"]),
item("romanian", "Romanian", "Europe", aliases: ["Română"]),
item("czech", "Czech", "Europe", aliases: ["Čeština"]),
item("hungarian", "Hungarian", "Europe", aliases: ["Magyar"]),
item("serbian", "Serbian", "Europe"),
item("croatian", "Croatian", "Europe"),
item("bulgarian", "Bulgarian", "Europe")
]
private static let southAsia: [LanguageCatalogItem] = [
item("hindi", "Hindi", "South Asia", aliases: ["हिन्दी"]),
item("urdu", "Urdu", "South Asia", aliases: ["اردو"]),
item("punjabi", "Punjabi", "South Asia", aliases: ["ਪੰਜਾਬੀ"]),
item("bengali", "Bengali", "South Asia", aliases: ["বাংলা"]),
item("tamil", "Tamil", "South Asia", aliases: ["தமிழ்"]),
item("telugu", "Telugu", "South Asia", aliases: ["తెలుగు"]),
item("marathi", "Marathi", "South Asia", aliases: ["मराठी"]),
item("gujarati", "Gujarati", "South Asia", aliases: ["ગુજરાતી"]),
item("malayalam", "Malayalam", "South Asia", aliases: ["മലയാളം"]),
item("kannada", "Kannada", "South Asia", aliases: ["ಕನ್ನಡ"]),
item("sinhala", "Sinhala", "South Asia"),
item("nepali", "Nepali", "South Asia", aliases: ["नेपाली"]),
item("pashto", "Pashto", "South Asia", aliases: ["پښتو"])
]
private static let eastAsia: [LanguageCatalogItem] = [
item("mandarin-chinese", "Mandarin Chinese", "East Asia", aliases: ["Mandarin", "中文", "普通话"]),
item("cantonese", "Cantonese", "East Asia", aliases: ["廣東話"]),
item("japanese", "Japanese", "East Asia", aliases: ["日本語"]),
item("korean", "Korean", "East Asia", aliases: ["한국어"]),
item("mongolian", "Mongolian", "East Asia")
]
private static let southeastAsia: [LanguageCatalogItem] = [
item("indonesian", "Indonesian", "Southeast Asia", aliases: ["Bahasa Indonesia"]),
item("malay", "Malay", "Southeast Asia", aliases: ["Bahasa Melayu"]),
item("vietnamese", "Vietnamese", "Southeast Asia", aliases: ["Tiếng Việt"]),
item("thai", "Thai", "Southeast Asia", aliases: ["ไทย"]),
item("tagalog", "Tagalog", "Southeast Asia", aliases: ["Filipino"]),
item("burmese", "Burmese", "Southeast Asia", aliases: ["Myanmar"]),
item("khmer", "Khmer", "Southeast Asia"),
item("lao", "Lao", "Southeast Asia"),
item("javanese", "Javanese", "Southeast Asia")
]
private static let africa: [LanguageCatalogItem] = [
item("swahili", "Swahili", "Africa", aliases: ["Kiswahili"]),
item("amharic", "Amharic", "Africa", aliases: ["አማርኛ"]),
item("hausa", "Hausa", "Africa"),
item("yoruba", "Yoruba", "Africa"),
item("igbo", "Igbo", "Africa"),
item("zulu", "Zulu", "Africa"),
item("xhosa", "Xhosa", "Africa"),
item("somali", "Somali", "Africa"),
item("oromo", "Oromo", "Africa"),
item("afrikaans", "Afrikaans", "Africa"),
item("wolof", "Wolof", "Africa"),
item("shona", "Shona", "Africa")
]
private static let americas: [LanguageCatalogItem] = [
item("haitian-creole", "Haitian Creole", "Americas", aliases: ["Kreyòl"]),
item("quechua", "Quechua", "Americas"),
item("guarani", "Guarani", "Americas"),
item("navajo", "Navajo", "Americas"),
item("inuktitut", "Inuktitut", "Americas")
]
private static let oceaniaAndPacific: [LanguageCatalogItem] = [
item("maori", "Maori", "Oceania & Pacific", aliases: ["Te Reo Māori"]),
item("hawaiian", "Hawaiian", "Oceania & Pacific"),
item("samoan", "Samoan", "Oceania & Pacific"),
item("tongan", "Tongan", "Oceania & Pacific"),
item("fijian", "Fijian", "Oceania & Pacific")
]
private static func item(
_ id: String,
_ name: String,
_ familyCategory: String,
aliases: [String] = [],
terms: [String] = []
) -> LanguageCatalogItem {
LanguageCatalogItem(
id: id,
name: name,
familyCategory: familyCategory,
aliases: aliases,
searchTerms: terms
)
}
private static func searchableTerms(for item: LanguageCatalogItem) -> [String] {
searchTokens(for: [item.name, item.familyCategory] + item.aliases + item.searchTerms)
}
private static func searchTokens(for query: String) -> [String] {
searchTokens(for: [query])
}
private static func searchTokens(for values: [String]) -> [String] {
values
.joined(separator: " ")
.folding(
options: [.caseInsensitive, .diacriticInsensitive, .widthInsensitive],
locale: Locale(identifier: "en_US_POSIX")
)
.split(whereSeparator: { $0.isWhitespace || $0 == "-" })
.map(String.init)
}
}