Skip to content

Commit 2418846

Browse files
committed
Conjugate: use dynamic conjugation and declension
1 parent 0ca13d6 commit 2418846

22 files changed

Lines changed: 1581 additions & 3056 deletions

Keyboards/DataContracts/de_declensions.json

Lines changed: 479 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"declensions": {
3+
"1": {
4+
"title": "Винительные местоимения",
5+
"forms": {
6+
"я": "меня",
7+
"ты": "тебя",
8+
"он/она/оно": "его/её/его",
9+
"мы": "нас",
10+
"вы": "вас",
11+
"они": "их"
12+
}
13+
},
14+
"2": {
15+
"title": "Дательные местоимения",
16+
"forms": {
17+
"я": "мне",
18+
"ты": "тебе",
19+
"он/она/оно": "ему/ей/ему",
20+
"мы": "нам",
21+
"вы": "вам",
22+
"они": "им"
23+
}
24+
},
25+
"3": {
26+
"title": "Родительные местоимения",
27+
"forms": {
28+
"я": "меня",
29+
"ты": "тебя",
30+
"он/она/оно": "его/её/его",
31+
"мы": "нас",
32+
"вы": "вас",
33+
"они": "их"
34+
}
35+
},
36+
"4": {
37+
"title": "Творительные местоимения",
38+
"forms": {
39+
"я": "мной",
40+
"ты": "тобой",
41+
"он/она/оно": "им/ей/им",
42+
"мы": "нами",
43+
"вы": "вами",
44+
"они": "ими"
45+
}
46+
},
47+
"5": {
48+
"title": "Предложные местоимения",
49+
"forms": {
50+
"я": "мне",
51+
"ты": "тебе",
52+
"он/она/оно": "нём/ней/нём",
53+
"мы": "нас",
54+
"вы": "вас",
55+
"они": "них"
56+
}
57+
}
58+
}
59+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
import Foundation
3+
4+
/// Manages retrieval of verb conjugations for different languages.
5+
6+
class ConjugationManager {
7+
static let shared = ConjugationManager()
8+
9+
private init() {}
10+
11+
/// Returns nested structure: [TenseTitle: [TypeTitle: [(pronoun, conjugatedForm)]]]
12+
func getConjugations(
13+
verb: String,
14+
language: String
15+
) -> [(String, [(String, [(String, String)])])]? {
16+
17+
let contract = ContractManager.shared.loadContract(language: language)
18+
19+
guard let conjugations = contract.conjugations else {
20+
return nil
21+
}
22+
23+
var result: [(String, [(String, [(String, String)])])] = []
24+
25+
for (_, tenseGroup) in conjugations.sorted(by: {
26+
Int($0.key) ?? 0 < Int($1.key) ?? 0
27+
}) {
28+
var conjugationTypes: [(String, [(String, String)])] = []
29+
30+
for (_, conjugationType) in tenseGroup.conjugationTypes.sorted(by: {
31+
Int($0.key) ?? 0 < Int($1.key) ?? 0
32+
}) {
33+
var forms: [(String, String)] = []
34+
35+
for (pronoun, columnName) in conjugationType.conjugationForms {
36+
let conjugatedForm = queryConjugatedForm(
37+
verb: verb,
38+
columnName: columnName,
39+
language: language
40+
)
41+
forms.append((pronoun, conjugatedForm))
42+
}
43+
44+
conjugationTypes.append((conjugationType.title, forms))
45+
}
46+
47+
result.append((tenseGroup.title, conjugationTypes))
48+
}
49+
50+
return result.isEmpty ? nil : result
51+
}
52+
53+
private func queryConjugatedForm(
54+
verb: String,
55+
columnName: String,
56+
language: String
57+
) -> String {
58+
if columnName.contains("[") {
59+
return parseComplexForm(verb: verb, columnName: columnName, language: language)
60+
} else {
61+
// Simple column query
62+
let results = LanguageDBManager.shared.queryVerb(of: verb, with: [columnName])
63+
let result = results.first ?? ""
64+
65+
return result
66+
}
67+
}
68+
69+
private func parseComplexForm(
70+
verb: String,
71+
columnName: String,
72+
language: String
73+
) -> String {
74+
// Extract "[auxiliaryPart]" and "mainColumn"
75+
let pattern = "\\[(.*?)\\]"
76+
guard let regex = try? NSRegularExpression(pattern: pattern),
77+
let match = regex.firstMatch(in: columnName, range: NSRange(columnName.startIndex..., in: columnName)) else {
78+
return ""
79+
}
80+
81+
let auxiliaryPart = (columnName as NSString).substring(with: match.range(at: 1))
82+
let mainColumn = columnName.replacingOccurrences(of: pattern, with: "", options: .regularExpression)
83+
.trimmingCharacters(in: .whitespaces)
84+
85+
// Get the main verb form (e.g., "gegangen")
86+
guard let mainForm = LanguageDBManager.shared.queryVerb(of: verb, with: [mainColumn]).first,
87+
!mainForm.isEmpty else {
88+
return ""
89+
}
90+
91+
// Check if it's dynamic lookup (German style: "indicativePresentFirstPersonSingular auxiliaryVerb")
92+
let auxWords = auxiliaryPart.split(separator: " ")
93+
if auxWords.count > 1 {
94+
let targetForm = String(auxWords.first!) // e.g., "indicativePresentFirstPersonSingular"
95+
let auxColumn = String(auxWords.last!) // e.g., "auxiliaryVerb"
96+
97+
// Get the auxiliary verb identifier
98+
if let auxVerbId = LanguageDBManager.shared.queryVerb(of: verb, with: [auxColumn]).first,
99+
!auxVerbId.isEmpty {
100+
101+
// Try querying by wdLexemeId first
102+
var auxConjugated = LanguageDBManager.shared.queryVerb(
103+
of: auxVerbId,
104+
identifierColumn: "wdLexemeId",
105+
with: [targetForm]
106+
).first
107+
108+
// Fallback: try by infinitive
109+
if auxConjugated?.isEmpty ?? true {
110+
auxConjugated = LanguageDBManager.shared.queryVerb(
111+
of: auxVerbId,
112+
with: [targetForm]
113+
).first
114+
}
115+
116+
if let auxConjugated = auxConjugated, !auxConjugated.isEmpty {
117+
return "\(auxConjugated) \(mainForm)"
118+
}
119+
}
120+
}
121+
122+
// Fallback: use auxiliary as-is
123+
return "\(auxiliaryPart) \(mainForm)"
124+
}
125+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// SPDX-License-Identifier: GPL-3.0-or-later
2+
3+
import Foundation
4+
5+
/// Manages loading and parsing of declension contracts
6+
class DeclensionManager {
7+
static let shared = DeclensionManager()
8+
9+
private init() {}
10+
11+
/// Loads declension cases for a language
12+
func loadDeclensions(language: String) -> [NavigationLevel]? {
13+
let languageCode = language.lowercased()
14+
15+
// Load declensions JSON file (e.g., "de_declensions.json")
16+
guard let jsonResourcePath = Bundle.main.path(
17+
forResource: "\(languageCode)_declensions",
18+
ofType: "json"
19+
) else {
20+
NSLog("Declensions not found: \(languageCode)_declensions.json")
21+
return nil
22+
}
23+
24+
do {
25+
let data = try Data(contentsOf: URL(fileURLWithPath: jsonResourcePath))
26+
guard let json = try JSONSerialization.jsonObject(with: data) as? [String: Any],
27+
let declensionsDict = json["declensions"] as? [String: [String: Any]] else {
28+
return nil
29+
}
30+
31+
// Sort by numeric keys
32+
let sortedKeys = declensionsDict.keys.sorted { Int($0) ?? 0 < Int($1) ?? 0 }
33+
34+
var cases: [NavigationLevel] = []
35+
36+
for key in sortedKeys {
37+
guard let declension = declensionsDict[key],
38+
let title = declension["title"] as? String,
39+
let forms = declension["forms"] as? [String: Any] else {
40+
continue
41+
}
42+
43+
let options = parseForms(forms, parentTitle: title)
44+
cases.append(NavigationLevel(title: title, options: options))
45+
}
46+
47+
return cases
48+
} catch {
49+
NSLog("Error loading declensions \(languageCode)/declensions.json: \(error)")
50+
return nil
51+
}
52+
}
53+
54+
/// Recursively parses forms with optional displayValue
55+
private func parseForms(
56+
_ forms: [String: Any],
57+
parentTitle: String
58+
) -> [(label: String, node: NavigationNode)] {
59+
var options: [(label: String, node: NavigationNode)] = []
60+
61+
// Sort keys to maintain consistent order
62+
let sortedKeys = forms.keys.sorted()
63+
64+
for key in sortedKeys {
65+
guard let value = forms[key] else { continue }
66+
67+
if let stringValue = value as? String {
68+
// Final value
69+
options.append((label: key, node: .finalValue(stringValue)))
70+
71+
} else if let nestedDict = value as? [String: Any],
72+
let title = nestedDict["title"] as? String,
73+
let nestedForms = nestedDict["forms"] as? [String: Any] {
74+
// Has nested forms - need to navigate deeper
75+
76+
// Check for explicit displayValue, otherwise auto-generate
77+
let displayValue: String
78+
if let explicit = nestedDict["displayValue"] as? String {
79+
displayValue = explicit
80+
} else {
81+
displayValue = autoGenerateDisplayValue(from: nestedForms)
82+
}
83+
84+
let suboptions = parseForms(nestedForms, parentTitle: title)
85+
let nextLevel = NavigationLevel(title: title, options: suboptions)
86+
87+
options.append((label: key, node: .nextLevel(nextLevel, displayValue: displayValue)))
88+
}
89+
}
90+
91+
return options
92+
}
93+
94+
/// Auto-generates display value by joining direct child values
95+
private func autoGenerateDisplayValue(from forms: [String: Any]) -> String {
96+
var values: [String] = []
97+
98+
// Sort keys to maintain consistent order
99+
let sortedKeys = forms.keys.sorted()
100+
101+
for key in sortedKeys {
102+
guard let value = forms[key] else { continue }
103+
104+
if let stringValue = value as? String {
105+
// Direct string value - use it
106+
values.append(stringValue)
107+
}
108+
}
109+
110+
return values.joined(separator: "/")
111+
}
112+
}

Keyboards/DataManager/LanguageDBManager.swift

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ extension LanguageDBManager {
420420

421421
/// Query the verb form of word in `verbs`.
422422
func queryVerb(of word: String) -> [String] {
423+
let columnName = (controllerLanguage == "Swedish") ? "verb" : "infinitive"
423424
let query = """
424425
SELECT
425426
*
@@ -428,9 +429,9 @@ extension LanguageDBManager {
428429
verbs
429430
430431
WHERE
431-
verb = ?
432+
\(columnName) = ?
432433
"""
433-
let outputCols = ["verb"]
434+
let outputCols = [columnName]
434435
let args = [word]
435436

436437
return queryDBRow(query: query, outputCols: outputCols, args: StatementArguments(args))
@@ -439,8 +440,11 @@ extension LanguageDBManager {
439440
/// Query specific form of word in `verbs`.
440441
///
441442
/// - Parameters:
443+
/// - word: The value to search for
444+
/// - identifierColumn: The column to search in (default: "infinitive" or "verb" for Swedish)
442445
/// - outputCols: Specific form want to output
443-
func queryVerb(of word: String, with outputCols: [String]) -> [String] {
446+
func queryVerb(of word: String, identifierColumn: String? = nil, with outputCols: [String]) -> [String] {
447+
let columnName = identifierColumn ?? ((controllerLanguage == "Swedish") ? "verb" : "infinitive")
444448
let query = """
445449
SELECT
446450
*
@@ -449,7 +453,7 @@ extension LanguageDBManager {
449453
verbs
450454
451455
WHERE
452-
verb = ?
456+
\(columnName) = ?
453457
"""
454458
let args = [word]
455459

0 commit comments

Comments
 (0)