pyunislavic 0.1.0__tar.gz

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.
@@ -0,0 +1,25 @@
1
+ # macOS
2
+ .DS_Store
3
+
4
+ # Python bytecode
5
+ __pycache__/
6
+ *.py[cod]
7
+
8
+ # Build artifacts
9
+ build/
10
+ dist/
11
+ *.egg-info/
12
+
13
+ # Test and coverage artifacts
14
+ .pytest_cache/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # Virtual environments
19
+ .venv/
20
+ venv/
21
+ env/
22
+
23
+ # IDE settings
24
+ .idea/
25
+ .vscode/
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zijie ZHANG
4
+ Copyright (c) 2021 Medžuslovjansky (Меджусловјанскы)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,446 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyunislavic
3
+ Version: 0.1.0
4
+ Summary: A Python implementation for transliterating and normalizing text from many Slavic languages and Interslavic into unified writing systems.
5
+ Author: Zijie ZHANG
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Keywords: cyrillic,cyrillization,interslavic,latin,normalization,orthography,romanization,slavic,transliteration
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: Text Processing :: Linguistic
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+
18
+ # PyUniSlavic
19
+
20
+ A Python implementation for transliterating and normalizing text from many
21
+ Slavic languages and Interslavic into unified writing systems.
22
+
23
+ PyUniSlavic converts text written with a broad range of Latin and Cyrillic
24
+ letters used across Slavic orthographies into a selected common Latin,
25
+ ASCII-oriented, or Cyrillic representation. It preserves the source words,
26
+ grammar, and meaning: it is a writing-system converter, not a machine
27
+ translator.
28
+
29
+ The package is an independent, focused Python port of part of the
30
+ transliteration logic in
31
+ [`@interslavic/utils`](https://github.com/medzuslovjansky/js-utils). The
32
+ upstream algorithm was created for Interslavic transliteration. PyUniSlavic
33
+ also uses its permissive input normalization as a practical common writing
34
+ layer for text in multiple Slavic languages.
35
+
36
+ This broader use follows from the design of
37
+ [Interslavic orthography](https://interslavic.fun/learn/orthography/) itself.
38
+ Its standard Latin and Cyrillic alphabets are cross-Slavic compromises rather
39
+ than adaptations of any one national orthography, and they were designed to be
40
+ intuitively understandable and easy to transliterate between. Its optional
41
+ etymological letters represent shared historical phonemes whose developments
42
+ in modern Slavic languages are largely predictable. The official orthography
43
+ guide therefore explicitly identifies transcription from any Slavic language
44
+ into Interslavic orthography as one possible use of this etymological layer.
45
+ It illustrates the principle with `pęť`: one shared etymological form that
46
+ relates several divergent national spellings of the same inherited word.
47
+
48
+ The
49
+ [upstream implementation](https://github.com/medzuslovjansky/js-utils/blob/main/src/transliterate/transliterate.ts)
50
+ turns that cross-Slavic design into a reusable pipeline: its loose preprocessor
51
+ maps many Latin and Cyrillic letter variants, historical characters, and
52
+ alternative spellings into a common internal representation; the flavor stage
53
+ then preserves or merges selected distinctions before the result is rendered
54
+ in the requested output orthography. PyUniSlavic reuses this orthographic
55
+ pipeline without changing the source vocabulary or grammar. In this way, a
56
+ mechanism created for Interslavic transliteration can also provide practical
57
+ cross-Slavic script unification and same-script normalization. This remains
58
+ orthographic normalization rather than translation, and it does not claim
59
+ exact language-specific phonological conversion for every Slavic language.
60
+
61
+ PyUniSlavic is not an official Interslavic implementation and is not affiliated
62
+ with or endorsed by the Interslavic project.
63
+
64
+ ## Features
65
+
66
+ - Transliterates between common Latin and Cyrillic representations.
67
+ - Normalizes Latin text to a shared Latin orthography.
68
+ - Normalizes Cyrillic text to a shared Cyrillic orthography.
69
+ - Accepts many modern, historical, and alternative Latin and Cyrillic letters
70
+ recognized by the upstream loose-input normalizer.
71
+ - Provides etymological, standard, and simplified output profiles.
72
+ - Preserves punctuation, numbers, spacing, and basic capitalization patterns.
73
+ - Uses only the Python standard library.
74
+ - Does not translate, lemmatize, or alter the grammar of the source language.
75
+
76
+ ## Installation
77
+
78
+ ```bash
79
+ pip install pyunislavic
80
+ ```
81
+
82
+ ## Quick start
83
+
84
+ ```python
85
+ from pyunislavic import transliterate
86
+
87
+ result = transliterate(
88
+ "Меджусловјанскы језык",
89
+ type=1,
90
+ flav=2,
91
+ )
92
+
93
+ print(result)
94
+ # Medžuslovjansky jezyk
95
+ ```
96
+
97
+ The public function is:
98
+
99
+ ```python
100
+ transliterate(i_source: str, type: int, flav: int = 2) -> str
101
+ ```
102
+
103
+ ## Supported output types
104
+
105
+ | `type` | Name | Output |
106
+ | ---: | --- | --- |
107
+ | `1` | Latin | Common Latin orthography with diacritics |
108
+ | `2` | ASCII-oriented | Digraph-based Latin output such as `sz`, `cz`, and `zs` |
109
+ | `5` | Standard Cyrillic | Common Cyrillic orthography using letters such as `ј`, `љ`, and `њ` |
110
+
111
+ `type=2` follows the behavior and naming of the upstream `ASCII` mode. With
112
+ `flav=2`, some supported soft-consonant letters can remain non-ASCII, so this
113
+ mode should be understood as ASCII-oriented rather than an unconditional
114
+ 7-bit ASCII guarantee.
115
+
116
+ ## Supported flavor profiles
117
+
118
+ | `flav` | Name | Behavior |
119
+ | ---: | --- | --- |
120
+ | `2` | Etymological | Preserves the greatest number of distinctions supported by this port |
121
+ | `3` | Standard | Merges several historical vowels and soft-consonant distinctions |
122
+ | `4` | Slovianto | Applies additional simplification, including `ě → e` and `y → i` |
123
+
124
+ Every supported output type can be combined with every supported flavor, for a
125
+ total of nine combinations:
126
+
127
+ ```text
128
+ type = 1, 2, or 5
129
+ flav = 2, 3, or 4
130
+ ```
131
+
132
+ Unsupported values raise `ValueError`.
133
+
134
+ Enums are also available:
135
+
136
+ ```python
137
+ from enum import IntEnum
138
+
139
+
140
+ class TransliterationType(IntEnum):
141
+ LATIN = 1
142
+ ASCII = 2
143
+ STANDARD_CYRILLIC = 5
144
+
145
+
146
+ class FlavorizationType(IntEnum):
147
+ ETYMOLOGICAL = 2
148
+ STANDARD = 3
149
+ SLOVIANTO = 4
150
+ ```
151
+
152
+ They can be imported and passed directly to `transliterate`:
153
+
154
+ ```python
155
+ from pyunislavic import (
156
+ FlavorizationType,
157
+ TransliterationType,
158
+ transliterate,
159
+ )
160
+
161
+ result = transliterate(
162
+ "Medžuslovjansky jezyk",
163
+ type=TransliterationType.STANDARD_CYRILLIC,
164
+ flav=FlavorizationType.STANDARD,
165
+ )
166
+
167
+ print(result)
168
+ # Меджусловјанскы језык
169
+ ```
170
+
171
+ ## Same-script normalization
172
+
173
+ The source and output scripts do not have to be different. Latin input can be
174
+ normalized to the selected common Latin profile, and Cyrillic input can be
175
+ normalized to the selected common Cyrillic profile.
176
+
177
+ ### Latin input
178
+
179
+ ```python
180
+ transliterate(
181
+ "Nawet przy założeniu",
182
+ type=1,
183
+ flav=2,
184
+ )
185
+ # 'Navet przy založeniu'
186
+ ```
187
+
188
+ ### Cyrillic input
189
+
190
+ ```python
191
+ transliterate(
192
+ "Улаштовуватимуть водопровідний вузол",
193
+ type=5,
194
+ flav=2,
195
+ )
196
+ # 'Улаштовуватимуть водопровидниј вузол'
197
+ ```
198
+
199
+ These examples retain Polish and Ukrainian vocabulary and grammar. Only their
200
+ orthographic representation is normalized.
201
+
202
+ ## Extended real-language examples
203
+
204
+ The following examples were produced by the current implementation from text
205
+ in several Slavic languages. Language labels use ISO 639-3 codes. They show
206
+ orthographic conversion and normalization as implemented by the upstream-derived
207
+ pipeline; they are not translations or claims about official national spelling.
208
+
209
+ ### Latin etymological output (`type=1`, `flav=2`)
210
+
211
+ #### Belarusian (`bel`)
212
+
213
+ ```text
214
+ INPUT
215
+ Той жа Юры Чыж рабіў свой бізнэс па «паняцьцях», дзякуючы блізкасьці да «цела» меў прывілеі, атрымліваў вольны доступ у прыбытковыя сфэры, куды пускалі толькі абраных, напрыклад, у гандаль нафтапрадуктамі.
216
+
217
+ OUTPUT
218
+ Toj ža Jury Čiž rabiv svoj biznes pa «panjacjcjah», dźakujuči blizkaści da «cela» mev pryvilei, atrymlivav voĺny dostup u prybytkovyja sfery, kudy puskali toĺki abranyh, napryklad, u gandaĺ naftapraduktami.
219
+ ```
220
+
221
+ #### Russian (`rus`)
222
+
223
+ ```text
224
+ INPUT
225
+ В боях за Дубно 14 февраля 1944 года молодой фотокорреспондент находился в боевых порядках 29-го гвардейского кавалерийского полка, оказавшегося в эпицентре сражения за город.
226
+
227
+ OUTPUT
228
+ V bojah za Dubno 14 fevralja 1944 goda molodoj fotokorrespondent nahodilśa v boevyh poŕadkah 29-go gvardejskogo kavalerijskogo polka, okazavšegośa v epicentre sraženija za gorod.
229
+ ```
230
+
231
+ #### Ukrainian (`ukr`)
232
+
233
+ ```text
234
+ INPUT
235
+ "Улаштовуватимуть водопровідний вузол на водоводі діаметром 900 мм на розі вул. Зеленогірської та вул. Петра Радченка", — йдеться у повідомленні.
236
+
237
+ OUTPUT
238
+ "Ulaštovuvatimut́ vodoprovidnij vuzol na vodovodi diametrom 900 mm na rozi vul. Zelenogirśkoji ta vul. Petra Radčenka", — jdet́śa u povidomlenni.
239
+ ```
240
+
241
+ #### Rusyn (`rue`)
242
+
243
+ ```text
244
+ INPUT
245
+ Ключі організовано за дихотомічною системою і представлено у вигляді основного ключа до родин і 10 ключів до родів і видів кожної родини (кротові, мідицеві, вовчкові, мишівкові, тушканові, сліпакові, мишачі, хом'якові, норицеві).
246
+
247
+ OUTPUT
248
+ Ključi organizovano za dihotomičnoju sistemoju i predstavleno u vigljadi osnovnogo ključa do rodin i 10 ključiv do rodiv i vidiv kožnoji rodini (krotovi, midicevi, vovčkovi, mišivkovi, tuškanovi, slipakovi, mišači, hom'jakovi, noricevi).
249
+ ```
250
+
251
+ #### Serbian (`srp`)
252
+
253
+ ```text
254
+ INPUT
255
+ Бугари су имали 7.000 погинулих 6.000 заробљених војника и 130 топова а Грци су имали 8.700 погинулих.
256
+
257
+ OUTPUT
258
+ Bugari su imali 7.000 poginulih 6.000 zarobljenih vojnika i 130 topova a Grci su imali 8.700 poginulih.
259
+ ```
260
+
261
+ ### Cyrillic etymological output (`type=5`, `flav=2`)
262
+
263
+ #### Slovak (`slk`)
264
+
265
+ ```text
266
+ INPUT
267
+ Pridať do obľúbenýchProdukt si uložíte medzi vami zvolené obľúbené položky a môžete si ho tak rýchlo nájsť Drevený vozík na kolieskach obsahuje 24 farebných drevených kociek v rôznych tvaroch.
268
+
269
+ OUTPUT
270
+ Придать до обльубеныхпродукт си уложите медзи вами зволене обльубене положкы а можете си хо так рыхло најсть Древены возик на колиесках обсахује 24 фаребных древеных коциек в розных тварох.
271
+ ```
272
+
273
+ #### Polish (`pol`)
274
+
275
+ ```text
276
+ INPUT
277
+ Pytałem o Olsztyn, a oni wypowiadali się o mieście w samych superlatywach, twierdząc m.in., że szczególnie latem jest tu pięknie.
278
+
279
+ OUTPUT
280
+ Пыталем о Олштын, а они выповиадали сиѧ о миесьцие в самых суперлатывах, твиердзѫц м.ин., же шчеголние латем јест ту пиѧкние.
281
+ ```
282
+
283
+ #### Czech (`ces`)
284
+
285
+ ```text
286
+ INPUT
287
+ Trojice slonů afrických v Zoo Dvůr Králové nad Labem na Trutnovsku si dnes pochutnala na vánoční nadílce, v níž bylo i několik vánočních stromků.
288
+
289
+ OUTPUT
290
+ Тројице слону африцкых в Зоо Двур Кралове над Лабем на Трутновску си днес похутнала на ваночни надилце, в ниж было и нѣколик ваночних стромку.
291
+ ```
292
+
293
+ ### Latin standard output (`type=1`, `flav=3`)
294
+
295
+ #### Polish (`pol`)
296
+
297
+ ```text
298
+ INPUT
299
+ Pytałem o Olsztyn, a oni wypowiadali się o mieście w samych superlatywach, twierdząc m.in., że szczególnie latem jest tu pięknie.
300
+
301
+ OUTPUT
302
+ Pytalem o Olštyn, a oni vypoviadali sie o miescie v samyh superlatyvah, tvierdzuc m.in., že ščegolnie latem jest tu pieknie.
303
+ ```
304
+
305
+ ### ASCII-oriented standard output (`type=2`, `flav=3`)
306
+
307
+ #### Czech (`ces`)
308
+
309
+ ```text
310
+ INPUT
311
+ Trojice slonů afrických v Zoo Dvůr Králové nad Labem na Trutnovsku si dnes pochutnala na vánoční nadílce, v níž bylo i několik vánočních stromků.
312
+
313
+ OUTPUT
314
+ Trojice slonu africkyh v Zoo Dvur Kralove nad Labem na Trutnovsku si dnes pohutnala na vanoczni nadilce, v nizs bylo i njekolik vanocznih stromku.
315
+ ```
316
+
317
+ ### Cyrillic standard output (`type=5`, `flav=3`)
318
+
319
+ #### Belarusian (`bel`)
320
+
321
+ ```text
322
+ INPUT
323
+ Той жа Юры Чыж рабіў свой бізнэс па «паняцьцях», дзякуючы блізкасьці да «цела» меў прывілеі, атрымліваў вольны доступ у прыбытковыя сфэры, куды пускалі толькі абраных, напрыклад, у гандаль нафтапрадуктамі.
324
+
325
+ OUTPUT
326
+ Тој жа Јуры Чиж рабив свој бизнес па «пањацјцјах», дзакујучи близкасци да «цела» мев прывилеи, атрымливав волны доступ у прыбытковыја сферы, куды пускали толки абраных, напрыклад, у гандал нафтапрадуктами.
327
+ ```
328
+
329
+ #### Russian (`rus`)
330
+
331
+ ```text
332
+ INPUT
333
+ В боях за Дубно 14 февраля 1944 года молодой фотокорреспондент находился в боевых порядках 29-го гвардейского кавалерийского полка, оказавшегося в эпицентре сражения за город.
334
+
335
+ OUTPUT
336
+ В бојах за Дубно 14 февраља 1944 года молодој фотокорреспондент находилса в боевых порадках 29-го гвардејского кавалеријского полка, оказавшегоса в епицентре сраженија за город.
337
+ ```
338
+
339
+ #### Ukrainian (`ukr`)
340
+
341
+ ```text
342
+ INPUT
343
+ "Улаштовуватимуть водопровідний вузол на водоводі діаметром 900 мм на розі вул. Зеленогірської та вул. Петра Радченка", — йдеться у повідомленні.
344
+
345
+ OUTPUT
346
+ "Улаштовуватимут водопровидниј вузол на водоводи диаметром 900 мм на рози вул. Зеленогирскоји та вул. Петра Радченка", — јдетса у повидомленни.
347
+ ```
348
+
349
+ #### Rusyn (`rue`)
350
+
351
+ ```text
352
+ INPUT
353
+ Ключі організовано за дихотомічною системою і представлено у вигляді основного ключа до родин і 10 ключів до родів і видів кожної родини (кротові, мідицеві, вовчкові, мишівкові, тушканові, сліпакові, мишачі, хом'якові, норицеві).
354
+
355
+ OUTPUT
356
+ Кључи организовано за дихотомичноју системоју и представлено у вигљади основного кључа до родин и 10 кључив до родив и видив кожноји родини (кротови, мидицеви, вовчкови, мишивкови, тушканови, слипакови, мишачи, хом'јакови, норицеви).
357
+ ```
358
+
359
+ #### Slovak (`slk`)
360
+
361
+ ```text
362
+ INPUT
363
+ Pridať do obľúbenýchProdukt si uložíte medzi vami zvolené obľúbené položky a môžete si ho tak rýchlo nájsť Drevený vozík na kolieskach obsahuje 24 farebných drevených kociek v rôznych tvaroch.
364
+
365
+ OUTPUT
366
+ Придат до облубеныхпродукт си уложите медзи вами зволене облубене положкы а можете си хо так рыхло најст Древены возик на колиесках обсахује 24 фаребных древеных коциек в розных тварох.
367
+ ```
368
+
369
+ ## Choosing a flavor
370
+
371
+ Use `flav=2` when retaining soft consonants and etymological distinctions is
372
+ more important than minimizing the character inventory.
373
+
374
+ Use `flav=3` for a more neutral and compact common orthography. For example,
375
+ some soft-sign distinctions retained by `flav=2` are removed by `flav=3`.
376
+
377
+ Use `flav=4` when maximum simplification is preferred and the additional loss
378
+ of orthographic distinctions is acceptable.
379
+
380
+ ## Input processing
381
+
382
+ This focused port always uses the upstream loose-input preprocessing path. It:
383
+
384
+ 1. normalizes input to Unicode NFC;
385
+ 2. processes continuous sequences of Unicode letters and combining marks;
386
+ 3. converts recognized Latin and Cyrillic variants to an internal common form;
387
+ 4. applies the selected flavor profile;
388
+ 5. renders the selected output type; and
389
+ 6. restores lowercase, title-style, or uppercase word casing.
390
+
391
+ Punctuation, whitespace, digits, and other non-letter characters are preserved.
392
+
393
+ ## Scope and limitations
394
+
395
+ PyUniSlavic provides a common orthographic representation, not a normative
396
+ spelling standard for every living Slavic language.
397
+
398
+ The loose-input rules were designed primarily to accept varied Interslavic
399
+ input. Their broad character coverage is useful for other Slavic languages,
400
+ but the normalizer is not a complete language-specific phonological analyzer.
401
+ For example:
402
+
403
+ - Polish combinations such as `rz` or `dzi` are not always interpreted as a
404
+ single language-specific sound.
405
+ - Some Ukrainian distinctions, including certain uses of `і/и` and `г/ґ`, can
406
+ be merged by normalization.
407
+ - Etymological and standard profiles intentionally merge different sets of
408
+ letters, so conversion is not guaranteed to be reversible.
409
+ - Mixed or camel-case capitalization is reduced to lowercase, title-style, or
410
+ uppercase at the word level, matching the upstream behavior.
411
+
412
+ Applications that require exact source-language phonology should add a
413
+ language-specific input adapter before calling `transliterate`.
414
+
415
+ ## Compatibility testing
416
+
417
+ The initial Python port was checked against the upstream TypeScript behavior
418
+ for all nine supported parameter combinations. The parity suite included 144
419
+ targeted cases and 4,500 deterministic randomized Unicode cases, with identical
420
+ outputs for the tested inputs.
421
+
422
+ ## Upstream project and attribution
423
+
424
+ The core transliteration and loose-normalization rules are adapted from:
425
+
426
+ - [`medzuslovjansky/js-utils`](https://github.com/medzuslovjansky/js-utils)
427
+ - [`src/transliterate/transliterate.ts`](https://github.com/medzuslovjansky/js-utils/blob/main/src/transliterate/transliterate.ts)
428
+ - [Interslavic](https://interslavic.fun/)
429
+
430
+ The upstream project describes `@interslavic/utils` as a collection of
431
+ JavaScript utilities for the Interslavic language, including transliteration.
432
+
433
+ The upstream source is distributed under the MIT License:
434
+
435
+ > Copyright (c) 2021 Medžuslovjansky (Меджусловјанскы)
436
+
437
+ Any distribution of this derived port must include the upstream copyright and
438
+ MIT permission notice. See the
439
+ [`@interslavic/utils` license](https://github.com/medzuslovjansky/js-utils/blob/main/LICENSE)
440
+ for the complete text.
441
+
442
+ ## Project status
443
+
444
+ PyUniSlavic currently implements only the nine combinations documented above.
445
+ Other upstream output modes and flavor profiles are intentionally outside the
446
+ scope of this focused port.
@@ -0,0 +1,429 @@
1
+ # PyUniSlavic
2
+
3
+ A Python implementation for transliterating and normalizing text from many
4
+ Slavic languages and Interslavic into unified writing systems.
5
+
6
+ PyUniSlavic converts text written with a broad range of Latin and Cyrillic
7
+ letters used across Slavic orthographies into a selected common Latin,
8
+ ASCII-oriented, or Cyrillic representation. It preserves the source words,
9
+ grammar, and meaning: it is a writing-system converter, not a machine
10
+ translator.
11
+
12
+ The package is an independent, focused Python port of part of the
13
+ transliteration logic in
14
+ [`@interslavic/utils`](https://github.com/medzuslovjansky/js-utils). The
15
+ upstream algorithm was created for Interslavic transliteration. PyUniSlavic
16
+ also uses its permissive input normalization as a practical common writing
17
+ layer for text in multiple Slavic languages.
18
+
19
+ This broader use follows from the design of
20
+ [Interslavic orthography](https://interslavic.fun/learn/orthography/) itself.
21
+ Its standard Latin and Cyrillic alphabets are cross-Slavic compromises rather
22
+ than adaptations of any one national orthography, and they were designed to be
23
+ intuitively understandable and easy to transliterate between. Its optional
24
+ etymological letters represent shared historical phonemes whose developments
25
+ in modern Slavic languages are largely predictable. The official orthography
26
+ guide therefore explicitly identifies transcription from any Slavic language
27
+ into Interslavic orthography as one possible use of this etymological layer.
28
+ It illustrates the principle with `pęť`: one shared etymological form that
29
+ relates several divergent national spellings of the same inherited word.
30
+
31
+ The
32
+ [upstream implementation](https://github.com/medzuslovjansky/js-utils/blob/main/src/transliterate/transliterate.ts)
33
+ turns that cross-Slavic design into a reusable pipeline: its loose preprocessor
34
+ maps many Latin and Cyrillic letter variants, historical characters, and
35
+ alternative spellings into a common internal representation; the flavor stage
36
+ then preserves or merges selected distinctions before the result is rendered
37
+ in the requested output orthography. PyUniSlavic reuses this orthographic
38
+ pipeline without changing the source vocabulary or grammar. In this way, a
39
+ mechanism created for Interslavic transliteration can also provide practical
40
+ cross-Slavic script unification and same-script normalization. This remains
41
+ orthographic normalization rather than translation, and it does not claim
42
+ exact language-specific phonological conversion for every Slavic language.
43
+
44
+ PyUniSlavic is not an official Interslavic implementation and is not affiliated
45
+ with or endorsed by the Interslavic project.
46
+
47
+ ## Features
48
+
49
+ - Transliterates between common Latin and Cyrillic representations.
50
+ - Normalizes Latin text to a shared Latin orthography.
51
+ - Normalizes Cyrillic text to a shared Cyrillic orthography.
52
+ - Accepts many modern, historical, and alternative Latin and Cyrillic letters
53
+ recognized by the upstream loose-input normalizer.
54
+ - Provides etymological, standard, and simplified output profiles.
55
+ - Preserves punctuation, numbers, spacing, and basic capitalization patterns.
56
+ - Uses only the Python standard library.
57
+ - Does not translate, lemmatize, or alter the grammar of the source language.
58
+
59
+ ## Installation
60
+
61
+ ```bash
62
+ pip install pyunislavic
63
+ ```
64
+
65
+ ## Quick start
66
+
67
+ ```python
68
+ from pyunislavic import transliterate
69
+
70
+ result = transliterate(
71
+ "Меджусловјанскы језык",
72
+ type=1,
73
+ flav=2,
74
+ )
75
+
76
+ print(result)
77
+ # Medžuslovjansky jezyk
78
+ ```
79
+
80
+ The public function is:
81
+
82
+ ```python
83
+ transliterate(i_source: str, type: int, flav: int = 2) -> str
84
+ ```
85
+
86
+ ## Supported output types
87
+
88
+ | `type` | Name | Output |
89
+ | ---: | --- | --- |
90
+ | `1` | Latin | Common Latin orthography with diacritics |
91
+ | `2` | ASCII-oriented | Digraph-based Latin output such as `sz`, `cz`, and `zs` |
92
+ | `5` | Standard Cyrillic | Common Cyrillic orthography using letters such as `ј`, `љ`, and `њ` |
93
+
94
+ `type=2` follows the behavior and naming of the upstream `ASCII` mode. With
95
+ `flav=2`, some supported soft-consonant letters can remain non-ASCII, so this
96
+ mode should be understood as ASCII-oriented rather than an unconditional
97
+ 7-bit ASCII guarantee.
98
+
99
+ ## Supported flavor profiles
100
+
101
+ | `flav` | Name | Behavior |
102
+ | ---: | --- | --- |
103
+ | `2` | Etymological | Preserves the greatest number of distinctions supported by this port |
104
+ | `3` | Standard | Merges several historical vowels and soft-consonant distinctions |
105
+ | `4` | Slovianto | Applies additional simplification, including `ě → e` and `y → i` |
106
+
107
+ Every supported output type can be combined with every supported flavor, for a
108
+ total of nine combinations:
109
+
110
+ ```text
111
+ type = 1, 2, or 5
112
+ flav = 2, 3, or 4
113
+ ```
114
+
115
+ Unsupported values raise `ValueError`.
116
+
117
+ Enums are also available:
118
+
119
+ ```python
120
+ from enum import IntEnum
121
+
122
+
123
+ class TransliterationType(IntEnum):
124
+ LATIN = 1
125
+ ASCII = 2
126
+ STANDARD_CYRILLIC = 5
127
+
128
+
129
+ class FlavorizationType(IntEnum):
130
+ ETYMOLOGICAL = 2
131
+ STANDARD = 3
132
+ SLOVIANTO = 4
133
+ ```
134
+
135
+ They can be imported and passed directly to `transliterate`:
136
+
137
+ ```python
138
+ from pyunislavic import (
139
+ FlavorizationType,
140
+ TransliterationType,
141
+ transliterate,
142
+ )
143
+
144
+ result = transliterate(
145
+ "Medžuslovjansky jezyk",
146
+ type=TransliterationType.STANDARD_CYRILLIC,
147
+ flav=FlavorizationType.STANDARD,
148
+ )
149
+
150
+ print(result)
151
+ # Меджусловјанскы језык
152
+ ```
153
+
154
+ ## Same-script normalization
155
+
156
+ The source and output scripts do not have to be different. Latin input can be
157
+ normalized to the selected common Latin profile, and Cyrillic input can be
158
+ normalized to the selected common Cyrillic profile.
159
+
160
+ ### Latin input
161
+
162
+ ```python
163
+ transliterate(
164
+ "Nawet przy założeniu",
165
+ type=1,
166
+ flav=2,
167
+ )
168
+ # 'Navet przy založeniu'
169
+ ```
170
+
171
+ ### Cyrillic input
172
+
173
+ ```python
174
+ transliterate(
175
+ "Улаштовуватимуть водопровідний вузол",
176
+ type=5,
177
+ flav=2,
178
+ )
179
+ # 'Улаштовуватимуть водопровидниј вузол'
180
+ ```
181
+
182
+ These examples retain Polish and Ukrainian vocabulary and grammar. Only their
183
+ orthographic representation is normalized.
184
+
185
+ ## Extended real-language examples
186
+
187
+ The following examples were produced by the current implementation from text
188
+ in several Slavic languages. Language labels use ISO 639-3 codes. They show
189
+ orthographic conversion and normalization as implemented by the upstream-derived
190
+ pipeline; they are not translations or claims about official national spelling.
191
+
192
+ ### Latin etymological output (`type=1`, `flav=2`)
193
+
194
+ #### Belarusian (`bel`)
195
+
196
+ ```text
197
+ INPUT
198
+ Той жа Юры Чыж рабіў свой бізнэс па «паняцьцях», дзякуючы блізкасьці да «цела» меў прывілеі, атрымліваў вольны доступ у прыбытковыя сфэры, куды пускалі толькі абраных, напрыклад, у гандаль нафтапрадуктамі.
199
+
200
+ OUTPUT
201
+ Toj ža Jury Čiž rabiv svoj biznes pa «panjacjcjah», dźakujuči blizkaści da «cela» mev pryvilei, atrymlivav voĺny dostup u prybytkovyja sfery, kudy puskali toĺki abranyh, napryklad, u gandaĺ naftapraduktami.
202
+ ```
203
+
204
+ #### Russian (`rus`)
205
+
206
+ ```text
207
+ INPUT
208
+ В боях за Дубно 14 февраля 1944 года молодой фотокорреспондент находился в боевых порядках 29-го гвардейского кавалерийского полка, оказавшегося в эпицентре сражения за город.
209
+
210
+ OUTPUT
211
+ V bojah za Dubno 14 fevralja 1944 goda molodoj fotokorrespondent nahodilśa v boevyh poŕadkah 29-go gvardejskogo kavalerijskogo polka, okazavšegośa v epicentre sraženija za gorod.
212
+ ```
213
+
214
+ #### Ukrainian (`ukr`)
215
+
216
+ ```text
217
+ INPUT
218
+ "Улаштовуватимуть водопровідний вузол на водоводі діаметром 900 мм на розі вул. Зеленогірської та вул. Петра Радченка", — йдеться у повідомленні.
219
+
220
+ OUTPUT
221
+ "Ulaštovuvatimut́ vodoprovidnij vuzol na vodovodi diametrom 900 mm na rozi vul. Zelenogirśkoji ta vul. Petra Radčenka", — jdet́śa u povidomlenni.
222
+ ```
223
+
224
+ #### Rusyn (`rue`)
225
+
226
+ ```text
227
+ INPUT
228
+ Ключі організовано за дихотомічною системою і представлено у вигляді основного ключа до родин і 10 ключів до родів і видів кожної родини (кротові, мідицеві, вовчкові, мишівкові, тушканові, сліпакові, мишачі, хом'якові, норицеві).
229
+
230
+ OUTPUT
231
+ Ključi organizovano za dihotomičnoju sistemoju i predstavleno u vigljadi osnovnogo ključa do rodin i 10 ključiv do rodiv i vidiv kožnoji rodini (krotovi, midicevi, vovčkovi, mišivkovi, tuškanovi, slipakovi, mišači, hom'jakovi, noricevi).
232
+ ```
233
+
234
+ #### Serbian (`srp`)
235
+
236
+ ```text
237
+ INPUT
238
+ Бугари су имали 7.000 погинулих 6.000 заробљених војника и 130 топова а Грци су имали 8.700 погинулих.
239
+
240
+ OUTPUT
241
+ Bugari su imali 7.000 poginulih 6.000 zarobljenih vojnika i 130 topova a Grci su imali 8.700 poginulih.
242
+ ```
243
+
244
+ ### Cyrillic etymological output (`type=5`, `flav=2`)
245
+
246
+ #### Slovak (`slk`)
247
+
248
+ ```text
249
+ INPUT
250
+ Pridať do obľúbenýchProdukt si uložíte medzi vami zvolené obľúbené položky a môžete si ho tak rýchlo nájsť Drevený vozík na kolieskach obsahuje 24 farebných drevených kociek v rôznych tvaroch.
251
+
252
+ OUTPUT
253
+ Придать до обльубеныхпродукт си уложите медзи вами зволене обльубене положкы а можете си хо так рыхло најсть Древены возик на колиесках обсахује 24 фаребных древеных коциек в розных тварох.
254
+ ```
255
+
256
+ #### Polish (`pol`)
257
+
258
+ ```text
259
+ INPUT
260
+ Pytałem o Olsztyn, a oni wypowiadali się o mieście w samych superlatywach, twierdząc m.in., że szczególnie latem jest tu pięknie.
261
+
262
+ OUTPUT
263
+ Пыталем о Олштын, а они выповиадали сиѧ о миесьцие в самых суперлатывах, твиердзѫц м.ин., же шчеголние латем јест ту пиѧкние.
264
+ ```
265
+
266
+ #### Czech (`ces`)
267
+
268
+ ```text
269
+ INPUT
270
+ Trojice slonů afrických v Zoo Dvůr Králové nad Labem na Trutnovsku si dnes pochutnala na vánoční nadílce, v níž bylo i několik vánočních stromků.
271
+
272
+ OUTPUT
273
+ Тројице слону африцкых в Зоо Двур Кралове над Лабем на Трутновску си днес похутнала на ваночни надилце, в ниж было и нѣколик ваночних стромку.
274
+ ```
275
+
276
+ ### Latin standard output (`type=1`, `flav=3`)
277
+
278
+ #### Polish (`pol`)
279
+
280
+ ```text
281
+ INPUT
282
+ Pytałem o Olsztyn, a oni wypowiadali się o mieście w samych superlatywach, twierdząc m.in., że szczególnie latem jest tu pięknie.
283
+
284
+ OUTPUT
285
+ Pytalem o Olštyn, a oni vypoviadali sie o miescie v samyh superlatyvah, tvierdzuc m.in., že ščegolnie latem jest tu pieknie.
286
+ ```
287
+
288
+ ### ASCII-oriented standard output (`type=2`, `flav=3`)
289
+
290
+ #### Czech (`ces`)
291
+
292
+ ```text
293
+ INPUT
294
+ Trojice slonů afrických v Zoo Dvůr Králové nad Labem na Trutnovsku si dnes pochutnala na vánoční nadílce, v níž bylo i několik vánočních stromků.
295
+
296
+ OUTPUT
297
+ Trojice slonu africkyh v Zoo Dvur Kralove nad Labem na Trutnovsku si dnes pohutnala na vanoczni nadilce, v nizs bylo i njekolik vanocznih stromku.
298
+ ```
299
+
300
+ ### Cyrillic standard output (`type=5`, `flav=3`)
301
+
302
+ #### Belarusian (`bel`)
303
+
304
+ ```text
305
+ INPUT
306
+ Той жа Юры Чыж рабіў свой бізнэс па «паняцьцях», дзякуючы блізкасьці да «цела» меў прывілеі, атрымліваў вольны доступ у прыбытковыя сфэры, куды пускалі толькі абраных, напрыклад, у гандаль нафтапрадуктамі.
307
+
308
+ OUTPUT
309
+ Тој жа Јуры Чиж рабив свој бизнес па «пањацјцјах», дзакујучи близкасци да «цела» мев прывилеи, атрымливав волны доступ у прыбытковыја сферы, куды пускали толки абраных, напрыклад, у гандал нафтапрадуктами.
310
+ ```
311
+
312
+ #### Russian (`rus`)
313
+
314
+ ```text
315
+ INPUT
316
+ В боях за Дубно 14 февраля 1944 года молодой фотокорреспондент находился в боевых порядках 29-го гвардейского кавалерийского полка, оказавшегося в эпицентре сражения за город.
317
+
318
+ OUTPUT
319
+ В бојах за Дубно 14 февраља 1944 года молодој фотокорреспондент находилса в боевых порадках 29-го гвардејского кавалеријского полка, оказавшегоса в епицентре сраженија за город.
320
+ ```
321
+
322
+ #### Ukrainian (`ukr`)
323
+
324
+ ```text
325
+ INPUT
326
+ "Улаштовуватимуть водопровідний вузол на водоводі діаметром 900 мм на розі вул. Зеленогірської та вул. Петра Радченка", — йдеться у повідомленні.
327
+
328
+ OUTPUT
329
+ "Улаштовуватимут водопровидниј вузол на водоводи диаметром 900 мм на рози вул. Зеленогирскоји та вул. Петра Радченка", — јдетса у повидомленни.
330
+ ```
331
+
332
+ #### Rusyn (`rue`)
333
+
334
+ ```text
335
+ INPUT
336
+ Ключі організовано за дихотомічною системою і представлено у вигляді основного ключа до родин і 10 ключів до родів і видів кожної родини (кротові, мідицеві, вовчкові, мишівкові, тушканові, сліпакові, мишачі, хом'якові, норицеві).
337
+
338
+ OUTPUT
339
+ Кључи организовано за дихотомичноју системоју и представлено у вигљади основного кључа до родин и 10 кључив до родив и видив кожноји родини (кротови, мидицеви, вовчкови, мишивкови, тушканови, слипакови, мишачи, хом'јакови, норицеви).
340
+ ```
341
+
342
+ #### Slovak (`slk`)
343
+
344
+ ```text
345
+ INPUT
346
+ Pridať do obľúbenýchProdukt si uložíte medzi vami zvolené obľúbené položky a môžete si ho tak rýchlo nájsť Drevený vozík na kolieskach obsahuje 24 farebných drevených kociek v rôznych tvaroch.
347
+
348
+ OUTPUT
349
+ Придат до облубеныхпродукт си уложите медзи вами зволене облубене положкы а можете си хо так рыхло најст Древены возик на колиесках обсахује 24 фаребных древеных коциек в розных тварох.
350
+ ```
351
+
352
+ ## Choosing a flavor
353
+
354
+ Use `flav=2` when retaining soft consonants and etymological distinctions is
355
+ more important than minimizing the character inventory.
356
+
357
+ Use `flav=3` for a more neutral and compact common orthography. For example,
358
+ some soft-sign distinctions retained by `flav=2` are removed by `flav=3`.
359
+
360
+ Use `flav=4` when maximum simplification is preferred and the additional loss
361
+ of orthographic distinctions is acceptable.
362
+
363
+ ## Input processing
364
+
365
+ This focused port always uses the upstream loose-input preprocessing path. It:
366
+
367
+ 1. normalizes input to Unicode NFC;
368
+ 2. processes continuous sequences of Unicode letters and combining marks;
369
+ 3. converts recognized Latin and Cyrillic variants to an internal common form;
370
+ 4. applies the selected flavor profile;
371
+ 5. renders the selected output type; and
372
+ 6. restores lowercase, title-style, or uppercase word casing.
373
+
374
+ Punctuation, whitespace, digits, and other non-letter characters are preserved.
375
+
376
+ ## Scope and limitations
377
+
378
+ PyUniSlavic provides a common orthographic representation, not a normative
379
+ spelling standard for every living Slavic language.
380
+
381
+ The loose-input rules were designed primarily to accept varied Interslavic
382
+ input. Their broad character coverage is useful for other Slavic languages,
383
+ but the normalizer is not a complete language-specific phonological analyzer.
384
+ For example:
385
+
386
+ - Polish combinations such as `rz` or `dzi` are not always interpreted as a
387
+ single language-specific sound.
388
+ - Some Ukrainian distinctions, including certain uses of `і/и` and `г/ґ`, can
389
+ be merged by normalization.
390
+ - Etymological and standard profiles intentionally merge different sets of
391
+ letters, so conversion is not guaranteed to be reversible.
392
+ - Mixed or camel-case capitalization is reduced to lowercase, title-style, or
393
+ uppercase at the word level, matching the upstream behavior.
394
+
395
+ Applications that require exact source-language phonology should add a
396
+ language-specific input adapter before calling `transliterate`.
397
+
398
+ ## Compatibility testing
399
+
400
+ The initial Python port was checked against the upstream TypeScript behavior
401
+ for all nine supported parameter combinations. The parity suite included 144
402
+ targeted cases and 4,500 deterministic randomized Unicode cases, with identical
403
+ outputs for the tested inputs.
404
+
405
+ ## Upstream project and attribution
406
+
407
+ The core transliteration and loose-normalization rules are adapted from:
408
+
409
+ - [`medzuslovjansky/js-utils`](https://github.com/medzuslovjansky/js-utils)
410
+ - [`src/transliterate/transliterate.ts`](https://github.com/medzuslovjansky/js-utils/blob/main/src/transliterate/transliterate.ts)
411
+ - [Interslavic](https://interslavic.fun/)
412
+
413
+ The upstream project describes `@interslavic/utils` as a collection of
414
+ JavaScript utilities for the Interslavic language, including transliteration.
415
+
416
+ The upstream source is distributed under the MIT License:
417
+
418
+ > Copyright (c) 2021 Medžuslovjansky (Меджусловјанскы)
419
+
420
+ Any distribution of this derived port must include the upstream copyright and
421
+ MIT permission notice. See the
422
+ [`@interslavic/utils` license](https://github.com/medzuslovjansky/js-utils/blob/main/LICENSE)
423
+ for the complete text.
424
+
425
+ ## Project status
426
+
427
+ PyUniSlavic currently implements only the nine combinations documented above.
428
+ Other upstream output modes and flavor profiles are intentionally outside the
429
+ scope of this focused port.
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling>=1.27"]
3
+ build-backend = "hatchling.build"
4
+
5
+
6
+ [project]
7
+ name = "pyunislavic"
8
+ version = "0.1.0"
9
+ description = "A Python implementation for transliterating and normalizing text from many Slavic languages and Interslavic into unified writing systems."
10
+ readme = "README.md"
11
+ requires-python = ">=3.9"
12
+ license = "MIT"
13
+ license-files = ["LICENSE"]
14
+ authors = [
15
+ { name = "Zijie ZHANG" }
16
+ ]
17
+ keywords = [
18
+ "slavic",
19
+ "interslavic",
20
+ "transliteration",
21
+ "cyrillic",
22
+ "latin",
23
+ "orthography",
24
+ "normalization",
25
+ "romanization",
26
+ "cyrillization",
27
+ ]
28
+ classifiers = [
29
+ "Development Status :: 3 - Alpha",
30
+ "Intended Audience :: Developers",
31
+ "Operating System :: OS Independent",
32
+ "Programming Language :: Python :: 3",
33
+ "Typing :: Typed",
34
+ "Topic :: Text Processing :: Linguistic",
35
+ ]
36
+ dependencies = []
37
+
38
+
39
+ [tool.hatch.build.targets.wheel]
40
+ packages = ["src/pyunislavic"]
@@ -0,0 +1,11 @@
1
+ from .transliterate import (
2
+ FlavorizationType,
3
+ TransliterationType,
4
+ transliterate,
5
+ )
6
+
7
+ __all__ = [
8
+ "FlavorizationType",
9
+ "TransliterationType",
10
+ "transliterate",
11
+ ]
File without changes
@@ -0,0 +1,376 @@
1
+ """Faithful Python subset of ``transliterate.ts``.
2
+
3
+ Supported combinations:
4
+
5
+ * type=1 (Latin), flav=2/3/4
6
+ * type=2 (ASCII), flav=2/3/4
7
+ * type=5 (Standard Cyrillic), flav=2/3/4
8
+
9
+ Only the original loose-input preprocessing path is included.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from enum import IntEnum
15
+ import re
16
+ import unicodedata
17
+
18
+
19
+ class TransliterationType(IntEnum):
20
+ LATIN = 1
21
+ ASCII = 2
22
+ STANDARD_CYRILLIC = 5
23
+
24
+
25
+ class FlavorizationType(IntEnum):
26
+ ETYMOLOGICAL = 2
27
+ STANDARD = 3
28
+ SLOVIANTO = 4
29
+
30
+
31
+ _SUPPORTED_TYPES = frozenset(TransliterationType)
32
+ _SUPPORTED_FLAVORS = frozenset(FlavorizationType)
33
+ _VOWELS = frozenset("aeiouyąęųåėȯèòěê")
34
+
35
+
36
+ def transliterate(i_source: str, type: int, flav: int = 2) -> str:
37
+ """Transliterate text using one of the nine supported combinations.
38
+
39
+ Args:
40
+ i_source: Input text.
41
+ type: 1 for Latin, 2 for ASCII, or 5 for Standard Cyrillic.
42
+ flav: 2 for etymological, 3 for standard, or 4 for Slovianto.
43
+
44
+ The function always uses the loose preprocessing mode from the TypeScript
45
+ implementation. Non-letter characters are preserved unchanged.
46
+ """
47
+ if not isinstance(i_source, str):
48
+ raise TypeError("i_source must be a string")
49
+ if type not in _SUPPORTED_TYPES:
50
+ raise ValueError("type must be one of: 1 (Latin), 2 (ASCII), 5 (Standard Cyrillic)")
51
+ if flav not in _SUPPORTED_FLAVORS:
52
+ raise ValueError("flav must be one of: 2 (Etymological), 3 (Standard), 4 (Slovianto)")
53
+
54
+ source = unicodedata.normalize("NFC", i_source)
55
+ result: list[str] = []
56
+ word: list[str] = []
57
+
58
+ def flush_word() -> None:
59
+ if not word:
60
+ return
61
+ original_word = "".join(word)
62
+ marked_original = f"%{original_word}%"
63
+ internal_word = _nmsify_loose(marked_original.lower())
64
+ result.append(
65
+ _transliterate_word(
66
+ internal_word,
67
+ marked_original,
68
+ int(type),
69
+ int(flav),
70
+ )
71
+ )
72
+ word.clear()
73
+
74
+ for char in source:
75
+ if unicodedata.category(char)[:1] in {"L", "M"}:
76
+ word.append(char)
77
+ else:
78
+ flush_word()
79
+ result.append(char)
80
+ flush_word()
81
+
82
+ return "".join(result)
83
+
84
+
85
+ def _transliterate_word(
86
+ i_w: str,
87
+ original_word: str,
88
+ type: int,
89
+ flav: int,
90
+ ) -> str:
91
+ # 'ŕ' remains between two consonants; otherwise it is represented by 'ř'.
92
+ i_w = i_w.replace("ŕ", "ř")
93
+ a_pos = i_w.find("ř")
94
+ if (
95
+ a_pos > 1
96
+ and i_w[a_pos - 1] != "%"
97
+ and i_w[a_pos - 1] not in _VOWELS
98
+ and i_w[a_pos + 1] not in _VOWELS
99
+ ):
100
+ i_w = i_w[:a_pos] + "ŕ" + i_w[a_pos + 1 :]
101
+
102
+ # Protect rj/jr, then classify the first remaining consonantal 'r'.
103
+ i_w = i_w.replace("rj", "Rj")
104
+ i_w = i_w.replace("jr", "jR")
105
+ r_pos = i_w.find("r")
106
+ if (
107
+ r_pos > 1
108
+ and i_w[r_pos - 1] != "%"
109
+ and i_w[r_pos - 1] not in _VOWELS
110
+ and i_w[r_pos + 1] not in _VOWELS
111
+ ):
112
+ i_w = i_w[:r_pos] + "ṙ" + i_w[r_pos + 1 :]
113
+ i_w = re.sub(r"([šžčc])ṙ", r"\g<1>ŕ", i_w)
114
+ i_w = i_w.replace("R", "r")
115
+
116
+ i_w = i_w.replace("x", "ks")
117
+ i_w = re.sub(r"([ńľřťďśź])j", r"\g<1>ıj", i_w)
118
+ i_w = re.sub(r"([dsz])j", r"\g<1>#j", i_w)
119
+ i_w = i_w.replace("%obj", "ob#j")
120
+ i_w = i_w.replace("%neobj", "neob#j")
121
+ i_w = i_w.replace("%vj", "v#j")
122
+
123
+ # Flavor 2 is the shared base for flavors 2, 3, and 4.
124
+ i_w = i_w.replace("ê", "ě")
125
+ i_w = i_w.replace("ŭ", "v")
126
+ i_w = re.sub(r"[ṱḓ]", "", i_w)
127
+ i_w = re.sub(r"[’`]", "#%", i_w)
128
+ i_w = re.sub(r"([čšžj])ě", r"\g<1>e", i_w)
129
+
130
+ # Flavors 3 and 4 share the standard simplifications.
131
+ if flav in (3, 4):
132
+ i_w = re.sub(r"[ęė]", "e", i_w)
133
+ i_w = i_w.replace("å", "a")
134
+ i_w = i_w.replace("ȯ", "o")
135
+ i_w = i_w.replace("ų", "u")
136
+ i_w = i_w.replace("ć", "č")
137
+ i_w = i_w.replace("đ", "dž")
138
+ i_w = i_w.replace("ř", "r")
139
+ i_w = i_w.replace("ľ", "l")
140
+ i_w = i_w.replace("ń", "n")
141
+ i_w = i_w.replace("ť", "t")
142
+ i_w = i_w.replace("ď", "d")
143
+ i_w = i_w.replace("ś", "s")
144
+ i_w = i_w.replace("ź", "z")
145
+
146
+ if flav == 4:
147
+ i_w = i_w.replace("ě", "e")
148
+ i_w = i_w.replace("y", "i")
149
+
150
+ if type == 1:
151
+ if flav == 2:
152
+ i_w = i_w.replace("ṙ", "r")
153
+ i_w = i_w.replace("ř", "ŕ")
154
+ i_w = i_w.replace("ľ", "ĺ")
155
+ i_w = i_w.replace("ť", "t́")
156
+ i_w = i_w.replace("ď", "d́")
157
+ i_w = re.sub(r"([čšžj])ŕ", r"\g<1>r", i_w)
158
+ elif flav in (3, 4):
159
+ i_w = re.sub(r"[ṙŕ]", "r", i_w)
160
+ i_w = i_w.replace("ȯ", "ă")
161
+
162
+ elif type == 2:
163
+ i_w = re.sub(r"[ṙŕ]", "r", i_w)
164
+ i_w = i_w.replace("š", "sz")
165
+ i_w = re.sub(r"[ćč]", "cz", i_w)
166
+ i_w = i_w.replace("ž", "zs")
167
+ i_w = i_w.replace("đ", "dzs")
168
+ i_w = i_w.replace("ě", "je")
169
+ i_w = i_w.replace("å", "a")
170
+ i_w = re.sub(r"[ęė]", "e", i_w)
171
+ i_w = i_w.replace("ȯ", "o")
172
+ i_w = i_w.replace("ų", "u")
173
+
174
+ elif type == 5:
175
+ i_w = i_w.replace("lj", "љ")
176
+ i_w = i_w.replace("nj", "њ")
177
+ i_w = i_w.replace("a", "а")
178
+ i_w = i_w.replace("å", "ӑ")
179
+ i_w = i_w.replace("b", "б")
180
+ i_w = i_w.replace("c", "ц")
181
+ i_w = i_w.replace("ć", "ћ")
182
+ i_w = i_w.replace("č", "ч")
183
+ i_w = re.sub(r"[dḓ]", "д", i_w)
184
+ i_w = i_w.replace("ď", "дь")
185
+ i_w = i_w.replace("đ", "ђ")
186
+ i_w = i_w.replace("e", "е")
187
+ i_w = i_w.replace("ę", "ѧ")
188
+ i_w = i_w.replace("ě", "є")
189
+ i_w = i_w.replace("f", "ф")
190
+ i_w = i_w.replace("g", "г")
191
+ i_w = i_w.replace("h", "х")
192
+ i_w = i_w.replace("i", "и")
193
+ i_w = i_w.replace("j", "ј")
194
+ i_w = i_w.replace("k", "к")
195
+ i_w = i_w.replace("l", "л")
196
+ i_w = i_w.replace("ľ", "ль")
197
+ i_w = i_w.replace("m", "м")
198
+ i_w = i_w.replace("n", "н")
199
+ i_w = i_w.replace("ń", "нь")
200
+ i_w = i_w.replace("o", "о")
201
+ i_w = i_w.replace("ȯ", "ъ")
202
+ i_w = i_w.replace("p", "п")
203
+ i_w = i_w.replace("r", "р")
204
+ i_w = i_w.replace("ř", "рь")
205
+ i_w = i_w.replace("s", "с")
206
+ i_w = i_w.replace("ś", "сь")
207
+ i_w = i_w.replace("š", "ш")
208
+ i_w = re.sub(r"[tṱ]", "т", i_w)
209
+ i_w = i_w.replace("ť", "ть")
210
+ i_w = i_w.replace("u", "у")
211
+ i_w = i_w.replace("ų", "ѫ")
212
+ i_w = i_w.replace("ŭ", "ў")
213
+ i_w = i_w.replace("v", "в")
214
+ i_w = i_w.replace("y", "ы")
215
+ i_w = i_w.replace("z", "з")
216
+ i_w = i_w.replace("ź", "зь")
217
+ i_w = i_w.replace("ž", "ж")
218
+ i_w = i_w.replace("’", "ъ")
219
+ i_w = i_w.replace("`", "’")
220
+
221
+ i_w = re.sub(r"[ṙŕ]", "р", i_w)
222
+ i_w = i_w.replace("ė", "е")
223
+ if flav == 2:
224
+ i_w = i_w.replace("є", "ѣ")
225
+
226
+ i_w = i_w.replace("jj", "j")
227
+ i_w = re.sub(r"[#ı%]", "", i_w)
228
+ original_word = original_word.replace("%", "")
229
+
230
+ i_w_first = i_w[:1]
231
+ i_w_rest = i_w[1:]
232
+ if original_word[0] == original_word[0].lower():
233
+ return i_w.lower()
234
+ if len(original_word) > 1 and original_word[1] == original_word[1].lower():
235
+ return i_w_first.upper() + i_w_rest.lower()
236
+ return i_w.upper()
237
+
238
+
239
+ def _nmsify_loose(i_w: str) -> str:
240
+ # Iotated vowels and soft-sign sequences.
241
+ i_w = re.sub(r"[яꙗ]", "#a", i_w)
242
+ i_w = i_w.replace("ьа", "#a")
243
+ i_w = i_w.replace("ѥ", "#e")
244
+ i_w = i_w.replace("ье", "#e")
245
+ i_w = i_w.replace("ї", "#i")
246
+ i_w = i_w.replace("ьи", "#i")
247
+ i_w = i_w.replace("ё", "#o")
248
+ i_w = i_w.replace("ьо", "#o")
249
+ i_w = i_w.replace("ю", "#u")
250
+ i_w = i_w.replace("ьу", "#u")
251
+ i_w = i_w.replace("ѩ", "#ę")
252
+ i_w = i_w.replace("ьѧ", "#ę")
253
+ i_w = i_w.replace("ѭ", "#ų")
254
+ i_w = i_w.replace("ьѫ", "#ų")
255
+ i_w = i_w.replace("нь", "ń")
256
+ i_w = i_w.replace("н#", "nj")
257
+ i_w = i_w.replace("њ", "nj")
258
+ i_w = i_w.replace("ль", "ĺ")
259
+ i_w = i_w.replace("л#", "lj")
260
+ i_w = i_w.replace("љ", "lj")
261
+ i_w = i_w.replace("рь", "ŕ")
262
+ i_w = i_w.replace("р#", "ŕ")
263
+ i_w = i_w.replace("ть", "ť")
264
+ i_w = i_w.replace("т#", "ť")
265
+ i_w = i_w.replace("дь", "ď")
266
+ i_w = i_w.replace("д#", "ď")
267
+ i_w = i_w.replace("сь", "ś")
268
+ i_w = i_w.replace("с#", "ś")
269
+ i_w = i_w.replace("зь", "ź")
270
+ i_w = i_w.replace("з#", "ź")
271
+ i_w = i_w.replace("ь%", "%")
272
+ i_w = re.sub(r"[ђѓ]", "đ", i_w)
273
+ i_w = re.sub(r"[ћќ]", "ć", i_w)
274
+ i_w = i_w.replace("ѕ", "dz")
275
+ i_w = i_w.replace("џ", "dž")
276
+
277
+ # Cyrillic to the internal Latin representation.
278
+ i_w = i_w.replace("а", "a")
279
+ i_w = i_w.replace("б", "b")
280
+ i_w = i_w.replace("в", "v")
281
+ i_w = re.sub(r"[гґ]", "g", i_w)
282
+ i_w = i_w.replace("д", "d")
283
+ i_w = re.sub(r"[еэ]", "e", i_w)
284
+ i_w = re.sub(r"[єѣ]", "ě", i_w)
285
+ i_w = i_w.replace("ж", "ž")
286
+ i_w = re.sub(r"[зꙁꙀ]", "z", i_w)
287
+ i_w = re.sub(r"[иіѵѷ]", "i", i_w)
288
+ i_w = re.sub(r"[йјь#]", "j", i_w)
289
+ i_w = i_w.replace("к", "k")
290
+ i_w = i_w.replace("л", "l")
291
+ i_w = i_w.replace("м", "m")
292
+ i_w = i_w.replace("н", "n")
293
+ i_w = re.sub(r"[оѡ]", "o", i_w)
294
+ i_w = i_w.replace("п", "p")
295
+ i_w = i_w.replace("р", "r")
296
+ i_w = i_w.replace("с", "s")
297
+ i_w = re.sub(r"[тѳ]", "t", i_w)
298
+ i_w = re.sub(r"[уȣѹ]", "u", i_w)
299
+ i_w = i_w.replace("ф", "f")
300
+ i_w = i_w.replace("х", "h")
301
+ i_w = i_w.replace("ц", "c")
302
+ i_w = i_w.replace("ч", "č")
303
+ i_w = i_w.replace("ш", "š")
304
+ i_w = i_w.replace("щ", "šč")
305
+ i_w = re.sub(r"[ыꙑ]", "y", i_w)
306
+ i_w = i_w.replace("ъ", "ȯ")
307
+ i_w = i_w.replace("ў", "ŭ")
308
+ i_w = i_w.replace("ѧ", "ę")
309
+ i_w = i_w.replace("ѫ", "ų")
310
+ i_w = i_w.replace("ѱ", "ps")
311
+ i_w = i_w.replace("ѯ", "ks")
312
+ i_w = i_w.replace("ӑ", "å")
313
+
314
+ i_w = i_w.replace("⁙", ".")
315
+
316
+ # Boundaries that protect morphology from later j/s transformations.
317
+ i_w = i_w.replace("zsk", "z#sk")
318
+ i_w = i_w.replace("zst", "z#st")
319
+ i_w = i_w.replace("%izs", "%iz#s")
320
+ i_w = i_w.replace("%bezs", "%bez#s")
321
+ i_w = i_w.replace("%obezs", "%obez#s")
322
+ i_w = i_w.replace("%razs", "%raz#s")
323
+ i_w = i_w.replace("%råzs", "%råz#s")
324
+ i_w = i_w.replace("%szadu%", "%s#zadu%")
325
+ i_w = i_w.replace("%vozs", "%voz#s")
326
+ i_w = i_w.replace("%vȯzs", "%vȯz#s")
327
+ i_w = i_w.replace("konjug", "kon#jug")
328
+ i_w = i_w.replace("konjun", "kon#jun")
329
+ i_w = i_w.replace("injek", "in#jek")
330
+
331
+ i_w = re.sub(r"s[xz]", "š", i_w)
332
+ i_w = re.sub(r"c[xz]", "č", i_w)
333
+ i_w = re.sub(r"z[xs]", "ž", i_w)
334
+ i_w = i_w.replace("ż", "ž")
335
+ i_w = i_w.replace("ye", "ě")
336
+
337
+ i_w = i_w.replace("qu", "kv")
338
+ i_w = i_w.replace("ŀ", "ȯl")
339
+ i_w = re.sub(r"[ăq`]", "’", i_w)
340
+ i_w = i_w.replace("ch", "h")
341
+ i_w = i_w.replace("w", "v")
342
+ i_w = i_w.replace("x", "ks")
343
+
344
+ i_w = re.sub(r"[áàâā]", "a", i_w)
345
+ i_w = re.sub(r"[íìîīĭı]", "i", i_w)
346
+ i_w = re.sub(r"[úûůū]", "u", i_w)
347
+ i_w = re.sub(r"[ąǫũ]", "ų", i_w)
348
+ i_w = i_w.replace("ù", "ŭ")
349
+ i_w = re.sub(r"[éē]", "e", i_w)
350
+ i_w = re.sub(r"[ĕëè]", "ė", i_w)
351
+ i_w = re.sub(r"[œóô]", "o", i_w)
352
+ i_w = re.sub(r"[ŏöò]", "ȯ", i_w)
353
+ i_w = i_w.replace("ý", "y")
354
+ i_w = i_w.replace("ł", "l")
355
+ i_w = i_w.replace("ç", "c")
356
+ i_w = i_w.replace("ʒ", "z")
357
+ i_w = i_w.replace("ĵ", "j")
358
+ i_w = re.sub(r"[ĺļlj]", "ľ", i_w)
359
+ i_w = re.sub(r"[ňñņnj]", "ń", i_w)
360
+ i_w = i_w.replace("ř", "ŕ")
361
+ i_w = i_w.replace("t́", "ť")
362
+ i_w = i_w.replace("d́", "ď")
363
+
364
+ i_w = re.sub(r"([jćđšžč])y", r"\g<1>i", i_w)
365
+ return i_w.replace("jj", "j")
366
+
367
+
368
+ __all__ = [
369
+ "FlavorizationType",
370
+ "TransliterationType",
371
+ "transliterate",
372
+ ]
373
+
374
+
375
+
376
+