ru-api-free 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ru_api_free/__init__.py +9 -0
- ru_api_free/__main__.py +10 -0
- ru_api_free/conjugator.py +758 -0
- ru_api_free/dictionary.py +21 -0
- ru_api_free/main.py +95 -0
- ru_api_free/static/index.html +213 -0
- ru_api_free-1.0.0.dist-info/METADATA +82 -0
- ru_api_free-1.0.0.dist-info/RECORD +12 -0
- ru_api_free-1.0.0.dist-info/WHEEL +5 -0
- ru_api_free-1.0.0.dist-info/entry_points.txt +2 -0
- ru_api_free-1.0.0.dist-info/licenses/LICENSE +21 -0
- ru_api_free-1.0.0.dist-info/top_level.txt +1 -0
ru_api_free/__init__.py
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from .conjugator import conjugate, search_verbs, list_verbs, get_exceptions, get_verb_exceptions, EXCEPTION_TYPES
|
|
2
|
+
from .dictionary import translate_en_to_ru
|
|
3
|
+
|
|
4
|
+
__version__ = "1.0.0"
|
|
5
|
+
__all__ = [
|
|
6
|
+
"conjugate", "search_verbs", "list_verbs",
|
|
7
|
+
"get_exceptions", "get_verb_exceptions", "EXCEPTION_TYPES",
|
|
8
|
+
"translate_en_to_ru",
|
|
9
|
+
]
|
ru_api_free/__main__.py
ADDED
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
# Russian Verb Conjugator API
|
|
2
|
+
|
|
3
|
+
PERSONS = ["я", "ты", "он/она/оно", "мы", "вы", "они"]
|
|
4
|
+
SIBILANTS = set("жшчщ")
|
|
5
|
+
|
|
6
|
+
IRR = {}
|
|
7
|
+
|
|
8
|
+
EXCEPTION_TYPES = {
|
|
9
|
+
"irregular": "Full conjugation is irregular (does not follow standard patterns)",
|
|
10
|
+
"conj_class": "2nd conjugation despite having a 1st-conjugation ending (-еть/-ать)",
|
|
11
|
+
"mut_1sg": "Consonant mutation in 1st person singular",
|
|
12
|
+
"stem_change": "Stem changes in present/future tense (-овать, -ва-, consonant-j, etc.)",
|
|
13
|
+
"sibilant_stem": "Sibilant/fricative stem alternation in present/future",
|
|
14
|
+
"past_irreg": "Irregular past tense form",
|
|
15
|
+
"nut_verb": "-нуть verb (perfective with special stem)",
|
|
16
|
+
"reflexive": "Reflexive verb (-ся)",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
EXCEPTIONS = {}
|
|
20
|
+
|
|
21
|
+
def ir(verb, present, past, future=None):
|
|
22
|
+
IRR[verb] = {"present": present, "past": past, "future": future}
|
|
23
|
+
|
|
24
|
+
# --- All irregular/conjugation-table verbs ---
|
|
25
|
+
ir("быть",
|
|
26
|
+
{"я": "есть", "ты": "есть", "он/она/оно": "есть", "мы": "есть", "вы": "есть", "они": "есть"},
|
|
27
|
+
{"мужской": "был", "женский": "была", "средний": "было", "множественное": "были"},
|
|
28
|
+
{"я": "буду", "ты": "будешь", "он/она/оно": "будет", "мы": "будем", "вы": "будете", "они": "будут"})
|
|
29
|
+
|
|
30
|
+
ir("дать", None,
|
|
31
|
+
{"мужской": "дал", "женский": "дала", "средний": "дало", "множественное": "дали"},
|
|
32
|
+
{"я": "дам", "ты": "дашь", "он/она/оно": "даст", "мы": "дадим", "вы": "дадите", "они": "дадут"})
|
|
33
|
+
|
|
34
|
+
ir("есть",
|
|
35
|
+
{"я": "ем", "ты": "ешь", "он/она/оно": "ест", "мы": "едим", "вы": "едите", "они": "едят"},
|
|
36
|
+
{"мужской": "ел", "женский": "ела", "средний": "ело", "множественное": "ели"})
|
|
37
|
+
|
|
38
|
+
ir("хотеть",
|
|
39
|
+
{"я": "хочу", "ты": "хочешь", "он/она/оно": "хочет", "мы": "хотим", "вы": "хотите", "они": "хотят"},
|
|
40
|
+
{"мужской": "хотел", "женский": "хотела", "средний": "хотело", "множественное": "хотели"})
|
|
41
|
+
|
|
42
|
+
ir("ехать",
|
|
43
|
+
{"я": "еду", "ты": "едешь", "он/она/оно": "едет", "мы": "едем", "вы": "едете", "они": "едут"},
|
|
44
|
+
{"мужской": "ехал", "женский": "ехала", "средний": "ехало", "множественное": "ехали"})
|
|
45
|
+
|
|
46
|
+
ir("ошибиться", None,
|
|
47
|
+
{"мужской": "ошибся", "женский": "ошиблась", "средний": "ошиблось", "множественное": "ошиблись"},
|
|
48
|
+
{"я": "ошибусь", "ты": "ошибёшься", "он/она/оно": "ошибётся", "мы": "ошибёмся", "вы": "ошибётесь", "они": "ошибутся"})
|
|
49
|
+
|
|
50
|
+
ir("взять", None,
|
|
51
|
+
{"мужской": "взял", "женский": "взяла", "средний": "взяло", "множественное": "взяли"},
|
|
52
|
+
{"я": "возьму", "ты": "возьмёшь", "он/она/оно": "возьмёт", "мы": "возьмём", "вы": "возьмёте", "они": "возьмут"})
|
|
53
|
+
|
|
54
|
+
ir("понять", None,
|
|
55
|
+
{"мужской": "понял", "женский": "поняла", "средний": "поняло", "множественное": "поняли"},
|
|
56
|
+
{"я": "пойму", "ты": "поймёшь", "он/она/оно": "поймёт", "мы": "поймём", "вы": "поймёте", "они": "поймут"})
|
|
57
|
+
|
|
58
|
+
ir("начать", None,
|
|
59
|
+
{"мужской": "начал", "женский": "начала", "средний": "начало", "множественное": "начали"},
|
|
60
|
+
{"я": "начну", "ты": "начнёшь", "он/она/оно": "начнёт", "мы": "начнём", "вы": "начнёте", "они": "начнут"})
|
|
61
|
+
|
|
62
|
+
ir("бежать",
|
|
63
|
+
{"я": "бегу", "ты": "бежишь", "он/она/оно": "бежит", "мы": "бежим", "вы": "бежите", "они": "бегут"},
|
|
64
|
+
{"мужской": "бежал", "женский": "бежала", "средний": "бежало", "множественное": "бежали"})
|
|
65
|
+
|
|
66
|
+
# -ти verbs
|
|
67
|
+
ir("идти",
|
|
68
|
+
{"я": "иду", "ты": "идёшь", "он/она/оно": "идёт", "мы": "идём", "вы": "идёте", "они": "идут"},
|
|
69
|
+
{"мужской": "шёл", "женский": "шла", "средний": "шло", "множественное": "шли"})
|
|
70
|
+
|
|
71
|
+
ir("прийти", None,
|
|
72
|
+
{"мужской": "пришёл", "женский": "пришла", "средний": "пришло", "множественное": "пришли"},
|
|
73
|
+
{"я": "приду", "ты": "придёшь", "он/она/оно": "придёт", "мы": "придём", "вы": "придёте", "они": "придут"})
|
|
74
|
+
|
|
75
|
+
ir("пройти", None,
|
|
76
|
+
{"мужской": "прошёл", "женский": "прошла", "средний": "прошло", "множественное": "прошли"},
|
|
77
|
+
{"я": "пройду", "ты": "пройдёшь", "он/она/оно": "пройдёт", "мы": "пройдём", "вы": "пройдёте", "они": "пройдут"})
|
|
78
|
+
|
|
79
|
+
ir("найти", None,
|
|
80
|
+
{"мужской": "нашёл", "женский": "нашла", "средний": "нашло", "множественное": "нашли"},
|
|
81
|
+
{"я": "найду", "ты": "найдёшь", "он/она/оно": "найдёт", "мы": "найдём", "вы": "найдёте", "они": "найдут"})
|
|
82
|
+
|
|
83
|
+
ir("нести",
|
|
84
|
+
{"я": "несу", "ты": "несёшь", "он/она/оно": "несёт", "мы": "несём", "вы": "несёте", "они": "несут"},
|
|
85
|
+
{"мужской": "нёс", "женский": "несла", "средний": "несло", "множественное": "несли"})
|
|
86
|
+
|
|
87
|
+
ir("везти",
|
|
88
|
+
{"я": "везу", "ты": "везёшь", "он/она/оно": "везёт", "мы": "везём", "вы": "везёте", "они": "везут"},
|
|
89
|
+
{"мужской": "вёз", "женский": "везла", "средний": "везло", "множественное": "везли"})
|
|
90
|
+
|
|
91
|
+
ir("вести",
|
|
92
|
+
{"я": "веду", "ты": "ведёшь", "он/она/оно": "ведёт", "мы": "ведём", "вы": "ведёте", "они": "ведут"},
|
|
93
|
+
{"мужской": "вёл", "женский": "вела", "средний": "вело", "множественное": "вели"})
|
|
94
|
+
|
|
95
|
+
ir("ползти",
|
|
96
|
+
{"я": "ползу", "ты": "ползёшь", "он/она/оно": "ползёт", "мы": "ползём", "вы": "ползёте", "они": "ползут"},
|
|
97
|
+
{"мужской": "полз", "женский": "ползла", "средний": "ползло", "множественное": "ползли"})
|
|
98
|
+
|
|
99
|
+
ir("расти",
|
|
100
|
+
{"я": "расту", "ты": "растёшь", "он/она/оно": "растёт", "мы": "растём", "вы": "растёте", "они": "растут"},
|
|
101
|
+
{"мужской": "рос", "женский": "росла", "средний": "росло", "множественное": "росли"})
|
|
102
|
+
|
|
103
|
+
ir("цвести",
|
|
104
|
+
{"я": "цвету", "ты": "цветёшь", "он/она/оно": "цветёт", "мы": "цветём", "вы": "цветёте", "они": "цветут"},
|
|
105
|
+
{"мужской": "цвёл", "женский": "цвела", "средний": "цвело", "множественное": "цвели"})
|
|
106
|
+
|
|
107
|
+
ir("класть",
|
|
108
|
+
{"я": "кладу", "ты": "кладёшь", "он/она/оно": "кладёт", "мы": "кладём", "вы": "кладёте", "они": "кладут"},
|
|
109
|
+
{"мужской": "клал", "женский": "клала", "средний": "клало", "множественное": "клали"})
|
|
110
|
+
|
|
111
|
+
ir("лезть",
|
|
112
|
+
{"я": "лезу", "ты": "лезешь", "он/она/оно": "лезет", "мы": "лезем", "вы": "лезете", "они": "лезут"},
|
|
113
|
+
{"мужской": "лез", "женский": "лезла", "средний": "лезло", "множественное": "лезли"})
|
|
114
|
+
|
|
115
|
+
# -чь verbs
|
|
116
|
+
ir("мочь",
|
|
117
|
+
{"я": "могу", "ты": "можешь", "он/она/оно": "может", "мы": "можем", "вы": "можете", "они": "могут"},
|
|
118
|
+
{"мужской": "мог", "женский": "могла", "средний": "могло", "множественное": "могли"})
|
|
119
|
+
|
|
120
|
+
ir("помочь", None,
|
|
121
|
+
{"мужской": "помог", "женский": "помогла", "средний": "помогло", "множественное": "помогли"},
|
|
122
|
+
{"я": "помогу", "ты": "поможешь", "он/она/оно": "поможет", "мы": "поможем", "вы": "поможете", "они": "помогут"})
|
|
123
|
+
|
|
124
|
+
ir("беречь",
|
|
125
|
+
{"я": "берегу", "ты": "бережёшь", "он/она/оно": "бережёт", "мы": "бережём", "вы": "бережёте", "они": "берегут"},
|
|
126
|
+
{"мужской": "берёг", "женский": "берегла", "средний": "берегло", "множественное": "берегли"})
|
|
127
|
+
|
|
128
|
+
ir("печь",
|
|
129
|
+
{"я": "пеку", "ты": "печёшь", "он/она/оно": "печёт", "мы": "печём", "вы": "печёте", "они": "пекут"},
|
|
130
|
+
{"мужской": "пёк", "женский": "пекла", "средний": "пекло", "множественное": "пекли"})
|
|
131
|
+
|
|
132
|
+
ir("течь",
|
|
133
|
+
{"я": "теку", "ты": "течёшь", "он/она/оно": "течёт", "мы": "течём", "вы": "течёте", "они": "текут"},
|
|
134
|
+
{"мужской": "тёк", "женский": "текла", "средний": "текло", "множественное": "текли"})
|
|
135
|
+
|
|
136
|
+
ir("сечь",
|
|
137
|
+
{"я": "секу", "ты": "сечёшь", "он/она/оно": "сечёт", "мы": "сечём", "вы": "сечёте", "они": "секут"},
|
|
138
|
+
{"мужской": "сёк", "женский": "секла", "средний": "секло", "множественное": "секли"})
|
|
139
|
+
|
|
140
|
+
ir("жечь",
|
|
141
|
+
{"я": "жгу", "ты": "жжёшь", "он/она/оно": "жжёт", "мы": "жжём", "вы": "жжёте", "они": "жгут"},
|
|
142
|
+
{"мужской": "жёг", "женский": "жгла", "средний": "жгло", "множественное": "жгли"})
|
|
143
|
+
|
|
144
|
+
ir("стричь",
|
|
145
|
+
{"я": "стригу", "ты": "стрижёшь", "он/она/оно": "стрижёт", "мы": "стрижём", "вы": "стрижёте", "они": "стригут"},
|
|
146
|
+
{"мужской": "стриг", "женский": "стригла", "средний": "стригло", "множественное": "стригли"})
|
|
147
|
+
|
|
148
|
+
ir("влечь",
|
|
149
|
+
{"я": "влеку", "ты": "влечёшь", "он/она/оно": "влечёт", "мы": "влечём", "вы": "влечёте", "они": "влекут"},
|
|
150
|
+
{"мужской": "влёк", "женский": "влекла", "средний": "влекло", "множественное": "влекли"})
|
|
151
|
+
|
|
152
|
+
# Consonant-j-stem verbs (бить, пить, etc.)
|
|
153
|
+
ir("бить",
|
|
154
|
+
{"я": "бью", "ты": "бьёшь", "он/она/оно": "бьёт", "мы": "бьём", "вы": "бьёте", "они": "бьют"},
|
|
155
|
+
{"мужской": "бил", "женский": "била", "средний": "било", "множественное": "били"})
|
|
156
|
+
|
|
157
|
+
ir("пить",
|
|
158
|
+
{"я": "пью", "ты": "пьёшь", "он/она/оно": "пьёт", "мы": "пьём", "вы": "пьёте", "они": "пьют"},
|
|
159
|
+
{"мужской": "пил", "женский": "пила", "средний": "пило", "множественное": "пили"})
|
|
160
|
+
|
|
161
|
+
ir("лить",
|
|
162
|
+
{"я": "лью", "ты": "льёшь", "он/она/оно": "льёт", "мы": "льём", "вы": "льёте", "они": "льют"},
|
|
163
|
+
{"мужской": "лил", "женский": "лила", "средний": "лило", "множественное": "лили"})
|
|
164
|
+
|
|
165
|
+
ir("шить",
|
|
166
|
+
{"я": "шью", "ты": "шьёшь", "он/она/оно": "шьёт", "мы": "шьём", "вы": "шьёте", "они": "шьют"},
|
|
167
|
+
{"мужской": "шил", "женский": "шила", "средний": "шило", "множественное": "шили"})
|
|
168
|
+
|
|
169
|
+
ir("вить",
|
|
170
|
+
{"я": "вью", "ты": "вьёшь", "он/она/оно": "вьёт", "мы": "вьём", "вы": "вьёте", "они": "вьют"},
|
|
171
|
+
{"мужской": "вил", "женский": "вила", "средний": "вило", "множественное": "вили"})
|
|
172
|
+
|
|
173
|
+
ir("брить",
|
|
174
|
+
{"я": "брею", "ты": "бреешь", "он/она/оно": "бреет", "мы": "бреем", "вы": "бреете", "они": "бреют"},
|
|
175
|
+
{"мужской": "брил", "женский": "брила", "средний": "брило", "множественное": "брили"})
|
|
176
|
+
|
|
177
|
+
ir("жить",
|
|
178
|
+
{"я": "живу", "ты": "живёшь", "он/она/оно": "живёт", "мы": "живём", "вы": "живёте", "они": "живут"},
|
|
179
|
+
{"мужской": "жил", "женский": "жила", "средний": "жило", "множественное": "жили"})
|
|
180
|
+
|
|
181
|
+
ir("плыть",
|
|
182
|
+
{"я": "плыву", "ты": "плывёшь", "он/она/оно": "плывёт", "мы": "плывём", "вы": "плывёте", "они": "плывут"},
|
|
183
|
+
{"мужской": "плыл", "женский": "плыла", "средний": "плыло", "множественное": "плыли"})
|
|
184
|
+
|
|
185
|
+
ir("слыть",
|
|
186
|
+
{"я": "слыву", "ты": "слывёшь", "он/она/оно": "слывёт", "мы": "слывём", "вы": "слывёте", "они": "слывут"},
|
|
187
|
+
{"мужской": "слыл", "женский": "слыла", "средний": "слыло", "множественное": "слыли"})
|
|
188
|
+
|
|
189
|
+
ir("петь",
|
|
190
|
+
{"я": "пою", "ты": "поёшь", "он/она/оно": "поёт", "мы": "поём", "вы": "поёте", "они": "поют"},
|
|
191
|
+
{"мужской": "пел", "женский": "пела", "средний": "пело", "множественное": "пели"})
|
|
192
|
+
|
|
193
|
+
ir("брать",
|
|
194
|
+
{"я": "беру", "ты": "берёшь", "он/она/оно": "берёт", "мы": "берём", "вы": "берёте", "они": "берут"},
|
|
195
|
+
{"мужской": "брал", "женский": "брала", "средний": "брало", "множественное": "брали"})
|
|
196
|
+
|
|
197
|
+
ir("ждать",
|
|
198
|
+
{"я": "жду", "ты": "ждёшь", "он/она/оно": "ждёт", "мы": "ждём", "вы": "ждёте", "они": "ждут"},
|
|
199
|
+
{"мужской": "ждал", "женский": "ждала", "средний": "ждало", "множественное": "ждали"})
|
|
200
|
+
|
|
201
|
+
ir("звать",
|
|
202
|
+
{"я": "зову", "ты": "зовёшь", "он/она/оно": "зовёт", "мы": "зовём", "вы": "зовёте", "они": "зовут"},
|
|
203
|
+
{"мужской": "звал", "женский": "звала", "средний": "звало", "множественное": "звали"})
|
|
204
|
+
|
|
205
|
+
ir("слать",
|
|
206
|
+
{"я": "шлю", "ты": "шлёшь", "он/она/оно": "шлёт", "мы": "шлём", "вы": "шлёте", "они": "шлют"},
|
|
207
|
+
{"мужской": "слал", "женский": "слала", "средний": "слало", "множественное": "слали"})
|
|
208
|
+
|
|
209
|
+
# -ва- insert verbs
|
|
210
|
+
ir("вставать",
|
|
211
|
+
{"я": "встаю", "ты": "встаёшь", "он/она/оно": "встаёт", "мы": "встаём", "вы": "встаёте", "они": "встают"},
|
|
212
|
+
{"мужской": "вставал", "женский": "вставала", "средний": "вставало", "множественное": "вставали"})
|
|
213
|
+
|
|
214
|
+
ir("давать",
|
|
215
|
+
{"я": "даю", "ты": "даёшь", "он/она/оно": "даёт", "мы": "даём", "вы": "даёте", "они": "дают"},
|
|
216
|
+
{"мужской": "давал", "женский": "давала", "средний": "давало", "множественное": "давали"})
|
|
217
|
+
|
|
218
|
+
ir("узнавать",
|
|
219
|
+
{"я": "узнаю", "ты": "узнаёшь", "он/она/оно": "узнаёт", "мы": "узнаём", "вы": "узнаёте", "они": "узнают"},
|
|
220
|
+
{"мужской": "узнавал", "женский": "узнавала", "средний": "узнавало", "множественное": "узнавали"})
|
|
221
|
+
|
|
222
|
+
ir("продавать",
|
|
223
|
+
{"я": "продаю", "ты": "продаёшь", "он/она/оно": "продаёт", "мы": "продаём", "вы": "продаёте", "они": "продают"},
|
|
224
|
+
{"мужской": "продавал", "женский": "продавала", "средний": "продавало", "множественное": "продавали"})
|
|
225
|
+
|
|
226
|
+
ir("создавать",
|
|
227
|
+
{"я": "создаю", "ты": "создаёшь", "он/она/оно": "создаёт", "мы": "создаём", "вы": "создаёте", "они": "создают"},
|
|
228
|
+
{"мужской": "создавал", "женский": "создавала", "средний": "создавало", "множественное": "создавали"})
|
|
229
|
+
|
|
230
|
+
# -ыть verbs
|
|
231
|
+
ir("мыть",
|
|
232
|
+
{"я": "мою", "ты": "моешь", "он/она/оно": "моет", "мы": "моем", "вы": "моете", "они": "моют"},
|
|
233
|
+
{"мужской": "мыл", "женский": "мыла", "средний": "мыло", "множественное": "мыли"})
|
|
234
|
+
|
|
235
|
+
ir("крыть",
|
|
236
|
+
{"я": "крою", "ты": "кроешь", "он/она/оно": "кроет", "мы": "кроем", "вы": "кроете", "они": "кроют"},
|
|
237
|
+
{"мужской": "крыл", "женский": "крыла", "средний": "крыло", "множественное": "крыли"})
|
|
238
|
+
|
|
239
|
+
ir("рыть",
|
|
240
|
+
{"я": "рою", "ты": "роешь", "он/она/оно": "роет", "мы": "роем", "вы": "роете", "они": "роют"},
|
|
241
|
+
{"мужской": "рыл", "женский": "рыла", "средний": "рыло", "множественное": "рыли"})
|
|
242
|
+
|
|
243
|
+
# Perfective of -ыть verbs
|
|
244
|
+
ir("открыть", None,
|
|
245
|
+
{"мужской": "открыл", "женский": "открыла", "средний": "открыло", "множественное": "открыли"},
|
|
246
|
+
{"я": "открою", "ты": "откроешь", "он/она/оно": "откроет", "мы": "откроем", "вы": "откроете", "они": "откроют"})
|
|
247
|
+
|
|
248
|
+
ir("закрыть", None,
|
|
249
|
+
{"мужской": "закрыл", "женский": "закрыла", "средний": "закрыло", "множественное": "закрыли"},
|
|
250
|
+
{"я": "закрою", "ты": "закроешь", "он/она/оно": "закроет", "мы": "закроем", "вы": "закроете", "они": "закроют"})
|
|
251
|
+
|
|
252
|
+
# Regular reflexives (fully spelled out due to -ся rules)
|
|
253
|
+
ir("улыбаться",
|
|
254
|
+
{"я": "улыбаюсь", "ты": "улыбаешься", "он/она/оно": "улыбается", "мы": "улыбаемся", "вы": "улыбаетесь", "они": "улыбаются"},
|
|
255
|
+
{"мужской": "улыбался", "женский": "улыбалась", "средний": "улыбалось", "множественное": "улыбались"})
|
|
256
|
+
|
|
257
|
+
ir("купаться",
|
|
258
|
+
{"я": "купаюсь", "ты": "купаешься", "он/она/оно": "купается", "мы": "купаемся", "вы": "купаетесь", "они": "купаются"},
|
|
259
|
+
{"мужской": "купался", "женский": "купалась", "средний": "купалось", "множественное": "купались"})
|
|
260
|
+
|
|
261
|
+
ir("одеваться",
|
|
262
|
+
{"я": "одеваюсь", "ты": "одеваешься", "он/она/оно": "одевается", "мы": "одеваемся", "вы": "одеваетесь", "они": "одеваются"},
|
|
263
|
+
{"мужской": "одевался", "женский": "одевалась", "средний": "одевалось", "множественное": "одевались"})
|
|
264
|
+
|
|
265
|
+
ir("умываться",
|
|
266
|
+
{"я": "умываюсь", "ты": "умываешься", "он/она/оно": "умывается", "мы": "умываемся", "вы": "умываетесь", "они": "умываются"},
|
|
267
|
+
{"мужской": "умывался", "женский": "умывалась", "средний": "умывалось", "множественное": "умывались"})
|
|
268
|
+
|
|
269
|
+
ir("просыпаться",
|
|
270
|
+
{"я": "просыпаюсь", "ты": "просыпаешься", "он/она/оно": "просыпается", "мы": "просыпаемся", "вы": "просыпаетесь", "они": "просыпаются"},
|
|
271
|
+
{"мужской": "просыпался", "женский": "просыпалась", "средний": "просыпалось", "множественное": "просыпались"})
|
|
272
|
+
|
|
273
|
+
ir("ложиться",
|
|
274
|
+
{"я": "ложусь", "ты": "ложишься", "он/она/оно": "ложится", "мы": "ложимся", "вы": "ложитесь", "они": "ложатся"},
|
|
275
|
+
{"мужской": "ложился", "женский": "ложилась", "средний": "ложилось", "множественное": "ложились"})
|
|
276
|
+
|
|
277
|
+
ir("садиться",
|
|
278
|
+
{"я": "сажусь", "ты": "садишься", "он/она/оно": "садится", "мы": "садимся", "вы": "садитесь", "они": "садятся"},
|
|
279
|
+
{"мужской": "садился", "женский": "садилась", "средний": "садилось", "множественное": "садились"})
|
|
280
|
+
|
|
281
|
+
ir("бояться",
|
|
282
|
+
{"я": "боюсь", "ты": "боишься", "он/она/оно": "боится", "мы": "боимся", "вы": "боитесь", "они": "боятся"},
|
|
283
|
+
{"мужской": "боялся", "женский": "боялась", "средний": "боялось", "множественное": "боялись"})
|
|
284
|
+
|
|
285
|
+
ir("смеяться",
|
|
286
|
+
{"я": "смеюсь", "ты": "смеёшься", "он/она/оно": "смеётся", "мы": "смеёмся", "вы": "смеётесь", "они": "смеются"},
|
|
287
|
+
{"мужской": "смеялся", "женский": "смеялась", "средний": "смеялось", "множественное": "смеялись"})
|
|
288
|
+
|
|
289
|
+
ir("надеяться",
|
|
290
|
+
{"я": "надеюсь", "ты": "надеешься", "он/она/оно": "надеется", "мы": "надеемся", "вы": "надеетесь", "они": "надеются"},
|
|
291
|
+
{"мужской": "надеялся", "женский": "надеялась", "средний": "надеялось", "множественное": "надеялись"})
|
|
292
|
+
|
|
293
|
+
ir("кататься",
|
|
294
|
+
{"я": "катаюсь", "ты": "катаешься", "он/она/оно": "катается", "мы": "катаемся", "вы": "катаетесь", "они": "катаются"},
|
|
295
|
+
{"мужской": "катался", "женский": "каталась", "средний": "каталось", "множественное": "катались"})
|
|
296
|
+
|
|
297
|
+
ir("собираться",
|
|
298
|
+
{"я": "собираюсь", "ты": "собираешься", "он/она/оно": "собирается", "мы": "собираемся", "вы": "собираетесь", "они": "собираются"},
|
|
299
|
+
{"мужской": "собирался", "женский": "собиралась", "средний": "собиралось", "множественное": "собирались"})
|
|
300
|
+
|
|
301
|
+
ir("казаться",
|
|
302
|
+
{"я": "кажусь", "ты": "кажешься", "он/она/оно": "кажется", "мы": "кажемся", "вы": "кажетесь", "они": "кажутся"},
|
|
303
|
+
{"мужской": "казался", "женский": "казалась", "средний": "казалось", "множественное": "казались"})
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
VERBS = {}
|
|
307
|
+
|
|
308
|
+
def conj2_stem(verb):
|
|
309
|
+
if verb.endswith("ить"):
|
|
310
|
+
return verb[:-3]
|
|
311
|
+
if verb.endswith("еть"):
|
|
312
|
+
return verb[:-3]
|
|
313
|
+
if verb.endswith("ать"):
|
|
314
|
+
return verb[:-3]
|
|
315
|
+
return verb[:-2]
|
|
316
|
+
|
|
317
|
+
def add(verb, conj, aspect, refl=False, stem=None, mut_1sg=None):
|
|
318
|
+
entry = {"conj": conj, "aspect": aspect, "refl": refl}
|
|
319
|
+
if stem:
|
|
320
|
+
entry["stem"] = stem
|
|
321
|
+
if mut_1sg:
|
|
322
|
+
entry["mut_1sg"] = mut_1sg
|
|
323
|
+
VERBS[verb] = entry
|
|
324
|
+
|
|
325
|
+
# Regular 1st conj verbs (vowel-stem: чита, дела, etc.)
|
|
326
|
+
for v in ["читать","делать","играть","работать","думать","знать","понимать",
|
|
327
|
+
"слушать","гулять","кушать","обедать","ужинать","завтракать",
|
|
328
|
+
"отдыхать","встречать","отвечать","спрашивать","повторять",
|
|
329
|
+
"изучать","начинать","кончать","мечтать","желать","менять",
|
|
330
|
+
"терять","бросать","кидать","ломать","хватать","купать",
|
|
331
|
+
"опаздывать","сделать","прочитать","уметь","жалеть","худеть",
|
|
332
|
+
"толстеть","успеть","поспеть","созреть",
|
|
333
|
+
"бегать","плавать","летать","лазать","ползать"]:
|
|
334
|
+
asp = "perf" if v in ("сделать","прочитать","успеть","поспеть","созреть") else "imp"
|
|
335
|
+
add(v, 1, asp)
|
|
336
|
+
|
|
337
|
+
# Sibilant-stem 1st conj (писать, искать, etc.)
|
|
338
|
+
for v, s, a in [("писать","пиш","imp"),("искать","ищ","imp"),
|
|
339
|
+
("резать","реж","imp"),("мазать","маж","imp"),
|
|
340
|
+
("плакать","плач","imp"),("пахать","паш","imp"),
|
|
341
|
+
("скакать","скач","imp"),
|
|
342
|
+
("написать","напиш","perf"),("сказать","скаж","perf"),
|
|
343
|
+
("рассказать","расскаж","perf")]:
|
|
344
|
+
add(v, 1, a, stem=s)
|
|
345
|
+
|
|
346
|
+
# -овать verbs (stem in -уj)
|
|
347
|
+
for v in ["рисовать","танцевать","целовать","советовать","требовать",
|
|
348
|
+
"чувствовать","интересовать","участвовать","существовать",
|
|
349
|
+
"рекомендовать","воевать","ночевать","попробовать"]:
|
|
350
|
+
add(v, 1, "perf" if v == "попробовать" else "imp", stem=v[:-5] + "уj")
|
|
351
|
+
|
|
352
|
+
# -нуть verbs
|
|
353
|
+
for v in ["прыгнуть","крикнуть","стукнуть","толкнуть","махнуть","вернуть","отдохнуть"]:
|
|
354
|
+
add(v, 1, "perf", stem=v[:-4] + "н")
|
|
355
|
+
|
|
356
|
+
# замёрзнуть drops -ну- in past: замёрз, замёрзла, замёрзло, замёрзли
|
|
357
|
+
ir("замёрзнуть", None,
|
|
358
|
+
{"мужской": "замёрз", "женский": "замёрзла", "средний": "замёрзло", "множественное": "замёрзли"},
|
|
359
|
+
{"я": "замёрзну", "ты": "замёрзнешь", "он/она/оно": "замёрзнет", "мы": "замёрзнем", "вы": "замёрзнете", "они": "замёрзнут"})
|
|
360
|
+
|
|
361
|
+
# Add all IRR verbs to VERBS
|
|
362
|
+
for verb in IRR:
|
|
363
|
+
conj = 2 if verb in ("есть","бежать","бояться",) else 1
|
|
364
|
+
refl = verb.endswith("ся")
|
|
365
|
+
asp = "imp"
|
|
366
|
+
if verb in ("дать","взять","понять","начать","помочь","прийти","пройти",
|
|
367
|
+
"найти","открыть","закрыть","купить","обидеть","увидеть",
|
|
368
|
+
"посмотреть","услышать","открыть","закрыть","ошибиться",
|
|
369
|
+
"пустить","простить","приготовить","встретить","объяснить",
|
|
370
|
+
"кончить","закончить","написать","сказать","рассказать",
|
|
371
|
+
"сделать","прочитать","успеть","поспеть","созреть","попробовать",
|
|
372
|
+
"прыгнуть","крикнуть","стукнуть","толкнуть","махнуть","вернуть",
|
|
373
|
+
"отдохнуть","замёрзнуть"):
|
|
374
|
+
asp = "perf"
|
|
375
|
+
add(verb, conj, asp, refl=refl)
|
|
376
|
+
|
|
377
|
+
# Regular 2nd conj -ить verbs
|
|
378
|
+
for v, a, m in [("говорить","imp","говорю"),("любить","imp","люблю"),
|
|
379
|
+
("купить","perf","куплю"),("носить","imp","ношу"),
|
|
380
|
+
("ходить","imp","хожу"),("просить","imp","прошу"),
|
|
381
|
+
("возить","imp","вожу"),("платить","imp","плачу"),
|
|
382
|
+
("крутить","imp","кручу"),("варить","imp","варю"),
|
|
383
|
+
("дарить","imp","дарю"),("готовить","imp","готовлю"),
|
|
384
|
+
("ловить","imp","ловлю"),("ставить","imp","ставлю"),
|
|
385
|
+
("тратить","imp","трачу"),("пустить","perf","пущу"),
|
|
386
|
+
("простить","perf","прощу"),("грустить","imp","грущу"),
|
|
387
|
+
("чистить","imp","чищу"),("ездить","imp","езжу"),
|
|
388
|
+
("кончить","perf","кончу"),("закончить","perf","закончу"),
|
|
389
|
+
("объяснить","perf","объясню"),
|
|
390
|
+
("приготовить","perf","приготовлю"),
|
|
391
|
+
("встретить","perf","встречу")]:
|
|
392
|
+
add(v, 2, a, stem=conj2_stem(v), mut_1sg=m)
|
|
393
|
+
|
|
394
|
+
# 2nd conj -еть exceptions
|
|
395
|
+
for v, a, m in [("сидеть","imp","сижу"),("висеть","imp","вишу"),
|
|
396
|
+
("лететь","imp","лечу"),("шуметь","imp","шумлю"),
|
|
397
|
+
("смотреть","imp","смотрю"),("видеть","imp","вижу"),
|
|
398
|
+
("ненавидеть","imp","ненавижу"),("терпеть","imp","терплю"),
|
|
399
|
+
("обидеть","perf","обижу"),("зависеть","imp","завишу"),
|
|
400
|
+
("вертеть","imp","верчу"),
|
|
401
|
+
("увидеть","perf","увижу"),("посмотреть","perf","посмотрю")]:
|
|
402
|
+
add(v, 2, a, stem=conj2_stem(v), mut_1sg=m)
|
|
403
|
+
|
|
404
|
+
# 2nd conj -ать exceptions
|
|
405
|
+
for v, a in [("слышать","imp"),("держать","imp"),("дышать","imp"),
|
|
406
|
+
("гнать","imp"),("лежать","imp"),("молчать","imp"),
|
|
407
|
+
("кричать","imp"),("звучать","imp"),("стучать","imp"),
|
|
408
|
+
("услышать","perf")]:
|
|
409
|
+
add(v, 2, a, stem=conj2_stem(v))
|
|
410
|
+
|
|
411
|
+
# спать (special 1sg mutation)
|
|
412
|
+
for v, a, m in [("спать","imp","сплю")]:
|
|
413
|
+
add(v, 2, a, stem=conj2_stem(v), mut_1sg=m)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
# --- Classify exceptions for every verb ---
|
|
417
|
+
def _build_exceptions():
|
|
418
|
+
truly_irregular = {"быть","дать","есть","хотеть","ехать","бежать","взять",
|
|
419
|
+
"понять","начать","ошибиться","замёрзнуть"}
|
|
420
|
+
|
|
421
|
+
for verb, info in VERBS.items():
|
|
422
|
+
exc = []
|
|
423
|
+
|
|
424
|
+
if verb in truly_irregular:
|
|
425
|
+
exc.append("irregular")
|
|
426
|
+
|
|
427
|
+
# conj_class: 2nd conj but ending in -еть/-ать (normally 1st conj endings)
|
|
428
|
+
if info["conj"] == 2 and not verb.endswith("ить") and verb not in ("спать",):
|
|
429
|
+
if verb.endswith("еть") or verb.endswith("ать") or verb.endswith("ять"):
|
|
430
|
+
exc.append("conj_class")
|
|
431
|
+
if verb == "спать":
|
|
432
|
+
exc.append("conj_class")
|
|
433
|
+
|
|
434
|
+
if "mut_1sg" in info:
|
|
435
|
+
exc.append("mut_1sg")
|
|
436
|
+
|
|
437
|
+
if "stem" in info:
|
|
438
|
+
s = info["stem"]
|
|
439
|
+
if s.endswith("j"):
|
|
440
|
+
exc.append("stem_change")
|
|
441
|
+
else:
|
|
442
|
+
exc.append("sibilant_stem")
|
|
443
|
+
|
|
444
|
+
if verb.endswith("нуть") and verb not in truly_irregular:
|
|
445
|
+
exc.append("nut_verb")
|
|
446
|
+
|
|
447
|
+
if verb.endswith("ся"):
|
|
448
|
+
exc.append("reflexive")
|
|
449
|
+
|
|
450
|
+
# past_irreg: compare generated past with actual past from IRR
|
|
451
|
+
if verb in IRR and IRR[verb].get("past"):
|
|
452
|
+
try:
|
|
453
|
+
irr_past = IRR[verb]["past"]
|
|
454
|
+
if verb.endswith("ть"):
|
|
455
|
+
stem = verb[:-2]
|
|
456
|
+
elif verb.endswith("ти"):
|
|
457
|
+
stem = verb[:-2]
|
|
458
|
+
elif verb.endswith("чь"):
|
|
459
|
+
stem = verb[:-2]
|
|
460
|
+
else:
|
|
461
|
+
stem = verb
|
|
462
|
+
expected = {"мужской": stem+"л", "женский": stem+"ла",
|
|
463
|
+
"средний": stem+"ло", "множественное": stem+"ли"}
|
|
464
|
+
if irr_past != expected:
|
|
465
|
+
exc.append("past_irreg")
|
|
466
|
+
except Exception:
|
|
467
|
+
exc.append("past_irreg")
|
|
468
|
+
|
|
469
|
+
if exc:
|
|
470
|
+
EXCEPTIONS[verb] = exc
|
|
471
|
+
|
|
472
|
+
_build_exceptions()
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def get_exceptions():
|
|
476
|
+
return {v: {"types": t, "descriptions": [EXCEPTION_TYPES[e] for e in t]}
|
|
477
|
+
for v, t in EXCEPTIONS.items()}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
def get_verb_exceptions(verb):
|
|
481
|
+
exc = EXCEPTIONS.get(verb)
|
|
482
|
+
if not exc:
|
|
483
|
+
return None
|
|
484
|
+
return {"verb": verb, "types": exc, "descriptions": [EXCEPTION_TYPES[e] for e in exc]}
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
VOWELS = set("аеёиоуыэюя")
|
|
488
|
+
|
|
489
|
+
def conj1_present(stem):
|
|
490
|
+
if stem.endswith("j"):
|
|
491
|
+
stem = stem[:-1]
|
|
492
|
+
def e(p):
|
|
493
|
+
if p == 0:
|
|
494
|
+
if stem and stem[-1] in SIBILANTS:
|
|
495
|
+
return stem + "у"
|
|
496
|
+
if stem and stem[-1] in VOWELS:
|
|
497
|
+
return stem + "ю"
|
|
498
|
+
return stem + "у"
|
|
499
|
+
if p == 5:
|
|
500
|
+
if stem and stem[-1] in SIBILANTS:
|
|
501
|
+
return stem + "ут"
|
|
502
|
+
if stem and stem[-1] in VOWELS:
|
|
503
|
+
return stem + "ют"
|
|
504
|
+
return stem + "ут"
|
|
505
|
+
return stem + ["ешь","ет","ем","ете"][p-1]
|
|
506
|
+
return {PERSONS[i]: e(i) for i in range(6)}
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def conj2_present(stem, mut_1sg=None):
|
|
510
|
+
def e(p):
|
|
511
|
+
if p == 0:
|
|
512
|
+
return mut_1sg if mut_1sg else stem + "ю"
|
|
513
|
+
if p == 5:
|
|
514
|
+
if stem and stem[-1] in SIBILANTS:
|
|
515
|
+
return stem + "ат"
|
|
516
|
+
return stem + "ят"
|
|
517
|
+
return stem + ["ишь","ит","им","ите"][p-1]
|
|
518
|
+
return {PERSONS[i]: e(i) for i in range(6)}
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def build_past(verb):
|
|
522
|
+
if verb.endswith("ть"):
|
|
523
|
+
s = verb[:-2]
|
|
524
|
+
elif verb.endswith("ти"):
|
|
525
|
+
s = verb[:-2]
|
|
526
|
+
elif verb.endswith("чь"):
|
|
527
|
+
s = verb[:-2]
|
|
528
|
+
else:
|
|
529
|
+
s = verb
|
|
530
|
+
return {"мужской": s+"л", "женский": s+"ла", "средний": s+"ло", "множественное": s+"ли"}
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def conjugate(verb):
|
|
535
|
+
verb = verb.lower().strip()
|
|
536
|
+
info = VERBS.get(verb)
|
|
537
|
+
if not info:
|
|
538
|
+
return conjugate_any(verb)
|
|
539
|
+
|
|
540
|
+
if verb in IRR:
|
|
541
|
+
irr = IRR[verb]
|
|
542
|
+
tenses = {
|
|
543
|
+
"present": irr["present"],
|
|
544
|
+
"past": irr["past"],
|
|
545
|
+
}
|
|
546
|
+
if info["aspect"] == "imp" and verb != "быть":
|
|
547
|
+
bp = {"я": "буду", "ты": "будешь", "он/она/оно": "будет",
|
|
548
|
+
"мы": "будем", "вы": "будете", "они": "будут"}
|
|
549
|
+
tenses["future"] = {p: f"{b} {verb}" for p, b in bp.items()}
|
|
550
|
+
else:
|
|
551
|
+
f = irr.get("future")
|
|
552
|
+
if f:
|
|
553
|
+
tenses["future"] = f
|
|
554
|
+
elif info["aspect"] == "perf":
|
|
555
|
+
stem = info.get("stem", verb[:-2] if verb.endswith("ть") else verb)
|
|
556
|
+
if info["conj"] == 1:
|
|
557
|
+
tenses["future"] = conj1_present(stem)
|
|
558
|
+
else:
|
|
559
|
+
tenses["future"] = conj2_present(stem, info.get("mut_1sg"))
|
|
560
|
+
if info["aspect"] == "perf" and verb not in ("быть",):
|
|
561
|
+
tenses["present"] = None
|
|
562
|
+
return build_result(verb, info, tenses), 200
|
|
563
|
+
|
|
564
|
+
stem = info.get("stem", verb[:-2] if verb.endswith("ть") else verb)
|
|
565
|
+
tenses = {}
|
|
566
|
+
|
|
567
|
+
if info["conj"] == 1:
|
|
568
|
+
tenses["present"] = conj1_present(stem)
|
|
569
|
+
else:
|
|
570
|
+
tenses["present"] = conj2_present(stem, info.get("mut_1sg"))
|
|
571
|
+
|
|
572
|
+
tenses["past"] = build_past(verb)
|
|
573
|
+
|
|
574
|
+
if info["aspect"] == "perf":
|
|
575
|
+
tenses["present"] = None
|
|
576
|
+
if info["conj"] == 1:
|
|
577
|
+
tenses["future"] = conj1_present(stem)
|
|
578
|
+
else:
|
|
579
|
+
tenses["future"] = conj2_present(stem, info.get("mut_1sg"))
|
|
580
|
+
else:
|
|
581
|
+
bp = {"я": "буду", "ты": "будешь", "он/она/оно": "будет",
|
|
582
|
+
"мы": "будем", "вы": "будете", "они": "будут"}
|
|
583
|
+
tenses["future"] = {p: f"{b} {verb}" for p, b in bp.items()}
|
|
584
|
+
|
|
585
|
+
return build_result(verb, info, tenses), 200
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def build_result(verb, info, tenses):
|
|
589
|
+
aspect_map = {"imp": "imperfective", "perf": "perfective"}
|
|
590
|
+
result = {
|
|
591
|
+
"verb": verb,
|
|
592
|
+
"aspect": aspect_map.get(info["aspect"], info["aspect"]),
|
|
593
|
+
"conjugation_type": info["conj"],
|
|
594
|
+
"reflexive": info["refl"],
|
|
595
|
+
"tenses": tenses,
|
|
596
|
+
}
|
|
597
|
+
exc = get_verb_exceptions(verb)
|
|
598
|
+
if exc:
|
|
599
|
+
result["exceptions"] = exc
|
|
600
|
+
return result
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
# --- Rule-based fallback for any Russian verb ---
|
|
604
|
+
|
|
605
|
+
SECOND_CONJ_EXCEPTIONS = {
|
|
606
|
+
"сидеть","висеть","лететь","шуметь","смотреть","видеть",
|
|
607
|
+
"ненавидеть","терпеть","обидеть","зависеть","вертеть",
|
|
608
|
+
"увидеть","посмотреть",
|
|
609
|
+
"слышать","держать","дышать","гнать","лежать","молчать",
|
|
610
|
+
"кричать","звучать","стучать","услышать","спать",
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
def _add_refl(form):
|
|
614
|
+
if form and form[-1] in VOWELS:
|
|
615
|
+
return form + "сь"
|
|
616
|
+
return form + "ся"
|
|
617
|
+
|
|
618
|
+
def _reflexivize(tenses):
|
|
619
|
+
for tense_key in list(tenses.keys()):
|
|
620
|
+
if tense_key in ("past", "future"):
|
|
621
|
+
continue
|
|
622
|
+
if tenses[tense_key] is None:
|
|
623
|
+
continue
|
|
624
|
+
forms = {}
|
|
625
|
+
for person, form in tenses[tense_key].items():
|
|
626
|
+
forms[person] = _add_refl(form)
|
|
627
|
+
tenses[tense_key] = forms
|
|
628
|
+
return tenses
|
|
629
|
+
|
|
630
|
+
def _past_of(verb):
|
|
631
|
+
if verb.endswith("ся"):
|
|
632
|
+
base = verb[:-2]
|
|
633
|
+
else:
|
|
634
|
+
base = verb
|
|
635
|
+
if base.endswith("ть"):
|
|
636
|
+
s = base[:-2]
|
|
637
|
+
elif base.endswith("ти"):
|
|
638
|
+
s = base[:-2]
|
|
639
|
+
elif base.endswith("чь"):
|
|
640
|
+
s = base[:-2]
|
|
641
|
+
else:
|
|
642
|
+
s = base
|
|
643
|
+
past = {"мужской": s+"л", "женский": s+"ла", "средний": s+"ло", "множественное": s+"ли"}
|
|
644
|
+
if verb.endswith("ся"):
|
|
645
|
+
return {"мужской": past["мужской"]+"ся",
|
|
646
|
+
"женский": past["женский"]+"сь",
|
|
647
|
+
"средний": past["средний"]+"сь",
|
|
648
|
+
"множественное": past["множественное"]+"сь"}
|
|
649
|
+
return past
|
|
650
|
+
|
|
651
|
+
def _conj_number(verb):
|
|
652
|
+
if verb.endswith("ся"):
|
|
653
|
+
verb = verb[:-2]
|
|
654
|
+
if verb.endswith("ить"):
|
|
655
|
+
return 2
|
|
656
|
+
if verb in SECOND_CONJ_EXCEPTIONS:
|
|
657
|
+
return 2
|
|
658
|
+
return 1
|
|
659
|
+
|
|
660
|
+
def _guess_mutation(stem):
|
|
661
|
+
if not stem:
|
|
662
|
+
return None
|
|
663
|
+
non_mutating = set("рлнкгхжшчщй")
|
|
664
|
+
if stem[-1] in non_mutating:
|
|
665
|
+
return None
|
|
666
|
+
if stem.endswith("ст"):
|
|
667
|
+
return stem[:-2] + "щ"
|
|
668
|
+
if stem.endswith("зд"):
|
|
669
|
+
return stem[:-2] + "жд"
|
|
670
|
+
if stem.endswith("д"):
|
|
671
|
+
return stem[:-1] + "ж"
|
|
672
|
+
if stem.endswith("з"):
|
|
673
|
+
return stem[:-1] + "ж"
|
|
674
|
+
if stem.endswith("с"):
|
|
675
|
+
return stem[:-1] + "ш"
|
|
676
|
+
if stem.endswith("т"):
|
|
677
|
+
return stem[:-1] + "ч"
|
|
678
|
+
if stem.endswith("в"):
|
|
679
|
+
return stem[:-1] + "вл"
|
|
680
|
+
if stem.endswith("б"):
|
|
681
|
+
return stem[:-1] + "бл"
|
|
682
|
+
if stem.endswith("п"):
|
|
683
|
+
return stem[:-1] + "пл"
|
|
684
|
+
if stem.endswith("м"):
|
|
685
|
+
return stem[:-1] + "мл"
|
|
686
|
+
if stem.endswith("ф"):
|
|
687
|
+
return stem[:-1] + "фл"
|
|
688
|
+
return None
|
|
689
|
+
|
|
690
|
+
def _present_stem(verb):
|
|
691
|
+
if verb.endswith("ся"):
|
|
692
|
+
base = verb[:-2]
|
|
693
|
+
else:
|
|
694
|
+
base = verb
|
|
695
|
+
if base.endswith("овать"):
|
|
696
|
+
return base[:-5] + "уj"
|
|
697
|
+
if base.endswith("евать"):
|
|
698
|
+
return base[:-5] + "уj"
|
|
699
|
+
if base.endswith("нуть"):
|
|
700
|
+
return base[:-4] + "н"
|
|
701
|
+
if base.endswith("ить"):
|
|
702
|
+
return base[:-3]
|
|
703
|
+
if base.endswith("чь"):
|
|
704
|
+
return base[:-2]
|
|
705
|
+
if base.endswith("ти"):
|
|
706
|
+
return base[:-2]
|
|
707
|
+
if base.endswith("ть"):
|
|
708
|
+
return base[:-2]
|
|
709
|
+
return base
|
|
710
|
+
|
|
711
|
+
def conjugate_any(verb):
|
|
712
|
+
verb = verb.lower().strip()
|
|
713
|
+
refl = verb.endswith("ся")
|
|
714
|
+
conj = _conj_number(verb)
|
|
715
|
+
stem = _present_stem(verb)
|
|
716
|
+
mut = _guess_mutation(stem) if conj == 2 else None
|
|
717
|
+
tenses = {}
|
|
718
|
+
tenses["past"] = _past_of(verb)
|
|
719
|
+
if conj == 2:
|
|
720
|
+
tenses["present"] = conj2_present(stem, mut)
|
|
721
|
+
else:
|
|
722
|
+
tenses["present"] = conj1_present(stem)
|
|
723
|
+
bp = {"я": "буду", "ты": "будешь", "он/она/оно": "будет",
|
|
724
|
+
"мы": "будем", "вы": "будете", "они": "будут"}
|
|
725
|
+
tenses["future"] = {p: f"{b} {verb}" for p, b in bp.items()}
|
|
726
|
+
if refl:
|
|
727
|
+
tenses = _reflexivize(tenses)
|
|
728
|
+
info = {"conj": conj, "aspect": "imp", "refl": refl, "stem": stem}
|
|
729
|
+
if mut:
|
|
730
|
+
info["mut_1sg"] = mut
|
|
731
|
+
result = build_result(verb, info, tenses)
|
|
732
|
+
result["generated_by_rules"] = True
|
|
733
|
+
return result, 200
|
|
734
|
+
|
|
735
|
+
|
|
736
|
+
def search_verbs(query):
|
|
737
|
+
q = query.lower()
|
|
738
|
+
results = []
|
|
739
|
+
for verb in VERBS:
|
|
740
|
+
if q in verb.lower():
|
|
741
|
+
info = VERBS[verb]
|
|
742
|
+
results.append({
|
|
743
|
+
"verb": verb,
|
|
744
|
+
"aspect": "imperfective" if info["aspect"] == "imp" else "perfective",
|
|
745
|
+
"reflexive": info["refl"],
|
|
746
|
+
})
|
|
747
|
+
return sorted(results, key=lambda x: x["verb"])
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def list_verbs():
|
|
751
|
+
results = []
|
|
752
|
+
for verb, info in sorted(VERBS.items()):
|
|
753
|
+
results.append({
|
|
754
|
+
"verb": verb,
|
|
755
|
+
"aspect": "imperfective" if info["aspect"] == "imp" else "perfective",
|
|
756
|
+
"reflexive": info["refl"],
|
|
757
|
+
})
|
|
758
|
+
return results
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from deep_translator import GoogleTranslator
|
|
2
|
+
|
|
3
|
+
_translator = None
|
|
4
|
+
|
|
5
|
+
def get_translator():
|
|
6
|
+
global _translator
|
|
7
|
+
if _translator is None:
|
|
8
|
+
_translator = GoogleTranslator(source="en", target="ru")
|
|
9
|
+
return _translator
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def translate_en_to_ru(word):
|
|
13
|
+
word = word.lower().strip()
|
|
14
|
+
try:
|
|
15
|
+
t = get_translator()
|
|
16
|
+
result = t.translate(word)
|
|
17
|
+
if result and result.lower() != word.lower():
|
|
18
|
+
return result.lower()
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
return None
|
ru_api_free/main.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from fastapi import FastAPI, Query, HTTPException, Path
|
|
2
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
3
|
+
from fastapi.staticfiles import StaticFiles
|
|
4
|
+
|
|
5
|
+
from .conjugator import conjugate, search_verbs, list_verbs, get_exceptions, get_verb_exceptions, EXCEPTION_TYPES
|
|
6
|
+
from .dictionary import translate_en_to_ru
|
|
7
|
+
|
|
8
|
+
app = FastAPI(
|
|
9
|
+
title="Russian Verb Conjugation API",
|
|
10
|
+
description="Free API for conjugating Russian verbs in all tenses (present, past, future)",
|
|
11
|
+
version="1.0.0",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
app.add_middleware(
|
|
15
|
+
CORSMiddleware,
|
|
16
|
+
allow_origins=["*"],
|
|
17
|
+
allow_credentials=True,
|
|
18
|
+
allow_methods=["*"],
|
|
19
|
+
allow_headers=["*"],
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
import importlib.resources as res
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
static_path = res.files("ru_api_free").joinpath("static")
|
|
26
|
+
if static_path.is_dir():
|
|
27
|
+
app.mount("/site", StaticFiles(directory=str(static_path), html=True), name="static")
|
|
28
|
+
except Exception:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.get("/")
|
|
33
|
+
def root():
|
|
34
|
+
return {
|
|
35
|
+
"name": "Russian Verb Conjugation API",
|
|
36
|
+
"version": "1.0.0",
|
|
37
|
+
"endpoints": {
|
|
38
|
+
"/conjugate?verb={verb}": "Get full conjugation of a Russian verb",
|
|
39
|
+
"/translate?english={word}": "Enter an English verb, get Russian conjugation",
|
|
40
|
+
"/verbs": "List all available verbs",
|
|
41
|
+
"/search?q={query}": "Search for verbs",
|
|
42
|
+
"/exceptions": "List all verbs with conjugation exceptions",
|
|
43
|
+
"/exceptions/{verb}": "Get exceptions for a specific verb",
|
|
44
|
+
"/site": "Open the conjugation web UI",
|
|
45
|
+
},
|
|
46
|
+
"docs": "/docs",
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.get("/conjugate")
|
|
51
|
+
def get_conjugation(verb: str = Query(..., description="Russian verb in infinitive form (e.g., читать)")):
|
|
52
|
+
result, status_code = conjugate(verb)
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.get("/verbs")
|
|
57
|
+
def get_all_verbs():
|
|
58
|
+
return {"verbs": list_verbs(), "count": len(list_verbs())}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@app.get("/exceptions")
|
|
62
|
+
def get_all_exceptions(exception_type: str = Query(None, description="Filter by exception type (e.g., mut_1sg, conj_class, irregular)")):
|
|
63
|
+
exceptions = get_exceptions()
|
|
64
|
+
if exception_type:
|
|
65
|
+
filtered = {v: d for v, d in exceptions.items() if exception_type in d["types"]}
|
|
66
|
+
return {"exception_type": exception_type, "description": EXCEPTION_TYPES.get(exception_type, ""), "verbs": filtered, "count": len(filtered)}
|
|
67
|
+
return {"exception_types": EXCEPTION_TYPES, "verbs": exceptions, "count": len(exceptions)}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@app.get("/exceptions/{verb}")
|
|
71
|
+
def exceptions_for_verb(verb: str = Path(..., description="Russian verb infinitive")):
|
|
72
|
+
exc = get_verb_exceptions(verb.lower().strip())
|
|
73
|
+
if exc is None:
|
|
74
|
+
return {"verb": verb, "exception": None, "message": "This verb follows standard conjugation rules (no exceptions)"}
|
|
75
|
+
return exc
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@app.get("/search")
|
|
79
|
+
def search(query: str = Query(..., description="Search query for verb (e.g., чит)")):
|
|
80
|
+
results = search_verbs(query)
|
|
81
|
+
return {"query": query, "results": results, "count": len(results)}
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@app.get("/translate")
|
|
85
|
+
def translate_to_russian(english: str = Query(..., description="English verb (e.g., read)")):
|
|
86
|
+
eng = english.lower().strip()
|
|
87
|
+
ru_word = translate_en_to_ru(eng)
|
|
88
|
+
if not ru_word:
|
|
89
|
+
raise HTTPException(status_code=502, detail="Translation service unavailable")
|
|
90
|
+
result, status_code = conjugate(ru_word)
|
|
91
|
+
result["english"] = eng
|
|
92
|
+
result["translated"] = ru_word
|
|
93
|
+
if result.get("generated_by_rules"):
|
|
94
|
+
result["note"] = f"Conjugation generated by rules; '{ru_word}' was not in the exact verb database"
|
|
95
|
+
return result
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Russian Verb Conjugation</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
9
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #0f172a; color: #e2e8f0; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 2rem 1rem; }
|
|
10
|
+
.container { width: 100%; max-width: 800px; }
|
|
11
|
+
h1 { font-size: 1.8rem; font-weight: 700; margin-bottom: 0.25rem; background: linear-gradient(135deg, #60a5fa, #a78bfa); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
|
|
12
|
+
.subtitle { color: #94a3b8; margin-bottom: 1.5rem; font-size: 0.9rem; }
|
|
13
|
+
.search-box { display: flex; gap: 0.5rem; margin-bottom: 1.5rem; }
|
|
14
|
+
input { flex: 1; padding: 0.75rem 1rem; border: 1px solid #334155; border-radius: 8px; font-size: 1rem; background: #1e293b; color: #e2e8f0; outline: none; transition: border 0.2s; }
|
|
15
|
+
input:focus { border-color: #60a5fa; }
|
|
16
|
+
input::placeholder { color: #64748b; }
|
|
17
|
+
button { padding: 0.75rem 1.5rem; background: linear-gradient(135deg, #60a5fa, #a78bfa); border: none; border-radius: 8px; color: #fff; font-size: 1rem; font-weight: 600; cursor: pointer; transition: opacity 0.2s; white-space: nowrap; }
|
|
18
|
+
button:hover { opacity: 0.9; }
|
|
19
|
+
button:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
20
|
+
.hint { color: #94a3b8; font-size: 0.8rem; margin-bottom: 1.5rem; }
|
|
21
|
+
.hint span { display: inline-block; background: #1e293b; padding: 0.15rem 0.5rem; border-radius: 4px; margin: 0.15rem; cursor: pointer; transition: background 0.2s; }
|
|
22
|
+
.hint span:hover { background: #334155; }
|
|
23
|
+
.error { background: #7f1d1d; color: #fca5a5; padding: 0.75rem 1rem; border-radius: 8px; margin-bottom: 1rem; display: none; }
|
|
24
|
+
.loading { text-align: center; padding: 2rem; display: none; }
|
|
25
|
+
.loading::after { content: ''; display: inline-block; width: 2rem; height: 2rem; border: 3px solid #334155; border-top-color: #60a5fa; border-radius: 50%; animation: spin 0.6s linear infinite; }
|
|
26
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
27
|
+
.card { background: #1e293b; border-radius: 12px; padding: 1.5rem; margin-bottom: 1rem; display: none; }
|
|
28
|
+
.card-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 0.5rem; }
|
|
29
|
+
.card-header h2 { font-size: 1.2rem; }
|
|
30
|
+
.badge { display: inline-block; padding: 0.2rem 0.6rem; border-radius: 999px; font-size: 0.75rem; font-weight: 600; }
|
|
31
|
+
.badge-imp { background: #1e3a5f; color: #93c5fd; }
|
|
32
|
+
.badge-perf { background: #3b1e5f; color: #c4a5fd; }
|
|
33
|
+
.badge-refl { background: #1e5f3b; color: #a5fdc4; }
|
|
34
|
+
.verb-meta { display: flex; gap: 0.5rem; flex-wrap: wrap; }
|
|
35
|
+
.tenses { display: grid; gap: 1rem; }
|
|
36
|
+
.tense-block { }
|
|
37
|
+
.tense-block h3 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; color: #94a3b8; margin-bottom: 0.5rem; }
|
|
38
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
|
|
39
|
+
td { padding: 0.4rem 0.6rem; border-bottom: 1px solid #334155; }
|
|
40
|
+
td:first-child { color: #94a3b8; width: 30%; }
|
|
41
|
+
td:last-child { font-weight: 500; }
|
|
42
|
+
.empty-tense { color: #64748b; font-style: italic; padding: 0.5rem 0; }
|
|
43
|
+
.suggestions { display: flex; gap: 0.5rem; flex-wrap: wrap; margin-top: 0.5rem; }
|
|
44
|
+
.suggestions button { font-size: 0.8rem; padding: 0.3rem 0.8rem; }
|
|
45
|
+
@media (max-width: 600px) {
|
|
46
|
+
.search-box { flex-direction: column; }
|
|
47
|
+
body { padding: 1rem; }
|
|
48
|
+
}
|
|
49
|
+
</style>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<div class="container">
|
|
53
|
+
<h1>Russian Verb Conjugation</h1>
|
|
54
|
+
<p class="subtitle" id="noteMsg" style="display:none;color:#fbbf24;font-size:0.8rem;margin-bottom:0.75rem;"></p>
|
|
55
|
+
<p class="subtitle">Enter a verb in English or Russian to see all tenses</p>
|
|
56
|
+
<div class="search-box">
|
|
57
|
+
<input type="text" id="verbInput" placeholder="e.g. read, speak, писать, говорить..." autofocus>
|
|
58
|
+
<button id="searchBtn">Conjugate</button>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="hint">
|
|
61
|
+
Try: <span>read</span> <span>speak</span> <span>eat</span> <span>be</span> <span>run</span> <span>читать</span> <span>говорить</span>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="error" id="errorMsg"></div>
|
|
64
|
+
<div class="loading" id="loading"></div>
|
|
65
|
+
<div class="card" id="resultCard">
|
|
66
|
+
<div class="card-header">
|
|
67
|
+
<h2 id="verbTitle"></h2>
|
|
68
|
+
<div class="verb-meta" id="verbMeta"></div>
|
|
69
|
+
</div>
|
|
70
|
+
<div class="tenses" id="tensesContainer"></div>
|
|
71
|
+
<div class="suggestions" id="suggestionsContainer"></div>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
<script>
|
|
75
|
+
const input = document.getElementById('verbInput');
|
|
76
|
+
const searchBtn = document.getElementById('searchBtn');
|
|
77
|
+
const errorMsg = document.getElementById('errorMsg');
|
|
78
|
+
const loading = document.getElementById('loading');
|
|
79
|
+
const resultCard = document.getElementById('resultCard');
|
|
80
|
+
|
|
81
|
+
const API_BASE = window.location.origin;
|
|
82
|
+
|
|
83
|
+
document.querySelectorAll('.hint span').forEach(el => {
|
|
84
|
+
el.addEventListener('click', () => { input.value = el.textContent; searchVerb(); });
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
input.addEventListener('keydown', e => { if (e.key === 'Enter') searchVerb(); });
|
|
88
|
+
searchBtn.addEventListener('click', searchVerb);
|
|
89
|
+
|
|
90
|
+
async function searchVerb() {
|
|
91
|
+
const query = input.value.trim();
|
|
92
|
+
if (!query) return;
|
|
93
|
+
errorMsg.style.display = 'none';
|
|
94
|
+
resultCard.style.display = 'none';
|
|
95
|
+
loading.style.display = 'block';
|
|
96
|
+
try {
|
|
97
|
+
const isCyrillic = /[а-яё]/i.test(query);
|
|
98
|
+
let data;
|
|
99
|
+
if (isCyrillic) {
|
|
100
|
+
const res = await fetch(`${API_BASE}/conjugate?verb=${encodeURIComponent(query)}`);
|
|
101
|
+
if (!res.ok) throw { status: res.status, msg: (await res.json()).detail || 'Verb not found' };
|
|
102
|
+
data = await res.json();
|
|
103
|
+
} else {
|
|
104
|
+
const res = await fetch(`${API_BASE}/translate?english=${encodeURIComponent(query)}`);
|
|
105
|
+
if (!res.ok) {
|
|
106
|
+
const body = await res.json();
|
|
107
|
+
if (body.suggestions) {
|
|
108
|
+
showSuggestions(body.suggestions);
|
|
109
|
+
throw null;
|
|
110
|
+
}
|
|
111
|
+
throw { status: res.status, msg: body.detail || 'Verb not found' };
|
|
112
|
+
}
|
|
113
|
+
data = await res.json();
|
|
114
|
+
}
|
|
115
|
+
renderResult(data);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (err) showError(err.msg || 'An error occurred');
|
|
118
|
+
} finally {
|
|
119
|
+
loading.style.display = 'none';
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function showSuggestions(suggestions) {
|
|
124
|
+
const container = document.getElementById('suggestionsContainer');
|
|
125
|
+
container.innerHTML = '<p style="color:#94a3b8;font-size:0.85rem;width:100%;">Did you mean:</p>' +
|
|
126
|
+
suggestions.map(s => `<button onclick="trySuggestion('${s}')">${s}</button>`).join('');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function trySuggestion(word) {
|
|
130
|
+
input.value = word;
|
|
131
|
+
searchVerb();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function renderResult(data) {
|
|
135
|
+
resultCard.style.display = 'block';
|
|
136
|
+
document.getElementById('suggestionsContainer').innerHTML = '';
|
|
137
|
+
const noteMsg = document.getElementById('noteMsg');
|
|
138
|
+
if (data.note) {
|
|
139
|
+
noteMsg.textContent = data.note;
|
|
140
|
+
noteMsg.style.display = 'block';
|
|
141
|
+
} else {
|
|
142
|
+
noteMsg.style.display = 'none';
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const hasExceptions = data.exceptions;
|
|
146
|
+
document.getElementById('verbTitle').innerHTML = `
|
|
147
|
+
${data.verb}
|
|
148
|
+
${data.english ? `<span style="color:#94a3b8;font-size:0.8rem;font-weight:400;margin-left:0.5rem">(${data.english})</span>` : ''}
|
|
149
|
+
`;
|
|
150
|
+
|
|
151
|
+
const meta = document.getElementById('verbMeta');
|
|
152
|
+
meta.innerHTML = `
|
|
153
|
+
<span class="badge ${data.aspect === 'imperfective' ? 'badge-imp' : 'badge-perf'}">${data.aspect}</span>
|
|
154
|
+
<span class="badge" style="background:#1e293b;color:#94a3b8">Type ${data.conjugation_type} conjugation</span>
|
|
155
|
+
${data.reflexive ? '<span class="badge badge-refl">reflexive</span>' : ''}
|
|
156
|
+
${hasExceptions ? '<span class="badge" style="background:#5f3b1e;color:#fdc4a5">exceptions</span>' : ''}
|
|
157
|
+
`;
|
|
158
|
+
|
|
159
|
+
const container = document.getElementById('tensesContainer');
|
|
160
|
+
container.innerHTML = '';
|
|
161
|
+
|
|
162
|
+
const tenseLabels = {
|
|
163
|
+
present: 'Present Tense (Настоящее время)',
|
|
164
|
+
future: 'Future Tense (Будущее время)',
|
|
165
|
+
past: 'Past Tense (Прошедшее время)'
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
for (const [tense, forms] of Object.entries(data.tenses)) {
|
|
169
|
+
const block = document.createElement('div');
|
|
170
|
+
block.className = 'tense-block';
|
|
171
|
+
block.innerHTML = `<h3>${tenseLabels[tense] || tense}</h3>`;
|
|
172
|
+
|
|
173
|
+
if (forms === null) {
|
|
174
|
+
block.innerHTML += '<div class="empty-tense">— (perfective verb; use future for completed actions)</div>';
|
|
175
|
+
} else if (typeof forms === 'object') {
|
|
176
|
+
let table = '<table>';
|
|
177
|
+
if (forms['мужской']) {
|
|
178
|
+
for (const [gender, val] of Object.entries(forms)) {
|
|
179
|
+
const label = { мужской: 'Masculine', женский: 'Feminine', средний: 'Neuter', множественное: 'Plural' }[gender] || gender;
|
|
180
|
+
table += `<tr><td>${label}</td><td>${val}</td></tr>`;
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
for (const [person, val] of Object.entries(forms)) {
|
|
184
|
+
table += `<tr><td>${person}</td><td>${val}</td></tr>`;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
table += '</table>';
|
|
188
|
+
block.innerHTML += table;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
container.appendChild(block);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (data.exceptions) {
|
|
195
|
+
const excBlock = document.createElement('div');
|
|
196
|
+
excBlock.className = 'tense-block';
|
|
197
|
+
excBlock.innerHTML = '<h3>Exceptions</h3>';
|
|
198
|
+
const list = data.exceptions.descriptions.map(d => `<div style="padding:0.2rem 0;color:#fde68a;font-size:0.85rem">• ${d}</div>`).join('');
|
|
199
|
+
excBlock.innerHTML += list;
|
|
200
|
+
container.appendChild(excBlock);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
resultCard.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function showError(msg) {
|
|
207
|
+
errorMsg.textContent = msg;
|
|
208
|
+
errorMsg.style.display = 'block';
|
|
209
|
+
resultCard.style.display = 'none';
|
|
210
|
+
}
|
|
211
|
+
</script>
|
|
212
|
+
</body>
|
|
213
|
+
</html>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ru-api-free
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Free API for conjugating Russian verbs in all tenses (present, past, future)
|
|
5
|
+
Author-email: Your Name <your@email.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yourusername/ru-api-free
|
|
8
|
+
Project-URL: Source, https://github.com/yourusername/ru-api-free
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Topic :: Text Processing :: Linguistic
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: fastapi
|
|
22
|
+
Requires-Dist: uvicorn[standard]
|
|
23
|
+
Requires-Dist: aiofiles
|
|
24
|
+
Requires-Dist: deep_translator
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# ru-api-free
|
|
28
|
+
|
|
29
|
+
Free API for conjugating Russian verbs in all tenses (present, past, future).
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install ru-api-free
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage
|
|
38
|
+
|
|
39
|
+
Start the API server:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ru-api-free
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or directly with uvicorn:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uvicorn ru_api_free.main:app
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The API will be available at `http://localhost:8000`. Open `http://localhost:8000/docs` for interactive documentation.
|
|
52
|
+
|
|
53
|
+
### Endpoints
|
|
54
|
+
|
|
55
|
+
| Endpoint | Description |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `GET /conjugate?verb={verb}` | Get full conjugation of a Russian verb |
|
|
58
|
+
| `GET /translate?english={word}` | Enter an English verb, get Russian conjugation |
|
|
59
|
+
| `GET /verbs` | List all available verbs |
|
|
60
|
+
| `GET /search?q={query}` | Search for verbs |
|
|
61
|
+
| `GET /exceptions` | List all verbs with conjugation exceptions |
|
|
62
|
+
| `GET /exceptions/{verb}` | Get exceptions for a specific verb |
|
|
63
|
+
|
|
64
|
+
## Python API
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from ru_api_free import conjugate
|
|
68
|
+
|
|
69
|
+
result, status = conjugate("читать")
|
|
70
|
+
print(result)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from ru_api_free import translate_en_to_ru
|
|
75
|
+
|
|
76
|
+
russian = translate_en_to_ru("read")
|
|
77
|
+
print(russian) # читать
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
ru_api_free/__init__.py,sha256=4b2MlWqC5GcxSq2GH1-TOSu3rNH5eTwZOMh2lGGACfs,331
|
|
2
|
+
ru_api_free/__main__.py,sha256=NC62rxprEIymRh2r8llyavSLnCNzogUjUKmkoQdvu8w,172
|
|
3
|
+
ru_api_free/conjugator.py,sha256=pyBg52SWE0zRPJp3jy1falG7-qliQn_askkcgRxv0Do,42137
|
|
4
|
+
ru_api_free/dictionary.py,sha256=jtJ-QqGgKiAx6LBEMNpyTAuCV2VW4cBCyojj5X5Cquw,503
|
|
5
|
+
ru_api_free/main.py,sha256=o0Oe7-x_6g_A9EnXsVb6i-qHLMmoEG0Bwv9u1srG1cA,3514
|
|
6
|
+
ru_api_free/static/index.html,sha256=X171o9UwlWsBILBHcZtdt_HojY3EhxPq_6ngtAvfeS8,9565
|
|
7
|
+
ru_api_free-1.0.0.dist-info/licenses/LICENSE,sha256=ESYyLizI0WWtxMeS7rGVcX3ivMezm-HOd5WdeOh-9oU,1056
|
|
8
|
+
ru_api_free-1.0.0.dist-info/METADATA,sha256=lwIoLjBB_VOHFdIDoc8ssPE70pNf84RhZnL3BZTZMlI,2159
|
|
9
|
+
ru_api_free-1.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
10
|
+
ru_api_free-1.0.0.dist-info/entry_points.txt,sha256=wc92u3NNqbWbri8qI53y2iGk8GWVGgikozQsM5UkVCk,58
|
|
11
|
+
ru_api_free-1.0.0.dist-info/top_level.txt,sha256=b1-HTcAJYK1hKG2EGbIFt5lMo8kf3uvaMoRuo84G7VM,12
|
|
12
|
+
ru_api_free-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ru_api_free
|