Вікісловник
ukwiktionary
https://uk.wiktionary.org/wiki/%D0%93%D0%BE%D0%BB%D0%BE%D0%B2%D0%BD%D0%B0_%D1%81%D1%82%D0%BE%D1%80%D1%96%D0%BD%D0%BA%D0%B0
MediaWiki 1.48.0-wmf.1
case-sensitive
Медіа
Спеціальна
Обговорення
Користувач
Обговорення користувача
Вікісловник
Обговорення Вікісловника
Файл
Обговорення файлу
MediaWiki
Обговорення MediaWiki
Шаблон
Обговорення шаблону
Довідка
Обговорення довідки
Категорія
Обговорення категорії
Додаток
Обговорення додатка
Індекс
Обговорення індексу
Субтитри
Обговорення субтитрів
Модуль
Обговорення модуля
Подія
Обговорення події
plameniak
0
9468
575011
562883
2026-10-29T08:51:14Z
Ігнат Лостюк
22957
575011
wikitext
text/x-wiki
{{=sk=}}
=== Морфосинтаксичні ознаки ===
{{імен sk m2|склади={{склади|plameniak}}|plameniak}}
{{морфо|}}
=== Вимова ===
* {{transcriptions|plameɲɪ̯ak|plameɲɪ̯aki}}
* {{audio|Sk-plameniak.ogg|прослухати вимову}}
=== Семантичні властивості ===
{{іл|Lightmatter_flamingo2.jpg|Фламінго}}
===Значення===
# [[фламінго]]{{семантика|синоніми=|антоніми=|гіпероніми=vták|гіпоніми=}} {{приклад2|текст=|переклад=}}
{{списки семантичних зв'язків}}
==== Холоніми ====
#
==== Мероніми ====
#
<!-- ==== Асоціації ==== -->
===Усталені та термінологічні словосполучення, фразеологізми ===
*
==== Колокації ====
====Прислів’я та приказки ====
===Споріднені слова===
{{спорідн|
|згруб=
|змп= plameniačik
|власні назви=
|прізвища=
|топоніми=
|іменники=
|прикметники=
|дієприкметники=
|дієслова=
|прислівники=
}}
{{довжина слова|9|lang=sk}}
{{Категорія|мова=sk|Птахи|||||}}
eq9o8r18pu326ose8ka36bwyixoiw1f
Шаблон:відмінки sk
10
59431
575006
270712
2026-10-29T08:41:03Z
Ігнат Лостюк
22957
575006
wikitext
text/x-wiki
{| style="float:right; margin-left:0.5em; margin-bottom:0.5em; border: 1px solid #6699CC; border-collapse:collapse;" cellpadding="2" rules="all" width="210"
! bgcolor="#EEF9FF" | [[відмінок]]
! bgcolor="#EEF9FF" | [[однина]]
! bgcolor="#EEF9FF" | [[множина]]
|-
| bgcolor="#EEF9FF" | [[називний|Н.]]
| bgcolor="#ffffff" | {{{nom-sg| }}}
| bgcolor="#ffffff" | {{{nom-pl| }}}
|-
| bgcolor="#EEF9FF" | [[родовий|Р.]]
| bgcolor="#ffffff" | {{{gen-sg| }}}
| bgcolor="#ffffff" | {{{gen-pl| }}}
|-
| bgcolor="#EEF9FF" | [[давальний|Д.]]
| bgcolor="#ffffff" | {{{dat-sg| }}}
| bgcolor="#ffffff" | {{{dat-pl| }}}
|-
| bgcolor="#EEF9FF" | [[знахідний|З.]]
| bgcolor="#ffffff" | {{{acc-sg|{{{acc-sg|}}}}}}
| bgcolor="#ffffff" | {{{acc-pl|{{{acc-pl|}}}}}}
|-
| bgcolor="#EEF9FF" | [[орудний|Ор.]]
| bgcolor="#ffffff" | {{{ins-sg| }}}
| bgcolor="#ffffff" | {{{ins-pl| }}}
|-
| bgcolor="#EEF9FF" | [[місцевий|М.]]
| bgcolor="#ffffff" | {{{loc-sg|{{{loc-sg|}}}}}}
| bgcolor="#ffffff" | {{{loc-pl|{{{loc-pl|}}}}}}
|- class="{{{vok-sg|hiddenStructure}}}"
| bgcolor="#eeeeee" | [[кличний|Кл.]]
| bgcolor="#eeeeee" | {{{voc-sg|{{{voc-sg|}}}}}}
| bgcolor="#eeeeee" | {{{voc-pl|{{{voc-pl|}}}}}}
|}<includeonly>{{#ifeq:{{NAMESPACE}}|{{ns:0}}|[[Категорія:Словацькі іменники]]}}</includeonly><noinclude>[[Категорія:Шаблони словозмін/sk]]</noinclude>
50yzdtzab8brduoa9y3tz213a4gxxu6
Montenegro
0
88454
574999
567271
2026-10-28T13:57:29Z
Ігнат Лостюк
22957
574999
wikitext
text/x-wiki
{{=en=}}
=== Морфосинтаксичні ознаки ===
{{імен en s|склади={{склади|Mon|te|neg|ro}}|Montenegro}}
{{топонім|en}}
{{морфо-en}}
=== Вимова ===
* {{transcription-en-us|mɒn.tɪˈnɛɡ.ɹəʊ], [mɒn.tɪˈneɪ.ɡɹəʊ|ˌmɑn.təˈneɪ.ɡɹoʊ], [ˌmɑn.tɪˈni.ɡɹoʊ}}
* {{audio2|LL-Q1860 (eng)-Soundguys-Montenegro.wav|прослухати вимову}}
=== Семантичні властивості ===
{{іл|Flag_of_Montenegro.svg|Flag of '''Montenegro'''}}
==== Значення ====
# {{геогр.|en}} [[Чорногорія]] {{семантика|синоніми=|антоніми=|гіпероніми=country|гіпоніми=}} {{приклад|текст=|переклад=|джерело=}}
{{списки семантичних зв’язків}}
===Усталені та термінологічні словосполучення, фразеологізми ===
*
==== Колокації ====
====Прислів'я та приказки ====
===Споріднені слова===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=Montenegrin, Montenegran
|прикметники=Montenegrin, Montenegran
|дієприкметники=
|дієслова=
|прислівники=
}}
=== Етимологія ===
Від {{lang|vec|Montenegro}}, (від {{lang|vec|mónte}} + [[negro]]).
=== Джерела ===
{{довжина слова|10|lang=en}}
{{Категорія|мова=en|Країни|Країни Європи|Латинізми|||}}
75uny0p9g0mxs47q3gkwlgdl5il7ho2
піца
0
90272
575003
574709
2026-10-28T19:00:58Z
Ігнат Лостюк
22957
575003
wikitext
text/x-wiki
= {{-uk-}} =
=== Морфосинтаксичні ознаки ===
{{імен uk 1*a f una|склади={{склади|пі́|ца}}|пі́ц|піц}}
{{морфо-uk|піц|+а}}
=== Вимова ===
* {{transcriptions-uk|пі́ца|пі́ци}}
* {{audio2|LL-Q8798 (ukr)-Bicolino34-піца.wav|Прослухати вимову}}
* {{транскрипція|п’і́ца}}
=== Семантичні властивості ===
{{іл|Brier-hill-pizza.jpg}}
==== Значення ====
# {{кулін.|uk}} італійська національна [[страва]], [[печений]] [[млинець]] із кислого (дріжджового) тіста з [[сир]]ом, [[помідор]]ами та різними добавками на його поверхні {{семантика|синоніми=|антоніми=|гіпероніми=страва, їжа|гіпоніми=}} {{приклад2|||джерело=}}
{{списки семантичних зв'язків}}
=== Усталені та термінологічні словосполучення, фразеологізми ===
*
=== Споріднені слова ===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=піцерія
|прикметники=
|дієприкметники=
|дієслова=
|прислівники=
|повн=
}}
=== Етимологія ===
Від {{lang|it|pizza}}. {{пор.}} {{lang|ru|пицца}}.
=== Переклад ===
{{переклад|
|ab=
|abq=
|ady=
|af=
|agh=
|ain.kana=
|ain.lat=
|ain=
|aja=
|akz=
|ale=
|als=
|alt=
|an=
|ang=
|ar={{t|ar|بيتزا|f|tr=bītzā}}
|arc.jud=
|arc.syr=
|art=
|asm=
|ast=
|av=
|ave=
|ay=
|az=
|ba=
|bar=
|bat-smg=
|be={{t|be|піца|f}}
|bg={{t|bg|пица|f}}
|bm=
|bn=
|br=
|bs=
|bua=
|ca=
|ce=
|chm=
|chu.cyr=
|chu.glag=
|cjs=
|co=
|crh=
|cs={{t|cs|pizza|f}}
|csb=
|cv=
|cy=
|da={{t|da|pizza|c}}
|dar=
|de={{t|de|Pizza|f}}
|dsb=
|dv=
|el={{t|el|πίτσα|f|tr=pítsa}}
|en={{t|en|pizza}}
|eo={{t|eo|pico}}
|es={{t|es|pizza|f}}
|et=
|eu={{t|eu|pizza}}
|eve=
|evn=
|ewe=
|fa={{t|fa|پیتزا|tr=pitzâ}}
|fi={{t|fi|pizza}}, {{t|fi|pitsa}}
|fo={{t|fo|pitsa|f}}
|fr={{t|fr|pizza|f}}
|frp=
|fur=
|fy=
|ga=
|gag=
|gd=
|gl={{t|gl|pizza|f}}
|gld=
|gn=
|grc=
|gu=
|gv=
|ha=
|haw=
|he=
|hi={{t|hi|पिज़ा|m|tr=pizā}}, {{t|hi|पिज़्ज़ा|m|tr=pizzā}}
|hr=
|hsb=
|ht=
|hu=
|hy={{t|hy|պիցցա|tr=picʻcʻa}}
|ia=
|id=
|ik=
|inh=
|io=
|is=
|it={{t|it|pizza|f}}
|itl=
|iu=
|ja={{t|ja|ピザ|tr=piza}}, {{t|ja|ピッツァ|tr=pittsa}}, {{t|ja|ピザパイ|tr=pizapai}}
|jbo=
|jv=
|ka=
|kaa=
|kbd=
|kim=
|kjh=
|kk=
|kl=
|km=
|kn=
|ko={{t|ko|피자|tr=pija}} (В Південній Кореї), {{t|ko|삐짜|tr=ppijja}} (В Північній Кореї)
|koi=
|kok=
|kom=
|krc=
|krl=
|ku=
|kum=
|kw=
|ky=
|la=
|lad=
|lb={{t|lb|Pizza|f}}
|lbe=
|lez=
|li=
|lmo=
|ln=
|lo=
|lt=
|lv=
|mas=
|mdf=
|mg=
|mi={{t|mi|parehe}}
|mk={{t|mk|пица|f}}
|ml=
|mn={{t|mn|пицца}}
|mo=
|mr={{t|mr|पिझ्झा|m|tr=pijhjhā}}
|ms={{t|ms|pizza}}
|mt=
|myv=
|na=
|nah=
|nap=
|nds={{t|nds|Pieza|m}}
|nio=
|nl={{t|nl|pizza|f}}
|nn={{t|nn|pizza|m}}
|no={{t|no|pizza|m}}
|nov=
|nv={{t|nv|náneeskaadítéél}}
|oc=
|oj=
|os=
|pa={{t|pa|ਪੀਜ਼ਾ|m|tr=pīzā}}
|pap=
|pl={{t|pl|pizza|f}}
|pms=
|prs=
|ps=
|pt=
|qu=
|rap=
|rm=
|ro={{t|ro|pizza|f}}
|roh=
|rom=
|ru={{t|ru|пицца|f}}
|rue=
|rw=
|sa=
|sah=
|sc=
|scn=
|sco=
|sd=
|se=
|sjd=
|sk=
|sl={{t|sl|pica|f}}, {{t|sl|pizza|f}}
|slovio-c=
|slovio-l=
|sm=
|so=
|sq={{t|sq|picë|f}}
|sr={{t|sr|пица|f}}
|sr-c=
|sr-l={{t|sr|pica|f}}
|su=
|sv=
|sw=
|szl=
|ta=
|tab=
|te=
|tet=
|tg=
|th=
|tk={{t|tk|pissa}}
|tl=
|tn=
|tpi=
|tr={{t|tr|pizza}}
|tt.cyr={{t|tt|Пицца}}
|tt.lat={{t|tt|Pitstsa}}
|tt=
|ttt=
|ty=
|tyv=
|udm=
|ug=
|ur=
|uz=
|vep=
|vi=
|vo=
|vot=
|vro=
|wa=
|wo=
|xal=
|xh=
|xsr=
|yi=
|yo=
|yrk=
|ze=
|zh=
|zh-cn=
|zh-tw=
|zu=
|zza=
}}
=== Джерела ===
<references />
* <!-- {{СУМ20-слов}} -->
* <!-- {{СУМ-11}} -->
{{довжина слова|4|lang=uk}}
<!-- {{Категорія|Слова, відсутні у ВТССУМ|}} -->
{{Категорія|мова=uk|Предметні слова|Страва|Італійська кухня|||}}
{{=be=|{{PAGENAME}}}}
=== Морфосинтаксичні ознаки ===
{{імен be 1a f una|склади={{склади|пі́|ца}}|пі́ц|піц}}
{{морфо-be|}}
=== Вимова ===
* {{transcriptions-be|пі́ца|пі́цы}}
* {{audio2|LL-Q9091 (bel)-Ssvb-піца.wav|прослухати вимову}}
=== Семантичні властивості ===
==== Значення ====
# {{as uk}} {{семантика|синоніми=|антоніми=|гіпероніми=страва, ежа|гіпоніми=}} {{приклад2|текст=|переклад=}}
{{списки семантичних зв’язків}}
==== Холоніми ====
#
==== Мероніми ====
#
===Усталені та термінологічні словосполучення, фразеологізми ===
*
==== Колокації ====
====Прислів'я та приказки ====
===Споріднені слова===
{{спорідн|
|згруб=
|змп=
|власні назви=
|іменники=піцэрыя
|прикметники=
|дієслова=
|прислівники=
}}
=== Етимологія ===
Від {{етимологія|be}}
=== Джерела ===
{{довжина слова|4|lang=be}}
{{Категорія|мова=be|Предметні слова|Страва|Італійська кухня|||}}
hmnpuqfkwbn6f8mie1cr435xisvs4io
Montenegros
0
90859
575000
2026-10-28T13:59:00Z
Ігнат Лостюк
22957
Створена сторінка: = {{-en-}} = === Морфосинтаксичні ознаки === <b>{{склади|Mon|te|ne|gros}}</b> === Вимова === * {{transcription-en-us|mɒn.tɪˈnɛɡ.ɹəʊz], [mɒn.tɪˈneɪ.ɡɹəʊz|ˌmɑn.təˈneɪ.ɡɹoʊs], [ˌmɑn.tɪˈni.ɡɹoʊs}} * {{audio2|en-.ogg|прослухати вимову}} === Значення === # {{Форма-ім|мова=en|позначка=|число=мн|база=Montenegro}}.
575000
wikitext
text/x-wiki
= {{-en-}} =
=== Морфосинтаксичні ознаки ===
<b>{{склади|Mon|te|ne|gros}}</b>
=== Вимова ===
* {{transcription-en-us|mɒn.tɪˈnɛɡ.ɹəʊz], [mɒn.tɪˈneɪ.ɡɹəʊz|ˌmɑn.təˈneɪ.ɡɹoʊs], [ˌmɑn.tɪˈni.ɡɹoʊs}}
* {{audio2|en-.ogg|прослухати вимову}}
=== Значення ===
# {{Форма-ім|мова=en|позначка=|число=мн|база=Montenegro}}.
dzcstzc2g7f3jtdgxkt85smnope5bj9
асексуал
0
90860
575001
2026-10-28T14:07:57Z
Ігнат Лостюк
22957
Створена сторінка: = {{-uk-}} = === Морфосинтаксичні ознаки === {{імен uk 1a m a|склади={{склади|а|сек|суа́л}}|асексуа́л}} {{морфо-uk|а-|сексуал}} === Вимова === * {{transcriptions-uk|асексуа́л|асексуа́ли}} * {{audio2|LL-Q8798 (ukr)-Tohaomg-{{subst:PAGENAME}}.wav|Прослухати вимову}} * {{транскрипція|ас{{еи}}ксуа́л}} === Семантичні...
575001
wikitext
text/x-wiki
= {{-uk-}} =
=== Морфосинтаксичні ознаки ===
{{імен uk 1a m a|склади={{склади|а|сек|суа́л}}|асексуа́л}}
{{морфо-uk|а-|сексуал}}
=== Вимова ===
* {{transcriptions-uk|асексуа́л|асексуа́ли}}
* {{audio2|LL-Q8798 (ukr)-Tohaomg-асексуал.wav|Прослухати вимову}}
* {{транскрипція|ас{{еи}}ксуа́л}}
=== Семантичні властивості ===
==== Значення ====
# Людина, яка не відчуває або відчуває слабкий статевий [[потяг]] до інших людей {{семантика|синоніми=|антоніми=|гіпероніми=ЛГБТ|гіпоніми=}} {{приклад2|||джерело=}}
{{списки семантичних зв'язків}}
=== Усталені та термінологічні словосполучення, фразеологізми ===
*
=== Споріднені слова ===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=асексуалка, асексуальність
|прикметники=асексуальний
|дієприкметники=
|дієслова=
|прислівники=
|повн=
}}
=== Етимологія ===
Від {{етимологія:|так}}
=== Переклад ===
{{переклад|
|ab=
|abq=
|ady=
|af=
|agh=
|ain.kana=
|ain.lat=
|ain=
|aja=
|akz=
|ale=
|als=
|alt=
|an=
|ang=
|ar={{t|ar|لاجنسي|m|tr=lājinsiyy}}
|arc.jud=
|arc.syr=
|art=
|asm=
|ast=
|av=
|ave=
|ay=
|az=
|ba=
|bar=
|bat-smg=
|be=
|bg=
|bm=
|bn=
|br=
|bs=
|bua=
|ca=
|ce=
|chm=
|chu.cyr=
|chu.glag=
|cjs=
|co=
|crh=
|cs=
|csb=
|cv=
|cy=
|da=
|dar=
|de=
|dsb=
|dv=
|el=
|en={{t|en|asexual}}
|eo=
|es={{t|es|asexual|m}}
|et=
|eu=
|eve=
|evn=
|ewe=
|fa=
|fi=
|fo=
|fr=
|frp=
|fur=
|fy=
|ga=
|gag=
|gd=
|gl=
|gld=
|gn=
|grc=
|gu=
|gv=
|ha=
|haw=
|he=
|hi=
|hr=
|hsb=
|ht=
|hu=
|hy=
|ia=
|id=
|ik=
|inh=
|io=
|is=
|it=
|itl=
|iu=
|ja=
|jbo=
|jv=
|ka=
|kaa=
|kbd=
|kim=
|kjh=
|kk=
|kl=
|km=
|kn=
|ko=
|koi=
|kok=
|kom=
|krc=
|krl=
|ku=
|kum=
|kw=
|ky=
|la=
|lad=
|lb=
|lbe=
|lez=
|li=
|lmo=
|ln=
|lo=
|lt=
|lv=
|mas=
|mdf=
|mg=
|mi=
|mk=
|ml=
|mn=
|mo=
|mr=
|ms=
|mt=
|myv=
|na=
|nah=
|nap=
|nds=
|nio=
|nl=
|nn=
|no=
|nov=
|nv=
|oc=
|oj=
|os=
|pa=
|pap=
|pl={{t|pl|aseksualista|m}}, {{t|pl|aseksualistka|f}}
|pms=
|prs=
|ps=
|pt=
|qu=
|rap=
|rm=
|ro=
|roh=
|rom=
|ru={{t|ru|асексуалист|m}}, {{t|ru|асексуал|m}}
|rue=
|rw=
|sa=
|sah=
|sc=
|scn=
|sco=
|sd=
|se=
|sjd=
|sk=
|sl=
|slovio-c=
|slovio-l=
|sm=
|so=
|sq=
|sr=
|sr-c=
|sr-l=
|su=
|sv=
|sw=
|szl=
|ta=
|tab=
|te=
|tet=
|tg=
|th=
|tk=
|tl=
|tn=
|tpi=
|tr=
|tt.cyr=
|tt.lat=
|tt=
|ttt=
|ty=
|tyv=
|udm=
|ug=
|ur=
|uz=
|vep=
|vi=
|vo=
|vot=
|vro=
|wa=
|wo=
|xal=
|xh=
|xsr=
|yi=
|yo=
|yrk=
|ze=
|zh=
|zh-cn=
|zh-tw=
|zu=
|zza=
}}
=== Джерела ===
<references />
* <!-- {{СУМ20-слов}} -->
* <!-- {{СУМ-11}} -->
{{довжина слова|8|lang=uk}}
<!-- {{Категорія|Слова, відсутні у ВТССУМ|}} -->
{{Категорія|мова=uk|ЛГБТ|||||}}
pjxdblw0ppbaldomczqd34ebdqvy4wh
575002
575001
2026-10-28T14:10:47Z
Ігнат Лостюк
22957
575002
wikitext
text/x-wiki
= {{-uk-}} =
=== Морфосинтаксичні ознаки ===
{{імен uk 1a m a|склади={{склади|а|сек|суа́л}}|асексуа́л}}
{{морфо-uk|а-|сексуал}}
=== Вимова ===
* {{transcriptions-uk|асексуа́л|асексуа́ли}}
* {{audio2|LL-Q8798 (ukr)-Tohaomg-асексуал.wav|Прослухати вимову}}
* {{транскрипція|ас{{еи}}ксуа́л}}
=== Семантичні властивості ===
==== Значення ====
# Людина, яка не відчуває або відчуває слабкий статевий [[потяг]] до інших людей {{семантика|синоніми=|антоніми=|гіпероніми=ЛГБТ|гіпоніми=}} {{приклад2|||джерело=}}
{{списки семантичних зв'язків}}
=== Усталені та термінологічні словосполучення, фразеологізми ===
*
=== Споріднені слова ===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=асексуалка, асексуальність
|прикметники=асексуальний
|дієприкметники=
|дієслова=
|прислівники=
|повн=
}}
=== Етимологія ===
Від {{етимологія:|так}}
=== Переклад ===
{{переклад|
|ab=
|abq=
|ady=
|af=
|agh=
|ain.kana=
|ain.lat=
|ain=
|aja=
|akz=
|ale=
|als=
|alt=
|an=
|ang=
|ar={{t|ar|لاجنسي|m|tr=lājinsiyy}}
|arc.jud=
|arc.syr=
|art=
|asm=
|ast=
|av=
|ave=
|ay=
|az=
|ba=
|bar=
|bat-smg=
|be=
|bg=
|bm=
|bn=
|br=
|bs=
|bua=
|ca=
|ce=
|chm=
|chu.cyr=
|chu.glag=
|cjs=
|co=
|crh=
|cs=
|csb=
|cv=
|cy=
|da=
|dar=
|de=
|dsb=
|dv=
|el=
|en={{t|en|asexual}}
|eo=
|es={{t|es|asexual|m}}
|et=
|eu=
|eve=
|evn=
|ewe=
|fa=
|fi=
|fo=
|fr=
|frp=
|fur=
|fy=
|ga=
|gag=
|gd=
|gl=
|gld=
|gn=
|grc=
|gu=
|gv=
|ha=
|haw=
|he=
|hi=
|hr=
|hsb=
|ht=
|hu=
|hy=
|ia=
|id=
|ik=
|inh=
|io=
|is=
|it=
|itl=
|iu=
|ja=
|jbo=
|jv=
|ka=
|kaa=
|kbd=
|kim=
|kjh=
|kk=
|kl=
|km=
|kn=
|ko=
|koi=
|kok=
|kom=
|krc=
|krl=
|ku=
|kum=
|kw=
|ky=
|la=
|lad=
|lb=
|lbe=
|lez=
|li=
|lmo=
|ln=
|lo=
|lt=
|lv=
|mas=
|mdf=
|mg=
|mi=
|mk=
|ml=
|mn=
|mo=
|mr=
|ms=
|mt=
|myv=
|na=
|nah=
|nap=
|nds=
|nio=
|nl=
|nn=
|no=
|nov=
|nv=
|oc=
|oj=
|os=
|pa=
|pap=
|pl={{t|pl|aseksualista|m}}, {{t|pl|aseksualistka|f}}
|pms=
|prs=
|ps=
|pt=
|qu=
|rap=
|rm=
|ro=
|roh=
|rom=
|ru={{t|ru|асексуалист|m}}, {{t|ru|асексуал|m}}
|rue=
|rw=
|sa=
|sah=
|sc=
|scn=
|sco=
|sd=
|se=
|sjd=
|sk=
|sl=
|slovio-c=
|slovio-l=
|sm=
|so=
|sq=
|sr=
|sr-c=
|sr-l=
|su=
|sv=
|sw=
|szl=
|ta=
|tab=
|te=
|tet=
|tg=
|th=
|tk=
|tl=
|tn=
|tpi=
|tr=
|tt.cyr=
|tt.lat=
|tt=
|ttt=
|ty=
|tyv=
|udm=
|ug=
|ur=
|uz=
|vep=
|vi=
|vo=
|vot=
|vro=
|wa=
|wo=
|xal=
|xh=
|xsr=
|yi=
|yo=
|yrk=
|ze=
|zh=
|zh-cn=
|zh-tw=
|zu=
|zza=
}}
=== Джерела ===
<references />
* <!-- {{СУМ20-слов}} -->
* <!-- {{СУМ-11}} -->
{{довжина слова|8|lang=uk}}
<!-- {{Категорія|Слова, відсутні у ВТССУМ|}} -->
{{Категорія|мова=uk|ЛГБТ|||||}}
{{=ru=|{{PAGENAME}}}}
=== Морфосинтаксичні ознаки ===
{{імен ru 1a m a|склади={{склади|а|сек|суа́л}}|асексуа́л}}
{{морфо-ru|а-|сексуал}}
=== Вимова ===
* {{transcriptions-ru|асексуа́л|асексуа́лы}}
* {{audio2|Ru-асексуал.ogg|прослухати вимову}}
=== Семантичні властивості ===
==== Значення ====
# {{as uk}} {{семантика|синоніми=|антоніми=|гіпероніми=ЛГБТ|гіпоніми=}} {{приклад2|текст=|переклад=}}
{{списки семантичних зв’язків}}
==== Холоніми ====
#
==== Мероніми ====
#
===Усталені та термінологічні словосполучення, фразеологізми ===
*
==== Колокації ====
====Прислів'я та приказки ====
===Споріднені слова===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=асексуалка, асексуальность
|прикметники=асексуальный
|дієслова=
|прислівники=
}}
=== Етимологія ===
Від {{етимологія|ru}}
=== Джерела ===
{{довжина слова|8|lang=ru}}
{{Категорія|мова=ru|ЛГБТ}}
e59xl3mkp8qs94ybhobl50edthsmqei
Шаблон:імен nl m
10
90861
575004
2026-10-28T21:25:37Z
Ігнат Лостюк
22957
Створена сторінка: <b>{{{склади|}}}</b> Іменник, чоловічий рід.<includeonly>[[Категорія:Нідерландські іменники]][[Категорія:Чоловічий рід/nl]]</includeonly><noinclude>[[Категорія:Шаблони словозміни/nl]]</noinclude>
575004
wikitext
text/x-wiki
<b>{{{склади|}}}</b>
Іменник, чоловічий рід.<includeonly>[[Категорія:Нідерландські іменники]][[Категорія:Чоловічий рід/nl]]</includeonly><noinclude>[[Категорія:Шаблони словозміни/nl]]</noinclude>
cu5by6vg7qu5z3df2yjzktc1xkdnxzb
Benelux
0
90862
575005
2026-10-28T21:29:08Z
Ігнат Лостюк
22957
Створена сторінка: {{=en=}} === Морфосинтаксичні ознаки === {{імен en |склади={{склади|Be|ne|lux}}||}} {{топонім|en}} {{морфо-en}} === Вимова === * {{transcription|ˈbɛnəlʌks}} * {{audio2|LL-Q1860 (eng)-Vealhurl-Benelux.wav|прослухати вимову}} === Семантичні властивості === ==== Значення ==== # {{політ.|en}} [[Бенілюкс]] (економічний союз) {...
575005
wikitext
text/x-wiki
{{=en=}}
=== Морфосинтаксичні ознаки ===
{{імен en |склади={{склади|Be|ne|lux}}||}}
{{топонім|en}}
{{морфо-en}}
=== Вимова ===
* {{transcription|ˈbɛnəlʌks}}
* {{audio2|LL-Q1860 (eng)-Vealhurl-Benelux.wav|прослухати вимову}}
=== Семантичні властивості ===
==== Значення ====
# {{політ.|en}} [[Бенілюкс]] (економічний союз) {{семантика|синоніми=|антоніми=|гіпероніми=|гіпоніми=}} {{приклад|текст=|переклад=|джерело=}}
# {{геогр.|en}} [[Бенілюкс]] (регіон Європи) {{семантика|синоніми=Low Countries|антоніми=|гіпероніми=Europe|гіпоніми=}} {{приклад|текст=|переклад=|джерело=}}
{{списки семантичних зв’язків}}
===Усталені та термінологічні словосполучення, фразеологізми ===
*
==== Колокації ====
====Прислів'я та приказки ====
===Споріднені слова===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=Beneluxer, Beneluxian
|прикметники=Beneluxian
|дієприкметники=
|дієслова=
|прислівники=
}}
=== Етимологія ===
Від {{lang|nl|Benelux|Бенілюкс}}, [[België|'''Be'''lgië]] + [[Nederland|'''Ne'''derland]] + [[Luxemburg|'''Lux'''emburg]].
=== Джерела ===
{{довжина слова|7|lang=en}}
{{Категорія|мова=en|Європа|||||}}
{{=nl=}}
===Морфосинтаксичні ознаки===
{{імен nl m|склади={{склади|Be|ne|lux}}|}}
{{морфо|}}
===Вимова===
* {{transcription|ˌbeː.nəˈlyks], [ˈbeː.nəˌlyks}}
* {{audio2|Nl-Benelux.ogg|прослухати вимову}}
===Семантичні властивості===
==== Значення ====
# {{політ.|nl}} [[Бенілюкс]] (економічний союз) {{семантика|синоніми=|антоніми=|гіпероніми=|гіпоніми=}} {{приклад|текст=|переклад=|джерело=}}
# {{геогр.|nl}} [[Бенілюкс]] (регіон Європи) {{семантика|синоніми=Lage Landen|антоніми=|гіпероніми=Europa|гіпоніми=}} {{приклад|текст=|переклад=|джерело=}}
{{списки семантичних зв’язків}}
===Усталені та термінологічні словосполучення, фразеологізми ===
*
==== Колокації ====
====Прислів'я та приказки ====
===Споріднені слова===
{{спорідн|
|згруб=
|змп=
|власні назви=
|прізвища=
|топоніми=
|іменники=
|прикметники=
|дієслова=
|прислівники=
}}
===Етимологія===
Від [[нідерландська мова|нідерл.]] [[België|'''Be'''lgië]] + [[Nederland|'''Ne'''derland]] + [[Luxemburg|'''Lux'''emburg]].
=== Джерела ===
{{довжина слова|7|lang=nl}}
{{Категорія|мова=nl|Європа|||||}}
lu74nlea893p35929t4nb1ixcotav8j
Шаблон:transcriptions-sk
10
90863
575007
2026-10-29T08:43:26Z
Ігнат Лостюк
22957
Створена сторінка: {{{{{|safesubst:}}}ifsubst|<includeonly>{{transcriptions|{{{{{|safesubst:}}}#invoke:sk-pron|ipa|{{{phon|{{{1|{{{{{|safesubst:}}}PAGENAME}}}}}}}}|adj={{{adj|}}}|gem={{{gem|}}}|pal={{{pal|}}}}}|{{ subst:#if:{{{2|}}}|{{{{{|safesubst:}}}#invoke:sk-pron|ipa|{{{2}}}|adj={{{adj|}}}|gem={{{gem|}}}|pal={{{pal|}}}}}}}{{ subst:#if:{{{3|}}}|{{subst:!}}{{{3}}}}}{{ subst:#if:{{{4|}}}|{{subst:!}}{{{4}}}}}{{ subst:#if:{{{норма|}}}|{{subst:!}}норма={{{норм...
575007
wikitext
text/x-wiki
{{{{{|safesubst:}}}ifsubst|<includeonly>{{transcriptions|{{{{{|safesubst:}}}#invoke:sk-pron|ipa|{{{phon|{{{1|{{{{{|safesubst:}}}PAGENAME}}}}}}}}|adj={{{adj|}}}|gem={{{gem|}}}|pal={{{pal|}}}}}|{{
subst:#if:{{{2|}}}|{{{{{|safesubst:}}}#invoke:sk-pron|ipa|{{{2}}}|adj={{{adj|}}}|gem={{{gem|}}}|pal={{{pal|}}}}}}}{{
subst:#if:{{{3|}}}|{{subst:!}}{{{3}}}}}{{
subst:#if:{{{4|}}}|{{subst:!}}{{{4}}}}}{{
subst:#if:{{{норма|}}}|{{subst:!}}норма={{{норма}}}}}{{
subst:#if:{{{джерело|}}}|{{subst:!}}джерело={{{джерело}}}}}}}</includeonly>|<includeonly>{{transcriptions|{{#invoke:sk-pron|ipa|{{{phon|{{{1|{{PAGENAME}}}}}}}}|adj={{{adj|}}}|gem={{{gem|}}}|pal={{{pal|}}}}}|2={{#if:{{{2|}}}|{{#invoke:sk-pron|ipa|{{{2}}}|adj={{{adj|}}}|gem={{{gem|}}}|pal={{{pal|}}}}}}}|3={{{3|}}}|4={{{4|}}}|норма={{{норма|}}}|джерело={{{джерело|}}}|lang=sk}}</includeonly>}}{{#if:{{{3|}}}||{{#ifexist:Media:Sk-{{PAGENAME}}.ogg|[[Категорія:Озвучені на Вікісховищі]]}}}}<noinclude>{{documentation}}</noinclude>
o36dw00sql7ng6vc60iofsaolygg3t9
Модуль:sk-pron
828
90864
575008
2026-10-29T08:43:50Z
Ігнат Лостюк
22957
Створена сторінка: local export = {} -- ============================================================================= -- [[ DATA TABLES ]] -- ============================================================================= -- [[ 1. Phonological Sets ]] -- local diphthongs_set = { ["ia"]=true, ["ie"]=true, ["iu"]=true, ["ô"]=true } local bigraphs_set = { ["ch"]=true, ["dz"]=true, ["dž"]=true } -- Combined Vowels (Short & Long + Foreign) local vowels_set = { ["a"]=true, ["...
575008
Scribunto
text/plain
local export = {}
-- =============================================================================
-- [[ DATA TABLES ]]
-- =============================================================================
-- [[ 1. Phonological Sets ]] --
local diphthongs_set = {
["ia"]=true, ["ie"]=true, ["iu"]=true, ["ô"]=true
}
local bigraphs_set = {
["ch"]=true, ["dz"]=true, ["dž"]=true
}
-- Combined Vowels (Short & Long + Foreign)
local vowels_set = {
["a"]=true, ["e"]=true, ["i"]=true, ["o"]=true, ["u"]=true, ["y"]=true,
["ä"]=true, ["ö"]=true, ["ü"]=true,
["á"]=true, ["é"]=true, ["í"]=true, ["ó"]=true, ["ú"]=true, ["ý"]=true,
["ő"]=true, ["ű"]=true
}
-- Voicing & Sonorants
local sonorants = {
['m']=true, ['n']=true, ['ň']=true, ['l']=true, ['ľ']=true, ['ĺ']=true,
['r']=true, ['ŕ']=true, ['j']=true, ['v']=true
}
local voiced_paired = {
['b']=true, ['d']=true, ['ď']=true, ['g']=true, ['dz']=true,
['dž']=true, ['h']=true, ['z']=true, ['ž']=true, ['w']=true
}
local voiceless_paired = {
['p']=true, ['t']=true, ['ť']=true, ['k']=true, ['c']=true,
['č']=true, ['ch']=true, ['s']=true, ['š']=true, ['f']=true
}
local voicing_map = {
-- Voiced to Voiceless
['b']='p', ['d']='t', ['ď']='ť', ['g']='k', ['dz']='c', ['dž']='č',
['h']='ch', ['w']='f', ['z']='s', ['ž']='š',
-- Voiceless to Voiced
['p']='b', ['t']='d', ['ť']='ď', ['k']='g', ['c']='dz', ['č']='dž',
['ch']='h', ['f']='w', ['s']='z', ['š']='ž'
}
-- [[ 2. Assimilation & Encoding ]] --
local dental_mergers = {
['z']='dzdz', ['dz']='dzdz', ['s']='cc', ['c']='cc',
['ž']='dždž', ['dž']='dždž', ['š']='čč', ['č']='čč'
}
-- Mergers: Encodes t.s -> 15, d.z -> 26, etc.
local c1_enc = {['t']=1, ['d']=2, ['ť']=3, ['ď']=4}
local c2_enc = {
['c']=1, ['dz']=2, ['č']=3, ['dž']=4, ['s']=5, ['z']=6, ['š']=7, ['ž']=8
}
-- Decoding: Used by IPA renderer to convert "15" -> t.ts
local c1_dec = { 't', 'd', 'c', 'ɟ' }
local c2_dec = { 't͡s', 'd͡z', 't͡ʃ', 'd͡ʒ', 's', 'z', 'ʃ', 'ʒ' }
-- [[ 3. Morphological & Lexical Exceptions ]] --
local prefixes = {
['pod']=true, ['ob']=true, ['od']=true, ['ráz']=true, ['roz']=true,
['bez']=true, ['ab']=true, ['vz']=true, ['pred']=true, ['nad']=true
}
local prepositions_set = {
["bez"]=true, ["cez"]=true, ["do"]=true, ["k"]=true, ["ku"]=true,
["na"]=true, ["nad"]=true, ["o"]=true, ["od"]=true, ["po"]=true,
["pod"]=true, ["pre"]=true, ["pred"]=true, ["pri"]=true, ["s"]=true,
["so"]=true, ["u"]=true, ["v"]=true, ["vo"]=true, ["z"]=true,
["za"]=true, ["zo"]=true
}
local clitics = {
["byť"]=true, ["som"]=true, ["si"]=true, ["je"]=true, ["sme"]=true,
["ste"]=true, ["sú"]=true, ["bol"]=true, ["mi"]=true, ["ti"]=true,
["mu"]=true, ["jej"]=true, ["ma"]=true, ["ťa"]=true, ["sa"]=true,
["ho"]=true, ["ju"]=true, ["nám"]=true, ["vám"]=true, ["im"]=true,
["nás"]=true, ["vás"]=true, ["ich"]=true, ["ten"]=true, ["tá"]=true,
["to"]=true, ["on"]=true, ["a"]=true, ["i"]=true, ["aj"]=true,
["že"]=true, ["keď"]=true, ["až"]=true, ["len"]=true, ["či"]=true,
["kde"]=true, ["ba"]=true, ["ak"]=true, ["už"]=true, ["by"]=true
}
local preposition_blockers = {
['mnou']=true, ['mne']=true, ['tebou']=true, ['ňou']=true,
['ním']=true, ['nami']=true, ['vami']=true, ['nimi']=true,
['tebe']=true, ['nemu']=true, ['nej']=true, ['nám']=true,
['vám']=true, ['nim']=true
}
-- [[ 4. IPA Output Maps ]] --
local ipa_map = {
-- Vowels
['a']='a', ['á']='aː', ['ä']='æ', ['e']='e', ['é']='eː',
['i']='i', ['í']='iː', ['o']='ɔ', ['ó']='ɔː', ['ô']='u̯ɔ',
['u']='u', ['ú']='uː', ['y']='i', ['ý']='iː', ['ö']='ø',
['ő']='øː', ['ü']='y', ['ű']='yː',
-- Consonants
['b']='b', ['c']='t͡s', ['č']='t͡ʃ', ['d']='d', ['ď']='ɟ',
['g']='ɡ', ['h']='ɦ', ['j']='j', ['k']='k', ['l']='l',
['ľ']='ʎ', ['m']='m', ['n']='n', ['ň']='ɲ', ['p']='p',
['r']='r', ['ř']='r̝', ['ŕ']='r̩ː', ['ĺ']='l̩ː', ['s']='s',
['š']='ʃ', ['t']='t', ['ť']='c', ['z']='z', ['ž']='ʒ',
['x']='x',
-- Special
['v']='v', ['w']='w', ['F']='F', ['f']='f', ['ů']='ů', ["'"]='ˈ',
-- Digraphs/Multigraphs
['dz']='d͡z', ['dž']='d͡ʒ', ['ch']='x', ['dzdz']='d͡zː', ['dždž']='d͡ʒː',
['ia']='ɪ̯a', ['ie']='ɪ̯e', ['iu']='ɪ̯u'
}
local variant_map = {
["H"]={"ɣ","ɦ"}, ["W"]={"ʋ","v"}, ["U"]={"u̯","ʋ"},
["J"]={"ɪ̯","j"}, ["N"]={"n","n̠"}
}
-- Module-level state variables
local IS_MO_VARIANT = false
local NAMESPACE = ""
local PAGENAME = ""
local LOAN = 0
local HARD_SUFFIX = 0
local AS_IMP = 0
-- =============================================================================
-- [[ HELPER FUNCTIONS ]]
-- =============================================================================
function get_voiced_counterpart(char) return voicing_map[char] end
-- Tokenizer (Preserves digraphs)
function get_graphemes(word)
local units = {}
local len = mw.ustring.len(word)
local i = 1
local is_loan = (LOAN == 1)
while i <= len do
local two_char = mw.ustring.sub(word, i, i + 1)
if bigraphs_set[two_char] or (diphthongs_set[two_char] and not is_loan) then
table.insert(units, two_char)
i = i + 2
else
table.insert(units, mw.ustring.sub(word, i, i))
i = i + 1
end
end
return units
end
-- Resolve Cluster (Contact Assimilation Logic)
function resolve_cluster(c1, c2, is_boundary)
-- GUARD: v/f complex logic
if c1 == 'v' or c1 == 'f' then return nil end
-- 1. SPECIAL: <h> or <ch> logic
if c1 == 'ch' or c1 == 'h' then
if (voiced_paired[c2] or sonorants[c2]) and is_boundary then return 'H' .. c2 end
end
-- 2. Standard Assimilation
local c1_new = c1
if (voiceless_paired[c1] and (voiced_paired[c2] or (is_boundary and (sonorants[c2] or vowels_set[c2]))))
or (voiced_paired[c1] and voiceless_paired[c2]) then
c1_new = voicing_map[c1] or c1
end
-- 3. Mergers & Gemination
if c1_enc[c1_new] and dental_mergers[c2] then
if is_boundary then
local d1, d2 = c1_enc[c1_new], c2_enc[c2]
if d1 and d2 then return d1 .. d2 end
end
return dental_mergers[c2]
end
if c1_new ~= c1 then return c1_new .. c2 end
return nil
end
function apply_assimilation(tokens, i, j, is_boundary)
if IS_MO_VARIANT and i == #tokens - 3 then return end
local res = resolve_cluster(tokens[i], tokens[j], is_boundary)
if res then
local res_tokens = get_graphemes(res)
if #res_tokens == 2 then
tokens[i] = res_tokens[1]; tokens[j] = res_tokens[2]
elseif #res_tokens == 1 then
tokens[i] = res_tokens[1]; tokens[j] = ""
end
end
end
local function check_vowel(units, i, d, len)
while i > 0 and i <= len do
local t = units[i]
if vowels_set[t] or diphthongs_set[t] then return true end
if t ~= "'" and t ~= "-" and t ~= " " and t ~= "_" then return false end
i = i + d
end
return false
end
function split_into_syllables(word)
local units = get_graphemes(word)
local nuc, syl, len = {}, {}, #units
-- Helper: Check if index k is a Nucleus
local function is_nuc(k)
local u = units[k]; if not u then return false end
if diphthongs_set[u] or vowels_set[u] then return true end
if not (u == 'r' or u == 'l' or u == 'ŕ' or u == 'ĺ') then return false end
return not (check_vowel(units, k-1, -1, len) or check_vowel(units, k+1, 1, len))
end
for i = 1, len do if is_nuc(i) then table.insert(nuc, i) end end
if #nuc == 0 then return {table.concat(units)} end
local start = 1
for k = 1, #nuc - 1 do
local curr, next_n = nuc[k], nuc[k+1]
local split = curr -- Default: Split after vowel (V-CV)
local c_cnt, first_c = 0, nil
for j = curr + 1, next_n - 1 do
local c = units[j]
if c == "-" or c == " " then split = j; c_cnt = -1; break -- 1. Hard Boundary
elseif c == "'" then split = j - 1; c_cnt = -1; break -- 2. Stress Boundary
elseif c ~= "." then c_cnt = c_cnt + 1; first_c = first_c or j end
end
if c_cnt >= 2 then split = first_c end -- 3. Cluster Rule (VC-CV)
table.insert(syl, table.concat(units, "", start, split))
start = split + 1
end
table.insert(syl, table.concat(units, "", start))
return syl
end
-- [[ Core IPA Transformation Logic ]] --
local function ipa_transform_worker(word)
-- 1. Preprocess: Strip punctuation but map boundaries
local bound, idx = {}, 0
word = mw.ustring.gsub(word, ".", function(c)
if c == "-" or c == "." then bound[idx] = true; return "" end
idx = idx + 1
end) -- Returns cleaned word, populates 'bound'
local sub = mw.ustring.sub
local w_len, res, i = idx, {}, 1
local function is_C(c)
return not c or c == "" or (not vowels_set[c] and not diphthongs_set[c])
end
while i <= w_len do
local c1 = sub(word, i, i)
if c1 == "+" then
i = i + 1
else
local nxt = sub(word, i+1, i+1)
local c2_raw = (nxt == "+") and sub(word, i+2, i+2) or nxt
local c2, c4 = c1 .. c2_raw, sub(word, i, i+3)
local jump = (nxt == "+") and 1 or 0
local sym, step, raw = nil, 1, c1
if c4 == "dzdz" or c4 == "dždž" then
sym, step, raw = ipa_map[c4], 4, c4
elseif (c1 == c2_raw or c2 == "čš") and ipa_map[c1] and not vowels_set[c1] then
sym, step, raw = ipa_map[c1] .. "ː", 2 + jump, c1
elseif (ipa_map[c2] and not diphthongs_set[c2])
or (diphthongs_set[c2] and (LOAN~=1 or jump>0) and not bound[i]) then
sym, step, raw = ipa_map[c2], 2 + jump, c2 -- treat as diphthong only if native or explicitly marked with +
else
sym = ipa_map[c1] or c1
end
if sym then
if (raw=='r' or raw=='l') and is_C(i>1 and sub(word,i-1,i-1)) and is_C(sub(word,i+step,i+step)) then
sym = sym .. '̩' -- mark liquid as syllabic if sandwiched between non-vowels
end
table.insert(res, sym)
end
i = i + step
end
end
local out = table.concat(res)
-- Post-processing for loanword endings (-ovan-)
if LOAN == 1 then
local sfx = mw.ustring.match(word, ".*ovan(.+)$")
if sfx then
local m = ipa_map
local fix = {
["ie"]=m["ie"], ["ia"]=m["ia"], ["iu"]=m["iu"],
["í"]=m["í"], ["ím"]=m["í"]..(m["m"] or 'm'),
["iam"]=m["ia"]..(m["m"] or 'm'), ["iach"]=m["ia"]..(m["x"] or 'x'),
["iami"]=m["ia"]..(m["m"] or 'm')..(m["i"] or 'i')
}
if fix[sfx] then
local bad = (m["n"] or 'n') .. sfx:gsub(".", function(c) return m[c] or c end)
out = mw.ustring.gsub(out, bad:gsub("[%p]", "%%%0") .. "$", (m['ň'] or 'ɲ') .. fix[sfx]) -- correct [n] to [ň] before soft vowels in loan suffixes
end
end
end
return out
end
-- [[ 2. STAGE 2: PHONETIC RESOLVER ]] --
function resolve_phonetic_base(base_text)
local res = {}
local len = mw.ustring.len(base_text)
local i = 1
-- Map for standard phonetic realization
local map = { ['v']='ʋ', ['w']='v', ['F']='v', ['f']='f', ['ů']='u̯', ['I']='ɪ̯' }
local sub = mw.ustring.sub -- Local ref
while i <= len do
local c = sub(base_text, i, i)
local symbol = map[c] or c
local next_c = (i < len) and sub(base_text, i+1, i+1) or ""
local next_resolved = map[next_c] or next_c
-- Apply FULL Nasal Assimilation
if c == 'm' then
if next_resolved == 'f' or next_resolved == 'v' or next_resolved == 'ʋ' then symbol = 'ɱ' end
elseif c == 'n' then
if next_resolved == 'k' or next_resolved == 'ɡ' then symbol = 'ŋ'
elseif next_resolved == 's' or next_resolved == 'z' or next_resolved == 'ʃ' or next_resolved == 'ʒ' then symbol = 'N'
elseif next_resolved == 'x' then symbol = 'ɰ̃'
elseif next_resolved == 'p' or next_resolved == 'b' then symbol = 'm' end
end
table.insert(res, symbol)
i = i + 1
end
return table.concat(res)
end
-- [[ 3. FINAL GENERATION ]] --
local function expand_variants(txt)
local find, sub, gsub = mw.ustring.find, mw.ustring.sub, mw.ustring.gsub
-- 1. Digits: Binary Branch (All-Split vs All-Merged)
if find(txt, "%d[ˈ%s_]*%d") then
local res = {}
for m = 1, 2 do
local s = gsub(txt, "(%d)([ˈ%s_]*)(%d)", function(d1, sep, d2)
d1, d2 = tonumber(d1), tonumber(d2)
if m == 1 then -- Split (Unassimilated)
local f = ((d1+d2)%2==1) and (d1 + (d2%2==0 and 1 or -1)) or d1
return (c1_dec[f] or "") .. sep .. (c2_dec[d2] or "")
else -- Merged (Assimilated)
local c = c2_dec[d2>4 and d2-4 or d2] or ""
-- If stressed: Unreleased + Short (t̚ ˈt͡s). If not: Long (t͡sː)
return find(sep, "ˈ") and (sub(c,1,1).."̚ ˈ"..c) or (c.."ː")
end
end)
for _, v in ipairs(expand_variants(s)) do table.insert(res, v) end
end
return res
end
-- 2. Letters: Recursive Expansion (HWUJN)
local s, e, k = find(txt, "([HWUJN])")
if not s then return {txt} end -- Base case
local res, h, t = {}, sub(txt, 1, s-1), sub(txt, e+1)
for _, r in ipairs(variant_map[k]) do
for _, sub in ipairs(expand_variants(h..r..t)) do table.insert(res, sub) end
end
return res
end
local function format_ipa_output(res, frame)
local distinct = table.concat(res.high) ~= table.concat(res.low)
local args = { "sk", "/" .. res.phonemic .. "/" }
local function add(list, tag)
for i, ipa in ipairs(list) do
local qs = {}
if distinct and tag and i == 1 then table.insert(qs, tag) end
if mw.ustring.find(ipa, "ɦ ?ˈ?ɦ") then table.insert(qs, "rare") end
local q = #qs > 0 and "<q:" .. table.concat(qs, ", ") .. ">" or ""
table.insert(args, "[" .. ipa .. "]" .. q)
end
end
add(res.high, "high register")
if distinct then add(res.low, "common") end
return frame:expandTemplate{ title = "IPA", args = args }
end
local function get_ipa_nuclei(ipa)
local s = mw.ustring.gsub(ipa, "[/ %[%]ˈ%.]", "")
local nuclei, skip, sub = {}, 0, mw.ustring.sub
mw.ustring.gsub(s, "()([rluɪaæeiɔuøy])", function(p, c)
if p < skip then return end
local nuc, len = nil, 1
local nxt = sub(s, p+1, p+1)
if (c=="r" or c=="l") and nxt=="̩" then
len = (sub(s, p+2, p+2)=="ː") and 3 or 2
nuc = sub(s, p, p+len-1)
elseif (c=="u" or c=="ɪ") and nxt=="̯" then
len = 3; nuc = sub(s, p, p+2)
elseif mw.ustring.find(c, "[aæeiɔuøy]") then
if sub(s, p+1, p+2) == "u̯" then len = 3; nuc = sub(s, p, p+2)
else len = (nxt=="ː") and 2 or 1; nuc = sub(s, p, p+len-1) end
end
if nuc then table.insert(nuclei, {ipa=nuc, s=p, e=p+len-1}); skip = p+len end
end)
return nuclei, s
end
local function format_rhymes(frame, nuclei, s)
local c = #nuclei
if c == 0 then return "" end
local rhyme = mw.ustring.sub(s, (c > 1 and nuclei[c-1] or nuclei[1]).s)
return "\n*" .. frame:expandTemplate{title="rhymes", args={"sk", rhyme, s=c}}
end
local function format_hyphenation(word, ipa, frame, respelling, nuclei)
if #nuclei <= 1 then return "" end
local sub, find, lower, len = mw.ustring.sub, mw.ustring.find, mw.ustring.lower, mw.ustring.len
local w_lower = lower(word)
-- 1. Parse Respelling (Boundaries & Forbidden Nuclei)
local forced, forbidden, idx, last = {}, {}, 0, ""
if respelling then
local clean_check = mw.ustring.gsub(lower(respelling), "[%.%-%+]", "")
if clean_check == w_lower then
for i = 1, len(respelling) do
local c = sub(respelling, i, i)
if c == "+" then
if last ~= "i" and last ~= "í" then forbidden[idx + 1] = true end
elseif c:match("[%.%-]") then
if idx > 0 then forced[idx] = true end
else
idx = idx + 1; last = lower(c)
end
end
end
end
local map = {
['a']='a', ['aː']='á', ['æ']='ä', ['e']='e', ['eː']='é',
['i']='[iy]', ['iː']='[íý]', ['o']='o', ['oː']='ó', ['ɔ']='o', ['ɔː']='ó',
['u']='u', ['uː']='ú', ['u̯ɔ']='ô', ['ɔu̯']='ou',
['ø']='ö', ['øː']='ő', ['y']='ü', ['yː']='ű',
['ɪ̯a']='ia', ['ɪ̯e']='ie', ['ɪ̯u']='iu',
['r̩']='r', ['r̩ː']='ŕ', ['l̩']='l', ['l̩ː']='ĺ'
}
-- Helper: Find next valid nucleus (skips forbidden off-glides)
local function get_nuc(pat, start)
while true do
local s, e = find(w_lower, pat, start)
if not s or not forbidden[s] then return s, e end
start = s + 1
end
end
local args, pos = {"sk"}, 1
local seg_start = 1
for i = 1, #nuclei do
local s, e = get_nuc(map[nuclei[i].ipa] or ".", pos)
if not s then return "" end
if i < #nuclei then
local s2, _ = get_nuc(map[nuclei[i+1].ipa] or ".", e + 1)
if not s2 then return "" end
-- Default: Split before last consonant (V-CV) or between consonants (VC-CV)
local clust = sub(w_lower, e+1, s2-1)
-- If cluster has space, DO NOT split.
if find(clust, " ") then
pos = e + 1
else
local c_len = len(clust)
local off = 0
if c_len >= 2 then
local dig = sub(clust, 1, 2)
off = (dig == "ch" or dig == "dz" or dig == "dž") and (c_len > 2 and 2 or 0) or 1
end
for k = e, s2 - 1 do
if forced[k] then off = k - e; break end
end
table.insert(args, sub(word, seg_start, e + off))
seg_start = e + off + 1
pos = e + off + 1
end
else
table.insert(args, sub(word, seg_start))
end
end
return "\n*" .. frame:expandTemplate{title="hyph", args=args}
end
-- =============================================================================
-- [[ PROCESSING STEPS ]]
-- =============================================================================
function step_foreign_graphemes(word)
word = mw.ustring.lower(word)
word = mw.ustring.gsub(word, "ů", "ú")
word = mw.ustring.gsub(word, "x", "ks")
word = mw.ustring.gsub(word, "w", "v")
word = mw.ustring.gsub(word, "qu", "kv")
word = mw.ustring.gsub(word, "[,']", "")
return word
end
local function is_empty_or_space(w, k)
local x = mw.ustring.sub(w, k, k)
return x == "" or x == " "
end
function step_softening(w)
if LOAN == 1 then return w end
local soft = {['d']='ď',['t']='ť',['n']='ň',['l']='ľ'}
local sub = mw.ustring.sub
local res, len = {}, mw.ustring.len(w)
for i = 1, len do
local c = sub(w, i, i)
local s = false
if soft[c] then
local n1 = sub(w, i+1, i+1)
local n2 = sub(w, i+1, i+2)
if n2 == "ia" or n2 == "ie" or n2 == "iu" then
s = true
elseif n1 == "i" or n1 == "í" or n1 == "e" then
s = true
if HARD_SUFFIX == 1 then
local n3 = sub(w, i+1, i+3)
if (n1 == "i" or n1 == "í" or n1 == "e") and is_empty_or_space(w, i+2) then s = false
elseif n2 == "ej" and is_empty_or_space(w, i+3) then s = false
elseif (n3 == "eho" or n3 == "emu") and is_empty_or_space(w, i+4) then s = false
end
end
end
end
table.insert(res, s and soft[c] or c)
end
return table.concat(res)
end
function step_prosody(word)
-- 1. Merge Prepositions
word = mw.ustring.gsub(word, "%f[%a](%a+) +", function(w)
return prepositions_set[mw.ustring.lower(w)] and (w .. "_") or nil
end)
-- 2. Add Stress
return mw.ustring.gsub(word, "%S+", function(w)
local raw = mw.ustring.lower(mw.ustring.gsub(w, "_", ""))
-- Exclusions: Clitics OR Starts with hyphen OR Single letter OR No syllabic nucleus
if clitics[raw]
or mw.ustring.sub(w, 1, 1) == "-"
or mw.ustring.sub(w, -1) == "-"
or mw.ustring.len(w) == 1
or not mw.ustring.find(raw, "[aáäeéiíoóuúyýöőüűôrŕlĺ]") then
return w
end
return "'" .. w
end)
end
function step_simplification(word)
local patterns = { "s[tť]s[tk]", "[pd][tz]sk", "[tďdz]st", "[ztd]šť", "[zdt]sk", "ptč" }
local function resolve(w)
local c1 = mw.ustring.sub(w, 1, 1)
if c1 == "s" then return c1 .. mw.ustring.sub(w, 4)
elseif c1 == "p" then return c1 .. mw.ustring.sub(w, 3)
else
w = mw.ustring.gsub(w, "z", "")
if mw.ustring.len(w) <= 2 then return w end
local c2 = mw.ustring.sub(w, 2, 2)
local head = (c2 == "s") and "c" or "č"
return head .. mw.ustring.sub(w, 3)
end
end
for _, pat in ipairs(patterns) do
word = mw.ustring.gsub(word, pat, resolve)
end
return word
end
function step_assimilation(word)
-- [[ 1. Pre-processing & Morphological Exceptions ]] --
word = mw.ustring.gsub(word, "('[nv]ášmu)(%f[%A])", function(match)
return mw.ustring.gsub(match, "š", "ž")
end)
-- 2. sme -> zme (Only if surrounded by boundaries)
if mw.ustring.find(word, "sme") then
word = mw.ustring.gsub(mw.ustring.gsub(word, "^sme$", "zme"), "^sme(%s)", "zme%1")
word = mw.ustring.gsub(mw.ustring.gsub(word, "(%s)sme$", "%1zme"), "(%s)sme(%s)", "%1zme%2")
end
-- A. Prefix "eks-" (eks- + vowel/voiced -> egz-)
local v_set = "[aeiouyáéíóúýäôglmnňrjvzhdbžg]"
word = mw.ustring.gsub(word, "^('?)eks("..v_set..")", "%1egz%2")
word = mw.ustring.gsub(word, "([%s_])('?)eks("..v_set..")", "%1%2egz%3")
word = mw.ustring.gsub(word, "^('?)dis("..v_set..")", "%1diz%2")
word = mw.ustring.gsub(word, "([%s_])('?)dis("..v_set..")", "%1%diz%3")
-- B. Preposition Vocalization (so/ku -> zo/gu)
word = mw.ustring.gsub(word, "([%s'])([sk][ou])(_)([^%s%._]+)", function(before, prep, sep, next_w)
if not preposition_blockers[next_w] then
local voiced = (prep == "so") and "zo" or "gu"
return before .. voiced .. sep .. next_w
end
end)
-- C. Imperative Suffix "-me" (Voice preceding consonant)
if AS_IMP == 1 then
word = mw.ustring.gsub(word, "([ptsťkfcčš])me", function(c)
return (voicing_map[c] or c) .. "me"
end)
end
-- [[ 2. Main Assimilation Logic ]] --
local tokens = get_graphemes(word)
local len = #tokens
local str = table.concat(tokens)
-- Check for "-mo" variant trigger
if mw.ustring.find(word, "[ptsčškcfx]%.mo%f[%A]") or mw.ustring.find(word, "ch%.mo%f[%A]") then
IS_MO_VARIANT = true
end
-- Helpers
local function prev_C(k)
while k >= 1 do
local t = tokens[k];
if t ~= "'" and t ~= "_" and t ~= " " and t ~= "-" and t ~= "." then return k end
k = k - 1
end
end
local function is_bound(k)
return k < 1 or k > len or tokens[k] == " " or tokens[k] == "_" or tokens[k] == "-"
end
local function prev_bound(k)
local l, idx = 0, k - 1
while idx - l >= 1 do
local t = tokens[idx - l]
if t == "'" or t == "_" or t == " " or t == "-" then break end
l = l + 1
end
return l
end
local function match_prefix(start_idx, end_idx)
local diff = end_idx - start_idx + 1
if diff == 2 then return prefixes[tokens[start_idx] .. tokens[start_idx+1]]
elseif diff == 3 then return prefixes[tokens[start_idx] .. tokens[start_idx+1] .. tokens[start_idx+2]]
elseif diff == 4 then return prefixes[tokens[start_idx] .. tokens[start_idx+1] .. tokens[start_idx+2] .. tokens[start_idx+3]]
end
return false
end
local simple = not mw.ustring.find(str, "[ %-_]")
local w_offset = (simple and tokens[1] == "'") and 1 or 0
-- p_len = Exact count of characters before 'i'
local p_len = simple and (len - w_offset - 1) or prev_bound(len)
-- Reverse Loop (Regressive Assimilation)
for i = len, 2, -1 do
-- Reset state if we crossed a word boundary
if not simple and is_bound(i+1) then p_len = prev_bound(i) end
local is_prefix = false
-- A. Prefix Check
if p_len >= 2 and p_len <= 4 then
if match_prefix(i - p_len, i - 1) then
apply_assimilation(tokens, i - 1, i, true)
is_prefix = true
end
end
-- B. General Assimilation (If not prefix)
if not is_prefix then
local tgt = prev_C(i-1)
if tgt then
local is_opt = false
for k = tgt + 1, i - 1 do
if tokens[k] == " " or tokens[k] == "-" or tokens[k] == "_" then is_opt = true; break end
end
-- Handles both Devoicing (standard) and Voicing (at boundaries)
apply_assimilation(tokens, tgt, i, is_opt)
end
end
p_len = p_len - 1
end
return table.concat(tokens)
end
local function is_nuc(c)
return vowels_set[c] or diphthongs_set[c] or c == 'r' or c == 'l' or c == 'ŕ' or c == 'ĺ'
end
function step_v_f_j_realization(word)
word = mw.ustring.gsub(mw.ustring.gsub(word, "anje[lľ]", "aňɪ̯el"), "_", "")
word = mw.ustring.gsub(mw.ustring.gsub(word, "%+u", "ů"), "%+i", "I")
local syllables = split_into_syllables(word)
local res_syls = {}
local clean_chars = {}
local char_map = {}
local idx = 1
for s_i, syl in ipairs(syllables) do
char_map[s_i] = {}
for c_i = 1, mw.ustring.len(syl) do
local c = mw.ustring.sub(syl, c_i, c_i)
if c ~= "'" and c ~= "." and c ~= "-" then
clean_chars[idx] = c
char_map[s_i][c_i] = idx
idx = idx + 1
end
end
end
for s_i, syl in ipairs(syllables) do
local s_len, res = mw.ustring.len(syl), {}
local nuc_i = nil
for k = 1, s_len do
if is_nuc(mw.ustring.sub(syl, k, k)) then nuc_i = k; break end
end
for c_i = 1, s_len do
local c = mw.ustring.sub(syl, c_i, c_i)
local out = c
if c ~= "'" and c ~= "." and c ~= "-" then
local flat_idx = char_map[s_i][c_i]
local prev = clean_chars[flat_idx - 1]
local nxt = clean_chars[flat_idx + 1]
local after_nxt = clean_chars[flat_idx + 2]
-- 2. DETECT POSITIONS (Ignore ' and .)
local is_onset = (mw.ustring.gsub(mw.ustring.sub(syl, 1, c_i-1), "['%.%-]", "") == "")
local is_coda = nuc_i and (c_i > nuc_i)
if c == 'u' then
if prev == 'o' and (not nxt or nxt == ' ') then out = "ů" end
elseif c == 'v' then
if is_coda and prev and is_nuc(prev) then
local is_u_prev = (prev == 'u' or prev == 'ú')
local is_amb_next = (nxt and (nxt == 'n' or nxt == 'ň' or nxt == 'l' or nxt == 'ľ' or nxt == 'r'))
out = (is_u_prev or is_amb_next) and "U" or "ů"
else
local prev_nuc = false
if s_i > 1 then prev_nuc = prev and is_nuc(prev) or false end
local next_is_vn = (nxt and (nxt == 'n' or nxt == 'ň'))
if is_onset and (s_i == 1 or prev_nuc) and next_is_vn then
out = "W"
else
local next_is_z = (nxt == 'z')
local after_z = next_is_z and after_nxt
local vz_unv = (next_is_z and after_z and voiceless_paired[after_z])
if vz_unv or (nxt and voiceless_paired[nxt]) then out = "f"
elseif nxt and voiced_paired[nxt] and not sonorants[nxt] then out = "w"
else out = "v" end
end
end
elseif c == 'f' then
if is_coda and nxt and (voiced_paired[nxt] or sonorants[nxt]) then out = "F" end
elseif c == 'j' then
local prev_is_iy = (prev == 'i' or prev == 'í' or prev == 'y' or prev == 'ý')
if is_onset then out = "j"
elseif not vowels_set[prev] or prev_is_iy then out = "J"
else out = "I" end
end
end
table.insert(res, out)
end
table.insert(res_syls, table.concat(res))
end
return table.concat(res_syls)
end
function step_final_devoicing(word)
local tokens = get_graphemes(word)
local i = #tokens
while i > 0 do
local c = tokens[i]
if voiced_paired[c] then tokens[i] = voicing_map[c]; i = i - 1
elseif voiceless_paired[c] then i = i - 1
else break end
end
return table.concat(tokens)
end
function generate_all_transcriptions(word, original_word)
local gsub = mw.ustring.gsub
-- A. Base IPA
local base_ipa = ipa_transform_worker(word)
-- B. Phonemic
local phonemic = gsub(gsub(base_ipa, "ˈ", ""), "[vwFůWU]", "v")
phonemic = gsub(gsub(phonemic, "J", "j"), "H", "ɦ")
phonemic = gsub(gsub(phonemic, "ä", "æ"), "ľ", "ʎ")
phonemic = gsub(phonemic, "I", "j")
-- Decode Dental Clusters
phonemic = gsub(phonemic, "(%d['%s_]*%d)", function(k)
local d1, sep, d2 = mw.ustring.match(k, "(%d)(['%s_]*)(%d)")
d1, d2 = tonumber(d1), tonumber(d2)
local d1_fix = ((d1+d2)%2==1) and (d1 + (d2%2==0 and 1 or -1)) or d1
return (c1_dec[d1_fix] or "") .. sep .. (c2_dec[d2] or "")
end)
-- C. Phonetic Base
local phonetic_base = resolve_phonetic_base(base_ipa)
local roots = { phonetic_base }
-- D. Handle "-mo" Variant
if IS_MO_VARIANT and original_word then
local c = mw.ustring.sub(original_word, -4, -4) -- grab the consonant immediately preceding the ".mo" suffix
local c_voiced = voicing_map[c]
if c_voiced then
local ipa_voiceless = (ipa_map[c] or c)
local ipa_voiced = (ipa_map[c_voiced] or c_voiced)
local pattern = mw.ustring.gsub(ipa_voiceless, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") .. "mɔ$" -- escape IPA chars for regex and target the end of string
local new_root, count = mw.ustring.gsub(phonetic_base, pattern, ipa_voiced .. "mɔ")
if count > 0 then
table.insert(roots, new_root)
end
end
end
-- E. Expand Variants
local bases_expanded = {}
for _, root in ipairs(roots) do
local expansions = expand_variants(root)
for _, e in ipairs(expansions) do table.insert(bases_expanded, e) end
end
-- F. Generate Registers
local high_res, low_res = {}, {}
for _, ipa in ipairs(bases_expanded) do
table.insert(high_res, (gsub(ipa, "æ", "ɛɐ̯")))
local l = gsub(gsub(gsub(ipa, "æ", "e"), "ʎ", "l"), "l ?ˈ?l", "lː")
table.insert(low_res, (gsub(l, "n([fvʋ])", "ɱ%1")))
end
return {
phonemic = phonemic,
high = high_res,
low = low_res
}
end
-- =============================================================================
-- [[ MAIN EXPORT ]]
-- =============================================================================
function export.show(frame)
local args = frame:getParent().args
local title = mw.title.getCurrentTitle()
local t = args["t"] or ""
NAMESPACE = title.nsText
PAGENAME = (NAMESPACE == "") and title.text or (args["pagename"] or "Pagename not specified")
LOAN = mw.ustring.find(t, "loan") and 1 or 0
HARD_SUFFIX = mw.ustring.find(t, "adj") and 1 or 0
AS_IMP = mw.ustring.find(t, "imp") and 1 or 0
IS_MO_VARIANT = false
local word = (args["1"] ~= nil) and args["1"] or PAGENAME
local original_word = word
local manual_hard = (args["1"] ~= nil and mw.ustring.find(args["1"], "[DTNL]"))
-- Display Switches
local show_rhymes = (args["r"] ~= "0")
and not mw.ustring.find(word, "^%-")
and not mw.ustring.find(word, "%-$")
local show_hyph = (args["h"] ~= "0")
local audio = args["a"] or false
local audio_accent = args["aa"] or ""
-- Pipeline
if not manual_hard then word = step_foreign_graphemes(word) end
word = step_softening(word)
if manual_hard then word = step_foreign_graphemes(word) end
word = step_prosody(word)
word = step_simplification(word)
word = step_assimilation(word)
word = step_v_f_j_realization(word)
word = step_final_devoicing(word)
local final = generate_all_transcriptions(word, original_word)
local nuclei, clean_ipa = get_ipa_nuclei(final.phonemic)
local out = format_ipa_output(final, frame)
if audio then out = out .. "\n*" .. frame:expandTemplate{title="audio", args={"sk", audio, a=audio_accent}} end
if show_rhymes then out = out .. format_rhymes(frame, nuclei, clean_ipa) end
if show_hyph then out = out .. format_hyphenation(PAGENAME, final.phonemic, frame, original_word, nuclei) end
return out
end
return export
tmz5mzaq9nr0y7rz6oiwhctv35yogps
575009
575008
2026-10-29T08:46:15Z
Ігнат Лостюк
22957
575009
Scribunto
text/plain
local export = {}
local m_IPA = require("Модуль:IPA")
local sk = require("Модуль:languages-data").getByCode("sk")
-- =============================================================================
-- [[ DATA TABLES ]]
-- =============================================================================
-- [[ 1. Phonological Sets ]] --
local diphthongs_set = {
["ia"]=true, ["ie"]=true, ["iu"]=true, ["ô"]=true
}
local bigraphs_set = {
["ch"]=true, ["dz"]=true, ["dž"]=true
}
-- Combined Vowels (Short & Long + Foreign)
local vowels_set = {
["a"]=true, ["e"]=true, ["i"]=true, ["o"]=true, ["u"]=true, ["y"]=true,
["ä"]=true, ["ö"]=true, ["ü"]=true,
["á"]=true, ["é"]=true, ["í"]=true, ["ó"]=true, ["ú"]=true, ["ý"]=true,
["ő"]=true, ["ű"]=true
}
-- Voicing & Sonorants
local sonorants = {
['m']=true, ['n']=true, ['ň']=true, ['l']=true, ['ľ']=true, ['ĺ']=true,
['r']=true, ['ŕ']=true, ['j']=true, ['v']=true
}
local voiced_paired = {
['b']=true, ['d']=true, ['ď']=true, ['g']=true, ['dz']=true,
['dž']=true, ['h']=true, ['z']=true, ['ž']=true, ['w']=true
}
local voiceless_paired = {
['p']=true, ['t']=true, ['ť']=true, ['k']=true, ['c']=true,
['č']=true, ['ch']=true, ['s']=true, ['š']=true, ['f']=true
}
local voicing_map = {
-- Voiced to Voiceless
['b']='p', ['d']='t', ['ď']='ť', ['g']='k', ['dz']='c', ['dž']='č',
['h']='ch', ['w']='f', ['z']='s', ['ž']='š',
-- Voiceless to Voiced
['p']='b', ['t']='d', ['ť']='ď', ['k']='g', ['c']='dz', ['č']='dž',
['ch']='h', ['f']='w', ['s']='z', ['š']='ž'
}
-- [[ 2. Assimilation & Encoding ]] --
local dental_mergers = {
['z']='dzdz', ['dz']='dzdz', ['s']='cc', ['c']='cc',
['ž']='dždž', ['dž']='dždž', ['š']='čč', ['č']='čč'
}
-- Mergers: Encodes t.s -> 15, d.z -> 26, etc.
local c1_enc = {['t']=1, ['d']=2, ['ť']=3, ['ď']=4}
local c2_enc = {
['c']=1, ['dz']=2, ['č']=3, ['dž']=4, ['s']=5, ['z']=6, ['š']=7, ['ž']=8
}
-- Decoding: Used by IPA renderer to convert "15" -> t.ts
local c1_dec = { 't', 'd', 'c', 'ɟ' }
local c2_dec = { 't͡s', 'd͡z', 't͡ʃ', 'd͡ʒ', 's', 'z', 'ʃ', 'ʒ' }
-- [[ 3. Morphological & Lexical Exceptions ]] --
local prefixes = {
['pod']=true, ['ob']=true, ['od']=true, ['ráz']=true, ['roz']=true,
['bez']=true, ['ab']=true, ['vz']=true, ['pred']=true, ['nad']=true
}
local prepositions_set = {
["bez"]=true, ["cez"]=true, ["do"]=true, ["k"]=true, ["ku"]=true,
["na"]=true, ["nad"]=true, ["o"]=true, ["od"]=true, ["po"]=true,
["pod"]=true, ["pre"]=true, ["pred"]=true, ["pri"]=true, ["s"]=true,
["so"]=true, ["u"]=true, ["v"]=true, ["vo"]=true, ["z"]=true,
["za"]=true, ["zo"]=true
}
local clitics = {
["byť"]=true, ["som"]=true, ["si"]=true, ["je"]=true, ["sme"]=true,
["ste"]=true, ["sú"]=true, ["bol"]=true, ["mi"]=true, ["ti"]=true,
["mu"]=true, ["jej"]=true, ["ma"]=true, ["ťa"]=true, ["sa"]=true,
["ho"]=true, ["ju"]=true, ["nám"]=true, ["vám"]=true, ["im"]=true,
["nás"]=true, ["vás"]=true, ["ich"]=true, ["ten"]=true, ["tá"]=true,
["to"]=true, ["on"]=true, ["a"]=true, ["i"]=true, ["aj"]=true,
["že"]=true, ["keď"]=true, ["až"]=true, ["len"]=true, ["či"]=true,
["kde"]=true, ["ba"]=true, ["ak"]=true, ["už"]=true, ["by"]=true
}
local preposition_blockers = {
['mnou']=true, ['mne']=true, ['tebou']=true, ['ňou']=true,
['ním']=true, ['nami']=true, ['vami']=true, ['nimi']=true,
['tebe']=true, ['nemu']=true, ['nej']=true, ['nám']=true,
['vám']=true, ['nim']=true
}
-- [[ 4. IPA Output Maps ]] --
local ipa_map = {
-- Vowels
['a']='a', ['á']='aː', ['ä']='æ', ['e']='e', ['é']='eː',
['i']='i', ['í']='iː', ['o']='ɔ', ['ó']='ɔː', ['ô']='u̯ɔ',
['u']='u', ['ú']='uː', ['y']='i', ['ý']='iː', ['ö']='ø',
['ő']='øː', ['ü']='y', ['ű']='yː',
-- Consonants
['b']='b', ['c']='t͡s', ['č']='t͡ʃ', ['d']='d', ['ď']='ɟ',
['g']='ɡ', ['h']='ɦ', ['j']='j', ['k']='k', ['l']='l',
['ľ']='ʎ', ['m']='m', ['n']='n', ['ň']='ɲ', ['p']='p',
['r']='r', ['ř']='r̝', ['ŕ']='r̩ː', ['ĺ']='l̩ː', ['s']='s',
['š']='ʃ', ['t']='t', ['ť']='c', ['z']='z', ['ž']='ʒ',
['x']='x',
-- Special
['v']='v', ['w']='w', ['F']='F', ['f']='f', ['ů']='ů', ["'"]='ˈ',
-- Digraphs/Multigraphs
['dz']='d͡z', ['dž']='d͡ʒ', ['ch']='x', ['dzdz']='d͡zː', ['dždž']='d͡ʒː',
['ia']='ɪ̯a', ['ie']='ɪ̯e', ['iu']='ɪ̯u'
}
local variant_map = {
["H"]={"ɣ","ɦ"}, ["W"]={"ʋ","v"}, ["U"]={"u̯","ʋ"},
["J"]={"ɪ̯","j"}, ["N"]={"n","n̠"}
}
-- Module-level state variables
local IS_MO_VARIANT = false
local NAMESPACE = ""
local PAGENAME = ""
local LOAN = 0
local HARD_SUFFIX = 0
local AS_IMP = 0
-- =============================================================================
-- [[ HELPER FUNCTIONS ]]
-- =============================================================================
function get_voiced_counterpart(char) return voicing_map[char] end
-- Tokenizer (Preserves digraphs)
function get_graphemes(word)
local units = {}
local len = mw.ustring.len(word)
local i = 1
local is_loan = (LOAN == 1)
while i <= len do
local two_char = mw.ustring.sub(word, i, i + 1)
if bigraphs_set[two_char] or (diphthongs_set[two_char] and not is_loan) then
table.insert(units, two_char)
i = i + 2
else
table.insert(units, mw.ustring.sub(word, i, i))
i = i + 1
end
end
return units
end
-- Resolve Cluster (Contact Assimilation Logic)
function resolve_cluster(c1, c2, is_boundary)
-- GUARD: v/f complex logic
if c1 == 'v' or c1 == 'f' then return nil end
-- 1. SPECIAL: <h> or <ch> logic
if c1 == 'ch' or c1 == 'h' then
if (voiced_paired[c2] or sonorants[c2]) and is_boundary then return 'H' .. c2 end
end
-- 2. Standard Assimilation
local c1_new = c1
if (voiceless_paired[c1] and (voiced_paired[c2] or (is_boundary and (sonorants[c2] or vowels_set[c2]))))
or (voiced_paired[c1] and voiceless_paired[c2]) then
c1_new = voicing_map[c1] or c1
end
-- 3. Mergers & Gemination
if c1_enc[c1_new] and dental_mergers[c2] then
if is_boundary then
local d1, d2 = c1_enc[c1_new], c2_enc[c2]
if d1 and d2 then return d1 .. d2 end
end
return dental_mergers[c2]
end
if c1_new ~= c1 then return c1_new .. c2 end
return nil
end
function apply_assimilation(tokens, i, j, is_boundary)
if IS_MO_VARIANT and i == #tokens - 3 then return end
local res = resolve_cluster(tokens[i], tokens[j], is_boundary)
if res then
local res_tokens = get_graphemes(res)
if #res_tokens == 2 then
tokens[i] = res_tokens[1]; tokens[j] = res_tokens[2]
elseif #res_tokens == 1 then
tokens[i] = res_tokens[1]; tokens[j] = ""
end
end
end
local function check_vowel(units, i, d, len)
while i > 0 and i <= len do
local t = units[i]
if vowels_set[t] or diphthongs_set[t] then return true end
if t ~= "'" and t ~= "-" and t ~= " " and t ~= "_" then return false end
i = i + d
end
return false
end
function split_into_syllables(word)
local units = get_graphemes(word)
local nuc, syl, len = {}, {}, #units
-- Helper: Check if index k is a Nucleus
local function is_nuc(k)
local u = units[k]; if not u then return false end
if diphthongs_set[u] or vowels_set[u] then return true end
if not (u == 'r' or u == 'l' or u == 'ŕ' or u == 'ĺ') then return false end
return not (check_vowel(units, k-1, -1, len) or check_vowel(units, k+1, 1, len))
end
for i = 1, len do if is_nuc(i) then table.insert(nuc, i) end end
if #nuc == 0 then return {table.concat(units)} end
local start = 1
for k = 1, #nuc - 1 do
local curr, next_n = nuc[k], nuc[k+1]
local split = curr -- Default: Split after vowel (V-CV)
local c_cnt, first_c = 0, nil
for j = curr + 1, next_n - 1 do
local c = units[j]
if c == "-" or c == " " then split = j; c_cnt = -1; break -- 1. Hard Boundary
elseif c == "'" then split = j - 1; c_cnt = -1; break -- 2. Stress Boundary
elseif c ~= "." then c_cnt = c_cnt + 1; first_c = first_c or j end
end
if c_cnt >= 2 then split = first_c end -- 3. Cluster Rule (VC-CV)
table.insert(syl, table.concat(units, "", start, split))
start = split + 1
end
table.insert(syl, table.concat(units, "", start))
return syl
end
-- [[ Core IPA Transformation Logic ]] --
local function ipa_transform_worker(word)
-- 1. Preprocess: Strip punctuation but map boundaries
local bound, idx = {}, 0
word = mw.ustring.gsub(word, ".", function(c)
if c == "-" or c == "." then bound[idx] = true; return "" end
idx = idx + 1
end) -- Returns cleaned word, populates 'bound'
local sub = mw.ustring.sub
local w_len, res, i = idx, {}, 1
local function is_C(c)
return not c or c == "" or (not vowels_set[c] and not diphthongs_set[c])
end
while i <= w_len do
local c1 = sub(word, i, i)
if c1 == "+" then
i = i + 1
else
local nxt = sub(word, i+1, i+1)
local c2_raw = (nxt == "+") and sub(word, i+2, i+2) or nxt
local c2, c4 = c1 .. c2_raw, sub(word, i, i+3)
local jump = (nxt == "+") and 1 or 0
local sym, step, raw = nil, 1, c1
if c4 == "dzdz" or c4 == "dždž" then
sym, step, raw = ipa_map[c4], 4, c4
elseif (c1 == c2_raw or c2 == "čš") and ipa_map[c1] and not vowels_set[c1] then
sym, step, raw = ipa_map[c1] .. "ː", 2 + jump, c1
elseif (ipa_map[c2] and not diphthongs_set[c2])
or (diphthongs_set[c2] and (LOAN~=1 or jump>0) and not bound[i]) then
sym, step, raw = ipa_map[c2], 2 + jump, c2 -- treat as diphthong only if native or explicitly marked with +
else
sym = ipa_map[c1] or c1
end
if sym then
if (raw=='r' or raw=='l') and is_C(i>1 and sub(word,i-1,i-1)) and is_C(sub(word,i+step,i+step)) then
sym = sym .. '̩' -- mark liquid as syllabic if sandwiched between non-vowels
end
table.insert(res, sym)
end
i = i + step
end
end
local out = table.concat(res)
-- Post-processing for loanword endings (-ovan-)
if LOAN == 1 then
local sfx = mw.ustring.match(word, ".*ovan(.+)$")
if sfx then
local m = ipa_map
local fix = {
["ie"]=m["ie"], ["ia"]=m["ia"], ["iu"]=m["iu"],
["í"]=m["í"], ["ím"]=m["í"]..(m["m"] or 'm'),
["iam"]=m["ia"]..(m["m"] or 'm'), ["iach"]=m["ia"]..(m["x"] or 'x'),
["iami"]=m["ia"]..(m["m"] or 'm')..(m["i"] or 'i')
}
if fix[sfx] then
local bad = (m["n"] or 'n') .. sfx:gsub(".", function(c) return m[c] or c end)
out = mw.ustring.gsub(out, bad:gsub("[%p]", "%%%0") .. "$", (m['ň'] or 'ɲ') .. fix[sfx]) -- correct [n] to [ň] before soft vowels in loan suffixes
end
end
end
return out
end
-- [[ 2. STAGE 2: PHONETIC RESOLVER ]] --
function resolve_phonetic_base(base_text)
local res = {}
local len = mw.ustring.len(base_text)
local i = 1
-- Map for standard phonetic realization
local map = { ['v']='ʋ', ['w']='v', ['F']='v', ['f']='f', ['ů']='u̯', ['I']='ɪ̯' }
local sub = mw.ustring.sub -- Local ref
while i <= len do
local c = sub(base_text, i, i)
local symbol = map[c] or c
local next_c = (i < len) and sub(base_text, i+1, i+1) or ""
local next_resolved = map[next_c] or next_c
-- Apply FULL Nasal Assimilation
if c == 'm' then
if next_resolved == 'f' or next_resolved == 'v' or next_resolved == 'ʋ' then symbol = 'ɱ' end
elseif c == 'n' then
if next_resolved == 'k' or next_resolved == 'ɡ' then symbol = 'ŋ'
elseif next_resolved == 's' or next_resolved == 'z' or next_resolved == 'ʃ' or next_resolved == 'ʒ' then symbol = 'N'
elseif next_resolved == 'x' then symbol = 'ɰ̃'
elseif next_resolved == 'p' or next_resolved == 'b' then symbol = 'm' end
end
table.insert(res, symbol)
i = i + 1
end
return table.concat(res)
end
-- [[ 3. FINAL GENERATION ]] --
local function expand_variants(txt)
local find, sub, gsub = mw.ustring.find, mw.ustring.sub, mw.ustring.gsub
-- 1. Digits: Binary Branch (All-Split vs All-Merged)
if find(txt, "%d[ˈ%s_]*%d") then
local res = {}
for m = 1, 2 do
local s = gsub(txt, "(%d)([ˈ%s_]*)(%d)", function(d1, sep, d2)
d1, d2 = tonumber(d1), tonumber(d2)
if m == 1 then -- Split (Unassimilated)
local f = ((d1+d2)%2==1) and (d1 + (d2%2==0 and 1 or -1)) or d1
return (c1_dec[f] or "") .. sep .. (c2_dec[d2] or "")
else -- Merged (Assimilated)
local c = c2_dec[d2>4 and d2-4 or d2] or ""
-- If stressed: Unreleased + Short (t̚ ˈt͡s). If not: Long (t͡sː)
return find(sep, "ˈ") and (sub(c,1,1).."̚ ˈ"..c) or (c.."ː")
end
end)
for _, v in ipairs(expand_variants(s)) do table.insert(res, v) end
end
return res
end
-- 2. Letters: Recursive Expansion (HWUJN)
local s, e, k = find(txt, "([HWUJN])")
if not s then return {txt} end -- Base case
local res, h, t = {}, sub(txt, 1, s-1), sub(txt, e+1)
for _, r in ipairs(variant_map[k]) do
for _, sub in ipairs(expand_variants(h..r..t)) do table.insert(res, sub) end
end
return res
end
local function format_ipa_output(res, frame)
local distinct = table.concat(res.high) ~= table.concat(res.low)
local args = { "sk", "/" .. res.phonemic .. "/" }
local function add(list, tag)
for i, ipa in ipairs(list) do
local qs = {}
if distinct and tag and i == 1 then table.insert(qs, tag) end
if mw.ustring.find(ipa, "ɦ ?ˈ?ɦ") then table.insert(qs, "rare") end
local q = #qs > 0 and "<q:" .. table.concat(qs, ", ") .. ">" or ""
table.insert(args, "[" .. ipa .. "]" .. q)
end
end
add(res.high, "high register")
if distinct then add(res.low, "common") end
return frame:expandTemplate{ title = "IPA", args = args }
end
local function get_ipa_nuclei(ipa)
local s = mw.ustring.gsub(ipa, "[/ %[%]ˈ%.]", "")
local nuclei, skip, sub = {}, 0, mw.ustring.sub
mw.ustring.gsub(s, "()([rluɪaæeiɔuøy])", function(p, c)
if p < skip then return end
local nuc, len = nil, 1
local nxt = sub(s, p+1, p+1)
if (c=="r" or c=="l") and nxt=="̩" then
len = (sub(s, p+2, p+2)=="ː") and 3 or 2
nuc = sub(s, p, p+len-1)
elseif (c=="u" or c=="ɪ") and nxt=="̯" then
len = 3; nuc = sub(s, p, p+2)
elseif mw.ustring.find(c, "[aæeiɔuøy]") then
if sub(s, p+1, p+2) == "u̯" then len = 3; nuc = sub(s, p, p+2)
else len = (nxt=="ː") and 2 or 1; nuc = sub(s, p, p+len-1) end
end
if nuc then table.insert(nuclei, {ipa=nuc, s=p, e=p+len-1}); skip = p+len end
end)
return nuclei, s
end
local function format_rhymes(frame, nuclei, s)
local c = #nuclei
if c == 0 then return "" end
local rhyme = mw.ustring.sub(s, (c > 1 and nuclei[c-1] or nuclei[1]).s)
return "\n*" .. frame:expandTemplate{title="rhymes", args={"sk", rhyme, s=c}}
end
local function format_hyphenation(word, ipa, frame, respelling, nuclei)
if #nuclei <= 1 then return "" end
local sub, find, lower, len = mw.ustring.sub, mw.ustring.find, mw.ustring.lower, mw.ustring.len
local w_lower = lower(word)
-- 1. Parse Respelling (Boundaries & Forbidden Nuclei)
local forced, forbidden, idx, last = {}, {}, 0, ""
if respelling then
local clean_check = mw.ustring.gsub(lower(respelling), "[%.%-%+]", "")
if clean_check == w_lower then
for i = 1, len(respelling) do
local c = sub(respelling, i, i)
if c == "+" then
if last ~= "i" and last ~= "í" then forbidden[idx + 1] = true end
elseif c:match("[%.%-]") then
if idx > 0 then forced[idx] = true end
else
idx = idx + 1; last = lower(c)
end
end
end
end
local map = {
['a']='a', ['aː']='á', ['æ']='ä', ['e']='e', ['eː']='é',
['i']='[iy]', ['iː']='[íý]', ['o']='o', ['oː']='ó', ['ɔ']='o', ['ɔː']='ó',
['u']='u', ['uː']='ú', ['u̯ɔ']='ô', ['ɔu̯']='ou',
['ø']='ö', ['øː']='ő', ['y']='ü', ['yː']='ű',
['ɪ̯a']='ia', ['ɪ̯e']='ie', ['ɪ̯u']='iu',
['r̩']='r', ['r̩ː']='ŕ', ['l̩']='l', ['l̩ː']='ĺ'
}
-- Helper: Find next valid nucleus (skips forbidden off-glides)
local function get_nuc(pat, start)
while true do
local s, e = find(w_lower, pat, start)
if not s or not forbidden[s] then return s, e end
start = s + 1
end
end
local args, pos = {"sk"}, 1
local seg_start = 1
for i = 1, #nuclei do
local s, e = get_nuc(map[nuclei[i].ipa] or ".", pos)
if not s then return "" end
if i < #nuclei then
local s2, _ = get_nuc(map[nuclei[i+1].ipa] or ".", e + 1)
if not s2 then return "" end
-- Default: Split before last consonant (V-CV) or between consonants (VC-CV)
local clust = sub(w_lower, e+1, s2-1)
-- If cluster has space, DO NOT split.
if find(clust, " ") then
pos = e + 1
else
local c_len = len(clust)
local off = 0
if c_len >= 2 then
local dig = sub(clust, 1, 2)
off = (dig == "ch" or dig == "dz" or dig == "dž") and (c_len > 2 and 2 or 0) or 1
end
for k = e, s2 - 1 do
if forced[k] then off = k - e; break end
end
table.insert(args, sub(word, seg_start, e + off))
seg_start = e + off + 1
pos = e + off + 1
end
else
table.insert(args, sub(word, seg_start))
end
end
return "\n*" .. frame:expandTemplate{title="hyph", args=args}
end
-- =============================================================================
-- [[ PROCESSING STEPS ]]
-- =============================================================================
function step_foreign_graphemes(word)
word = mw.ustring.lower(word)
word = mw.ustring.gsub(word, "ů", "ú")
word = mw.ustring.gsub(word, "x", "ks")
word = mw.ustring.gsub(word, "w", "v")
word = mw.ustring.gsub(word, "qu", "kv")
word = mw.ustring.gsub(word, "[,']", "")
return word
end
local function is_empty_or_space(w, k)
local x = mw.ustring.sub(w, k, k)
return x == "" or x == " "
end
function step_softening(w)
if LOAN == 1 then return w end
local soft = {['d']='ď',['t']='ť',['n']='ň',['l']='ľ'}
local sub = mw.ustring.sub
local res, len = {}, mw.ustring.len(w)
for i = 1, len do
local c = sub(w, i, i)
local s = false
if soft[c] then
local n1 = sub(w, i+1, i+1)
local n2 = sub(w, i+1, i+2)
if n2 == "ia" or n2 == "ie" or n2 == "iu" then
s = true
elseif n1 == "i" or n1 == "í" or n1 == "e" then
s = true
if HARD_SUFFIX == 1 then
local n3 = sub(w, i+1, i+3)
if (n1 == "i" or n1 == "í" or n1 == "e") and is_empty_or_space(w, i+2) then s = false
elseif n2 == "ej" and is_empty_or_space(w, i+3) then s = false
elseif (n3 == "eho" or n3 == "emu") and is_empty_or_space(w, i+4) then s = false
end
end
end
end
table.insert(res, s and soft[c] or c)
end
return table.concat(res)
end
function step_prosody(word)
-- 1. Merge Prepositions
word = mw.ustring.gsub(word, "%f[%a](%a+) +", function(w)
return prepositions_set[mw.ustring.lower(w)] and (w .. "_") or nil
end)
-- 2. Add Stress
return mw.ustring.gsub(word, "%S+", function(w)
local raw = mw.ustring.lower(mw.ustring.gsub(w, "_", ""))
-- Exclusions: Clitics OR Starts with hyphen OR Single letter OR No syllabic nucleus
if clitics[raw]
or mw.ustring.sub(w, 1, 1) == "-"
or mw.ustring.sub(w, -1) == "-"
or mw.ustring.len(w) == 1
or not mw.ustring.find(raw, "[aáäeéiíoóuúyýöőüűôrŕlĺ]") then
return w
end
return "'" .. w
end)
end
function step_simplification(word)
local patterns = { "s[tť]s[tk]", "[pd][tz]sk", "[tďdz]st", "[ztd]šť", "[zdt]sk", "ptč" }
local function resolve(w)
local c1 = mw.ustring.sub(w, 1, 1)
if c1 == "s" then return c1 .. mw.ustring.sub(w, 4)
elseif c1 == "p" then return c1 .. mw.ustring.sub(w, 3)
else
w = mw.ustring.gsub(w, "z", "")
if mw.ustring.len(w) <= 2 then return w end
local c2 = mw.ustring.sub(w, 2, 2)
local head = (c2 == "s") and "c" or "č"
return head .. mw.ustring.sub(w, 3)
end
end
for _, pat in ipairs(patterns) do
word = mw.ustring.gsub(word, pat, resolve)
end
return word
end
function step_assimilation(word)
-- [[ 1. Pre-processing & Morphological Exceptions ]] --
word = mw.ustring.gsub(word, "('[nv]ášmu)(%f[%A])", function(match)
return mw.ustring.gsub(match, "š", "ž")
end)
-- 2. sme -> zme (Only if surrounded by boundaries)
if mw.ustring.find(word, "sme") then
word = mw.ustring.gsub(mw.ustring.gsub(word, "^sme$", "zme"), "^sme(%s)", "zme%1")
word = mw.ustring.gsub(mw.ustring.gsub(word, "(%s)sme$", "%1zme"), "(%s)sme(%s)", "%1zme%2")
end
-- A. Prefix "eks-" (eks- + vowel/voiced -> egz-)
local v_set = "[aeiouyáéíóúýäôglmnňrjvzhdbžg]"
word = mw.ustring.gsub(word, "^('?)eks("..v_set..")", "%1egz%2")
word = mw.ustring.gsub(word, "([%s_])('?)eks("..v_set..")", "%1%2egz%3")
word = mw.ustring.gsub(word, "^('?)dis("..v_set..")", "%1diz%2")
word = mw.ustring.gsub(word, "([%s_])('?)dis("..v_set..")", "%1%diz%3")
-- B. Preposition Vocalization (so/ku -> zo/gu)
word = mw.ustring.gsub(word, "([%s'])([sk][ou])(_)([^%s%._]+)", function(before, prep, sep, next_w)
if not preposition_blockers[next_w] then
local voiced = (prep == "so") and "zo" or "gu"
return before .. voiced .. sep .. next_w
end
end)
-- C. Imperative Suffix "-me" (Voice preceding consonant)
if AS_IMP == 1 then
word = mw.ustring.gsub(word, "([ptsťkfcčš])me", function(c)
return (voicing_map[c] or c) .. "me"
end)
end
-- [[ 2. Main Assimilation Logic ]] --
local tokens = get_graphemes(word)
local len = #tokens
local str = table.concat(tokens)
-- Check for "-mo" variant trigger
if mw.ustring.find(word, "[ptsčškcfx]%.mo%f[%A]") or mw.ustring.find(word, "ch%.mo%f[%A]") then
IS_MO_VARIANT = true
end
-- Helpers
local function prev_C(k)
while k >= 1 do
local t = tokens[k];
if t ~= "'" and t ~= "_" and t ~= " " and t ~= "-" and t ~= "." then return k end
k = k - 1
end
end
local function is_bound(k)
return k < 1 or k > len or tokens[k] == " " or tokens[k] == "_" or tokens[k] == "-"
end
local function prev_bound(k)
local l, idx = 0, k - 1
while idx - l >= 1 do
local t = tokens[idx - l]
if t == "'" or t == "_" or t == " " or t == "-" then break end
l = l + 1
end
return l
end
local function match_prefix(start_idx, end_idx)
local diff = end_idx - start_idx + 1
if diff == 2 then return prefixes[tokens[start_idx] .. tokens[start_idx+1]]
elseif diff == 3 then return prefixes[tokens[start_idx] .. tokens[start_idx+1] .. tokens[start_idx+2]]
elseif diff == 4 then return prefixes[tokens[start_idx] .. tokens[start_idx+1] .. tokens[start_idx+2] .. tokens[start_idx+3]]
end
return false
end
local simple = not mw.ustring.find(str, "[ %-_]")
local w_offset = (simple and tokens[1] == "'") and 1 or 0
-- p_len = Exact count of characters before 'i'
local p_len = simple and (len - w_offset - 1) or prev_bound(len)
-- Reverse Loop (Regressive Assimilation)
for i = len, 2, -1 do
-- Reset state if we crossed a word boundary
if not simple and is_bound(i+1) then p_len = prev_bound(i) end
local is_prefix = false
-- A. Prefix Check
if p_len >= 2 and p_len <= 4 then
if match_prefix(i - p_len, i - 1) then
apply_assimilation(tokens, i - 1, i, true)
is_prefix = true
end
end
-- B. General Assimilation (If not prefix)
if not is_prefix then
local tgt = prev_C(i-1)
if tgt then
local is_opt = false
for k = tgt + 1, i - 1 do
if tokens[k] == " " or tokens[k] == "-" or tokens[k] == "_" then is_opt = true; break end
end
-- Handles both Devoicing (standard) and Voicing (at boundaries)
apply_assimilation(tokens, tgt, i, is_opt)
end
end
p_len = p_len - 1
end
return table.concat(tokens)
end
local function is_nuc(c)
return vowels_set[c] or diphthongs_set[c] or c == 'r' or c == 'l' or c == 'ŕ' or c == 'ĺ'
end
function step_v_f_j_realization(word)
word = mw.ustring.gsub(mw.ustring.gsub(word, "anje[lľ]", "aňɪ̯el"), "_", "")
word = mw.ustring.gsub(mw.ustring.gsub(word, "%+u", "ů"), "%+i", "I")
local syllables = split_into_syllables(word)
local res_syls = {}
local clean_chars = {}
local char_map = {}
local idx = 1
for s_i, syl in ipairs(syllables) do
char_map[s_i] = {}
for c_i = 1, mw.ustring.len(syl) do
local c = mw.ustring.sub(syl, c_i, c_i)
if c ~= "'" and c ~= "." and c ~= "-" then
clean_chars[idx] = c
char_map[s_i][c_i] = idx
idx = idx + 1
end
end
end
for s_i, syl in ipairs(syllables) do
local s_len, res = mw.ustring.len(syl), {}
local nuc_i = nil
for k = 1, s_len do
if is_nuc(mw.ustring.sub(syl, k, k)) then nuc_i = k; break end
end
for c_i = 1, s_len do
local c = mw.ustring.sub(syl, c_i, c_i)
local out = c
if c ~= "'" and c ~= "." and c ~= "-" then
local flat_idx = char_map[s_i][c_i]
local prev = clean_chars[flat_idx - 1]
local nxt = clean_chars[flat_idx + 1]
local after_nxt = clean_chars[flat_idx + 2]
-- 2. DETECT POSITIONS (Ignore ' and .)
local is_onset = (mw.ustring.gsub(mw.ustring.sub(syl, 1, c_i-1), "['%.%-]", "") == "")
local is_coda = nuc_i and (c_i > nuc_i)
if c == 'u' then
if prev == 'o' and (not nxt or nxt == ' ') then out = "ů" end
elseif c == 'v' then
if is_coda and prev and is_nuc(prev) then
local is_u_prev = (prev == 'u' or prev == 'ú')
local is_amb_next = (nxt and (nxt == 'n' or nxt == 'ň' or nxt == 'l' or nxt == 'ľ' or nxt == 'r'))
out = (is_u_prev or is_amb_next) and "U" or "ů"
else
local prev_nuc = false
if s_i > 1 then prev_nuc = prev and is_nuc(prev) or false end
local next_is_vn = (nxt and (nxt == 'n' or nxt == 'ň'))
if is_onset and (s_i == 1 or prev_nuc) and next_is_vn then
out = "W"
else
local next_is_z = (nxt == 'z')
local after_z = next_is_z and after_nxt
local vz_unv = (next_is_z and after_z and voiceless_paired[after_z])
if vz_unv or (nxt and voiceless_paired[nxt]) then out = "f"
elseif nxt and voiced_paired[nxt] and not sonorants[nxt] then out = "w"
else out = "v" end
end
end
elseif c == 'f' then
if is_coda and nxt and (voiced_paired[nxt] or sonorants[nxt]) then out = "F" end
elseif c == 'j' then
local prev_is_iy = (prev == 'i' or prev == 'í' or prev == 'y' or prev == 'ý')
if is_onset then out = "j"
elseif not vowels_set[prev] or prev_is_iy then out = "J"
else out = "I" end
end
end
table.insert(res, out)
end
table.insert(res_syls, table.concat(res))
end
return table.concat(res_syls)
end
function step_final_devoicing(word)
local tokens = get_graphemes(word)
local i = #tokens
while i > 0 do
local c = tokens[i]
if voiced_paired[c] then tokens[i] = voicing_map[c]; i = i - 1
elseif voiceless_paired[c] then i = i - 1
else break end
end
return table.concat(tokens)
end
function generate_all_transcriptions(word, original_word)
local gsub = mw.ustring.gsub
-- A. Base IPA
local base_ipa = ipa_transform_worker(word)
-- B. Phonemic
local phonemic = gsub(gsub(base_ipa, "ˈ", ""), "[vwFůWU]", "v")
phonemic = gsub(gsub(phonemic, "J", "j"), "H", "ɦ")
phonemic = gsub(gsub(phonemic, "ä", "æ"), "ľ", "ʎ")
phonemic = gsub(phonemic, "I", "j")
-- Decode Dental Clusters
phonemic = gsub(phonemic, "(%d['%s_]*%d)", function(k)
local d1, sep, d2 = mw.ustring.match(k, "(%d)(['%s_]*)(%d)")
d1, d2 = tonumber(d1), tonumber(d2)
local d1_fix = ((d1+d2)%2==1) and (d1 + (d2%2==0 and 1 or -1)) or d1
return (c1_dec[d1_fix] or "") .. sep .. (c2_dec[d2] or "")
end)
-- C. Phonetic Base
local phonetic_base = resolve_phonetic_base(base_ipa)
local roots = { phonetic_base }
-- D. Handle "-mo" Variant
if IS_MO_VARIANT and original_word then
local c = mw.ustring.sub(original_word, -4, -4) -- grab the consonant immediately preceding the ".mo" suffix
local c_voiced = voicing_map[c]
if c_voiced then
local ipa_voiceless = (ipa_map[c] or c)
local ipa_voiced = (ipa_map[c_voiced] or c_voiced)
local pattern = mw.ustring.gsub(ipa_voiceless, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") .. "mɔ$" -- escape IPA chars for regex and target the end of string
local new_root, count = mw.ustring.gsub(phonetic_base, pattern, ipa_voiced .. "mɔ")
if count > 0 then
table.insert(roots, new_root)
end
end
end
-- E. Expand Variants
local bases_expanded = {}
for _, root in ipairs(roots) do
local expansions = expand_variants(root)
for _, e in ipairs(expansions) do table.insert(bases_expanded, e) end
end
-- F. Generate Registers
local high_res, low_res = {}, {}
for _, ipa in ipairs(bases_expanded) do
table.insert(high_res, (gsub(ipa, "æ", "ɛɐ̯")))
local l = gsub(gsub(gsub(ipa, "æ", "e"), "ʎ", "l"), "l ?ˈ?l", "lː")
table.insert(low_res, (gsub(l, "n([fvʋ])", "ɱ%1")))
end
return {
phonemic = phonemic,
high = high_res,
low = low_res
}
end
-- =============================================================================
-- [[ MAIN EXPORT ]]
-- =============================================================================
function export.show(frame)
local args = frame:getParent().args
local title = mw.title.getCurrentTitle()
local t = args["t"] or ""
NAMESPACE = title.nsText
PAGENAME = (NAMESPACE == "") and title.text or (args["pagename"] or "Pagename not specified")
LOAN = mw.ustring.find(t, "loan") and 1 or 0
HARD_SUFFIX = mw.ustring.find(t, "adj") and 1 or 0
AS_IMP = mw.ustring.find(t, "imp") and 1 or 0
IS_MO_VARIANT = false
local word = (args["1"] ~= nil) and args["1"] or PAGENAME
local original_word = word
local manual_hard = (args["1"] ~= nil and mw.ustring.find(args["1"], "[DTNL]"))
-- Display Switches
local show_rhymes = (args["r"] ~= "0")
and not mw.ustring.find(word, "^%-")
and not mw.ustring.find(word, "%-$")
local show_hyph = (args["h"] ~= "0")
local audio = args["a"] or false
local audio_accent = args["aa"] or ""
-- Pipeline
if not manual_hard then word = step_foreign_graphemes(word) end
word = step_softening(word)
if manual_hard then word = step_foreign_graphemes(word) end
word = step_prosody(word)
word = step_simplification(word)
word = step_assimilation(word)
word = step_v_f_j_realization(word)
word = step_final_devoicing(word)
local final = generate_all_transcriptions(word, original_word)
local nuclei, clean_ipa = get_ipa_nuclei(final.phonemic)
local out = format_ipa_output(final, frame)
if audio then out = out .. "\n*" .. frame:expandTemplate{title="audio", args={"sk", audio, a=audio_accent}} end
if show_rhymes then out = out .. format_rhymes(frame, nuclei, clean_ipa) end
if show_hyph then out = out .. format_hyphenation(PAGENAME, final.phonemic, frame, original_word, nuclei) end
return out
end
return export
8t19cs677ehivu4812tnsh89ed8woti
575010
575009
2026-10-29T08:48:37Z
Ігнат Лостюк
22957
575010
Scribunto
text/plain
local export = {}
local m_table = require("Module:TableTools")
local m_IPA = require("Модуль:IPA")
local sk = require("Модуль:languages-data").getByCode("sk")
local u = mw.ustring.char
local rfind = mw.ustring.find
local rsubn = mw.ustring.gsub
local rmatch = mw.ustring.match
local rsplit = mw.text.split
local usub = mw.ustring.sub
local ulen = mw.ustring.len
-- version of rsubn() that discards all but the first return value
local function rsub(term, foo, bar)
local retval = rsubn(term, foo, bar)
return retval
end
-- apply function repeatedly until no change
local function do_sub_repeatedly(term, fun)
while true do
local new_term = fun(term)
if new_term == term then
return term
end
term = new_term
end
end
-- apply rsub() repeatedly until no change
local function rsub_repeatedly(term, foo, bar)
while true do
local new_term = rsub(term, foo, bar)
if new_term == term then
return term
end
term = new_term
end
end
-- =============================================================================
-- [[ DATA TABLES ]]
-- =============================================================================
-- [[ 1. Phonological Sets ]] --
local diphthongs_set = {
["ia"]=true, ["ie"]=true, ["iu"]=true, ["ô"]=true
}
local bigraphs_set = {
["ch"]=true, ["dz"]=true, ["dž"]=true
}
-- Combined Vowels (Short & Long + Foreign)
local vowels_set = {
["a"]=true, ["e"]=true, ["i"]=true, ["o"]=true, ["u"]=true, ["y"]=true,
["ä"]=true, ["ö"]=true, ["ü"]=true,
["á"]=true, ["é"]=true, ["í"]=true, ["ó"]=true, ["ú"]=true, ["ý"]=true,
["ő"]=true, ["ű"]=true
}
-- Voicing & Sonorants
local sonorants = {
['m']=true, ['n']=true, ['ň']=true, ['l']=true, ['ľ']=true, ['ĺ']=true,
['r']=true, ['ŕ']=true, ['j']=true, ['v']=true
}
local voiced_paired = {
['b']=true, ['d']=true, ['ď']=true, ['g']=true, ['dz']=true,
['dž']=true, ['h']=true, ['z']=true, ['ž']=true, ['w']=true
}
local voiceless_paired = {
['p']=true, ['t']=true, ['ť']=true, ['k']=true, ['c']=true,
['č']=true, ['ch']=true, ['s']=true, ['š']=true, ['f']=true
}
local voicing_map = {
-- Voiced to Voiceless
['b']='p', ['d']='t', ['ď']='ť', ['g']='k', ['dz']='c', ['dž']='č',
['h']='ch', ['w']='f', ['z']='s', ['ž']='š',
-- Voiceless to Voiced
['p']='b', ['t']='d', ['ť']='ď', ['k']='g', ['c']='dz', ['č']='dž',
['ch']='h', ['f']='w', ['s']='z', ['š']='ž'
}
-- [[ 2. Assimilation & Encoding ]] --
local dental_mergers = {
['z']='dzdz', ['dz']='dzdz', ['s']='cc', ['c']='cc',
['ž']='dždž', ['dž']='dždž', ['š']='čč', ['č']='čč'
}
-- Mergers: Encodes t.s -> 15, d.z -> 26, etc.
local c1_enc = {['t']=1, ['d']=2, ['ť']=3, ['ď']=4}
local c2_enc = {
['c']=1, ['dz']=2, ['č']=3, ['dž']=4, ['s']=5, ['z']=6, ['š']=7, ['ž']=8
}
-- Decoding: Used by IPA renderer to convert "15" -> t.ts
local c1_dec = { 't', 'd', 'c', 'ɟ' }
local c2_dec = { 't͡s', 'd͡z', 't͡ʃ', 'd͡ʒ', 's', 'z', 'ʃ', 'ʒ' }
-- [[ 3. Morphological & Lexical Exceptions ]] --
local prefixes = {
['pod']=true, ['ob']=true, ['od']=true, ['ráz']=true, ['roz']=true,
['bez']=true, ['ab']=true, ['vz']=true, ['pred']=true, ['nad']=true
}
local prepositions_set = {
["bez"]=true, ["cez"]=true, ["do"]=true, ["k"]=true, ["ku"]=true,
["na"]=true, ["nad"]=true, ["o"]=true, ["od"]=true, ["po"]=true,
["pod"]=true, ["pre"]=true, ["pred"]=true, ["pri"]=true, ["s"]=true,
["so"]=true, ["u"]=true, ["v"]=true, ["vo"]=true, ["z"]=true,
["za"]=true, ["zo"]=true
}
local clitics = {
["byť"]=true, ["som"]=true, ["si"]=true, ["je"]=true, ["sme"]=true,
["ste"]=true, ["sú"]=true, ["bol"]=true, ["mi"]=true, ["ti"]=true,
["mu"]=true, ["jej"]=true, ["ma"]=true, ["ťa"]=true, ["sa"]=true,
["ho"]=true, ["ju"]=true, ["nám"]=true, ["vám"]=true, ["im"]=true,
["nás"]=true, ["vás"]=true, ["ich"]=true, ["ten"]=true, ["tá"]=true,
["to"]=true, ["on"]=true, ["a"]=true, ["i"]=true, ["aj"]=true,
["že"]=true, ["keď"]=true, ["až"]=true, ["len"]=true, ["či"]=true,
["kde"]=true, ["ba"]=true, ["ak"]=true, ["už"]=true, ["by"]=true
}
local preposition_blockers = {
['mnou']=true, ['mne']=true, ['tebou']=true, ['ňou']=true,
['ním']=true, ['nami']=true, ['vami']=true, ['nimi']=true,
['tebe']=true, ['nemu']=true, ['nej']=true, ['nám']=true,
['vám']=true, ['nim']=true
}
-- [[ 4. IPA Output Maps ]] --
local ipa_map = {
-- Vowels
['a']='a', ['á']='aː', ['ä']='æ', ['e']='e', ['é']='eː',
['i']='i', ['í']='iː', ['o']='ɔ', ['ó']='ɔː', ['ô']='u̯ɔ',
['u']='u', ['ú']='uː', ['y']='i', ['ý']='iː', ['ö']='ø',
['ő']='øː', ['ü']='y', ['ű']='yː',
-- Consonants
['b']='b', ['c']='t͡s', ['č']='t͡ʃ', ['d']='d', ['ď']='ɟ',
['g']='ɡ', ['h']='ɦ', ['j']='j', ['k']='k', ['l']='l',
['ľ']='ʎ', ['m']='m', ['n']='n', ['ň']='ɲ', ['p']='p',
['r']='r', ['ř']='r̝', ['ŕ']='r̩ː', ['ĺ']='l̩ː', ['s']='s',
['š']='ʃ', ['t']='t', ['ť']='c', ['z']='z', ['ž']='ʒ',
['x']='x',
-- Special
['v']='v', ['w']='w', ['F']='F', ['f']='f', ['ů']='ů', ["'"]='ˈ',
-- Digraphs/Multigraphs
['dz']='d͡z', ['dž']='d͡ʒ', ['ch']='x', ['dzdz']='d͡zː', ['dždž']='d͡ʒː',
['ia']='ɪ̯a', ['ie']='ɪ̯e', ['iu']='ɪ̯u'
}
local variant_map = {
["H"]={"ɣ","ɦ"}, ["W"]={"ʋ","v"}, ["U"]={"u̯","ʋ"},
["J"]={"ɪ̯","j"}, ["N"]={"n","n̠"}
}
-- Module-level state variables
local IS_MO_VARIANT = false
local NAMESPACE = ""
local PAGENAME = ""
local LOAN = 0
local HARD_SUFFIX = 0
local AS_IMP = 0
-- =============================================================================
-- [[ HELPER FUNCTIONS ]]
-- =============================================================================
function get_voiced_counterpart(char) return voicing_map[char] end
-- Tokenizer (Preserves digraphs)
function get_graphemes(word)
local units = {}
local len = mw.ustring.len(word)
local i = 1
local is_loan = (LOAN == 1)
while i <= len do
local two_char = mw.ustring.sub(word, i, i + 1)
if bigraphs_set[two_char] or (diphthongs_set[two_char] and not is_loan) then
table.insert(units, two_char)
i = i + 2
else
table.insert(units, mw.ustring.sub(word, i, i))
i = i + 1
end
end
return units
end
-- Resolve Cluster (Contact Assimilation Logic)
function resolve_cluster(c1, c2, is_boundary)
-- GUARD: v/f complex logic
if c1 == 'v' or c1 == 'f' then return nil end
-- 1. SPECIAL: <h> or <ch> logic
if c1 == 'ch' or c1 == 'h' then
if (voiced_paired[c2] or sonorants[c2]) and is_boundary then return 'H' .. c2 end
end
-- 2. Standard Assimilation
local c1_new = c1
if (voiceless_paired[c1] and (voiced_paired[c2] or (is_boundary and (sonorants[c2] or vowels_set[c2]))))
or (voiced_paired[c1] and voiceless_paired[c2]) then
c1_new = voicing_map[c1] or c1
end
-- 3. Mergers & Gemination
if c1_enc[c1_new] and dental_mergers[c2] then
if is_boundary then
local d1, d2 = c1_enc[c1_new], c2_enc[c2]
if d1 and d2 then return d1 .. d2 end
end
return dental_mergers[c2]
end
if c1_new ~= c1 then return c1_new .. c2 end
return nil
end
function apply_assimilation(tokens, i, j, is_boundary)
if IS_MO_VARIANT and i == #tokens - 3 then return end
local res = resolve_cluster(tokens[i], tokens[j], is_boundary)
if res then
local res_tokens = get_graphemes(res)
if #res_tokens == 2 then
tokens[i] = res_tokens[1]; tokens[j] = res_tokens[2]
elseif #res_tokens == 1 then
tokens[i] = res_tokens[1]; tokens[j] = ""
end
end
end
local function check_vowel(units, i, d, len)
while i > 0 and i <= len do
local t = units[i]
if vowels_set[t] or diphthongs_set[t] then return true end
if t ~= "'" and t ~= "-" and t ~= " " and t ~= "_" then return false end
i = i + d
end
return false
end
function split_into_syllables(word)
local units = get_graphemes(word)
local nuc, syl, len = {}, {}, #units
-- Helper: Check if index k is a Nucleus
local function is_nuc(k)
local u = units[k]; if not u then return false end
if diphthongs_set[u] or vowels_set[u] then return true end
if not (u == 'r' or u == 'l' or u == 'ŕ' or u == 'ĺ') then return false end
return not (check_vowel(units, k-1, -1, len) or check_vowel(units, k+1, 1, len))
end
for i = 1, len do if is_nuc(i) then table.insert(nuc, i) end end
if #nuc == 0 then return {table.concat(units)} end
local start = 1
for k = 1, #nuc - 1 do
local curr, next_n = nuc[k], nuc[k+1]
local split = curr -- Default: Split after vowel (V-CV)
local c_cnt, first_c = 0, nil
for j = curr + 1, next_n - 1 do
local c = units[j]
if c == "-" or c == " " then split = j; c_cnt = -1; break -- 1. Hard Boundary
elseif c == "'" then split = j - 1; c_cnt = -1; break -- 2. Stress Boundary
elseif c ~= "." then c_cnt = c_cnt + 1; first_c = first_c or j end
end
if c_cnt >= 2 then split = first_c end -- 3. Cluster Rule (VC-CV)
table.insert(syl, table.concat(units, "", start, split))
start = split + 1
end
table.insert(syl, table.concat(units, "", start))
return syl
end
-- [[ Core IPA Transformation Logic ]] --
local function ipa_transform_worker(word)
-- 1. Preprocess: Strip punctuation but map boundaries
local bound, idx = {}, 0
word = mw.ustring.gsub(word, ".", function(c)
if c == "-" or c == "." then bound[idx] = true; return "" end
idx = idx + 1
end) -- Returns cleaned word, populates 'bound'
local sub = mw.ustring.sub
local w_len, res, i = idx, {}, 1
local function is_C(c)
return not c or c == "" or (not vowels_set[c] and not diphthongs_set[c])
end
while i <= w_len do
local c1 = sub(word, i, i)
if c1 == "+" then
i = i + 1
else
local nxt = sub(word, i+1, i+1)
local c2_raw = (nxt == "+") and sub(word, i+2, i+2) or nxt
local c2, c4 = c1 .. c2_raw, sub(word, i, i+3)
local jump = (nxt == "+") and 1 or 0
local sym, step, raw = nil, 1, c1
if c4 == "dzdz" or c4 == "dždž" then
sym, step, raw = ipa_map[c4], 4, c4
elseif (c1 == c2_raw or c2 == "čš") and ipa_map[c1] and not vowels_set[c1] then
sym, step, raw = ipa_map[c1] .. "ː", 2 + jump, c1
elseif (ipa_map[c2] and not diphthongs_set[c2])
or (diphthongs_set[c2] and (LOAN~=1 or jump>0) and not bound[i]) then
sym, step, raw = ipa_map[c2], 2 + jump, c2 -- treat as diphthong only if native or explicitly marked with +
else
sym = ipa_map[c1] or c1
end
if sym then
if (raw=='r' or raw=='l') and is_C(i>1 and sub(word,i-1,i-1)) and is_C(sub(word,i+step,i+step)) then
sym = sym .. '̩' -- mark liquid as syllabic if sandwiched between non-vowels
end
table.insert(res, sym)
end
i = i + step
end
end
local out = table.concat(res)
-- Post-processing for loanword endings (-ovan-)
if LOAN == 1 then
local sfx = mw.ustring.match(word, ".*ovan(.+)$")
if sfx then
local m = ipa_map
local fix = {
["ie"]=m["ie"], ["ia"]=m["ia"], ["iu"]=m["iu"],
["í"]=m["í"], ["ím"]=m["í"]..(m["m"] or 'm'),
["iam"]=m["ia"]..(m["m"] or 'm'), ["iach"]=m["ia"]..(m["x"] or 'x'),
["iami"]=m["ia"]..(m["m"] or 'm')..(m["i"] or 'i')
}
if fix[sfx] then
local bad = (m["n"] or 'n') .. sfx:gsub(".", function(c) return m[c] or c end)
out = mw.ustring.gsub(out, bad:gsub("[%p]", "%%%0") .. "$", (m['ň'] or 'ɲ') .. fix[sfx]) -- correct [n] to [ň] before soft vowels in loan suffixes
end
end
end
return out
end
-- [[ 2. STAGE 2: PHONETIC RESOLVER ]] --
function resolve_phonetic_base(base_text)
local res = {}
local len = mw.ustring.len(base_text)
local i = 1
-- Map for standard phonetic realization
local map = { ['v']='ʋ', ['w']='v', ['F']='v', ['f']='f', ['ů']='u̯', ['I']='ɪ̯' }
local sub = mw.ustring.sub -- Local ref
while i <= len do
local c = sub(base_text, i, i)
local symbol = map[c] or c
local next_c = (i < len) and sub(base_text, i+1, i+1) or ""
local next_resolved = map[next_c] or next_c
-- Apply FULL Nasal Assimilation
if c == 'm' then
if next_resolved == 'f' or next_resolved == 'v' or next_resolved == 'ʋ' then symbol = 'ɱ' end
elseif c == 'n' then
if next_resolved == 'k' or next_resolved == 'ɡ' then symbol = 'ŋ'
elseif next_resolved == 's' or next_resolved == 'z' or next_resolved == 'ʃ' or next_resolved == 'ʒ' then symbol = 'N'
elseif next_resolved == 'x' then symbol = 'ɰ̃'
elseif next_resolved == 'p' or next_resolved == 'b' then symbol = 'm' end
end
table.insert(res, symbol)
i = i + 1
end
return table.concat(res)
end
-- [[ 3. FINAL GENERATION ]] --
local function expand_variants(txt)
local find, sub, gsub = mw.ustring.find, mw.ustring.sub, mw.ustring.gsub
-- 1. Digits: Binary Branch (All-Split vs All-Merged)
if find(txt, "%d[ˈ%s_]*%d") then
local res = {}
for m = 1, 2 do
local s = gsub(txt, "(%d)([ˈ%s_]*)(%d)", function(d1, sep, d2)
d1, d2 = tonumber(d1), tonumber(d2)
if m == 1 then -- Split (Unassimilated)
local f = ((d1+d2)%2==1) and (d1 + (d2%2==0 and 1 or -1)) or d1
return (c1_dec[f] or "") .. sep .. (c2_dec[d2] or "")
else -- Merged (Assimilated)
local c = c2_dec[d2>4 and d2-4 or d2] or ""
-- If stressed: Unreleased + Short (t̚ ˈt͡s). If not: Long (t͡sː)
return find(sep, "ˈ") and (sub(c,1,1).."̚ ˈ"..c) or (c.."ː")
end
end)
for _, v in ipairs(expand_variants(s)) do table.insert(res, v) end
end
return res
end
-- 2. Letters: Recursive Expansion (HWUJN)
local s, e, k = find(txt, "([HWUJN])")
if not s then return {txt} end -- Base case
local res, h, t = {}, sub(txt, 1, s-1), sub(txt, e+1)
for _, r in ipairs(variant_map[k]) do
for _, sub in ipairs(expand_variants(h..r..t)) do table.insert(res, sub) end
end
return res
end
local function format_ipa_output(res, frame)
local distinct = table.concat(res.high) ~= table.concat(res.low)
local args = { "sk", "/" .. res.phonemic .. "/" }
local function add(list, tag)
for i, ipa in ipairs(list) do
local qs = {}
if distinct and tag and i == 1 then table.insert(qs, tag) end
if mw.ustring.find(ipa, "ɦ ?ˈ?ɦ") then table.insert(qs, "rare") end
local q = #qs > 0 and "<q:" .. table.concat(qs, ", ") .. ">" or ""
table.insert(args, "[" .. ipa .. "]" .. q)
end
end
add(res.high, "high register")
if distinct then add(res.low, "common") end
return frame:expandTemplate{ title = "IPA", args = args }
end
local function get_ipa_nuclei(ipa)
local s = mw.ustring.gsub(ipa, "[/ %[%]ˈ%.]", "")
local nuclei, skip, sub = {}, 0, mw.ustring.sub
mw.ustring.gsub(s, "()([rluɪaæeiɔuøy])", function(p, c)
if p < skip then return end
local nuc, len = nil, 1
local nxt = sub(s, p+1, p+1)
if (c=="r" or c=="l") and nxt=="̩" then
len = (sub(s, p+2, p+2)=="ː") and 3 or 2
nuc = sub(s, p, p+len-1)
elseif (c=="u" or c=="ɪ") and nxt=="̯" then
len = 3; nuc = sub(s, p, p+2)
elseif mw.ustring.find(c, "[aæeiɔuøy]") then
if sub(s, p+1, p+2) == "u̯" then len = 3; nuc = sub(s, p, p+2)
else len = (nxt=="ː") and 2 or 1; nuc = sub(s, p, p+len-1) end
end
if nuc then table.insert(nuclei, {ipa=nuc, s=p, e=p+len-1}); skip = p+len end
end)
return nuclei, s
end
local function format_rhymes(frame, nuclei, s)
local c = #nuclei
if c == 0 then return "" end
local rhyme = mw.ustring.sub(s, (c > 1 and nuclei[c-1] or nuclei[1]).s)
return "\n*" .. frame:expandTemplate{title="rhymes", args={"sk", rhyme, s=c}}
end
local function format_hyphenation(word, ipa, frame, respelling, nuclei)
if #nuclei <= 1 then return "" end
local sub, find, lower, len = mw.ustring.sub, mw.ustring.find, mw.ustring.lower, mw.ustring.len
local w_lower = lower(word)
-- 1. Parse Respelling (Boundaries & Forbidden Nuclei)
local forced, forbidden, idx, last = {}, {}, 0, ""
if respelling then
local clean_check = mw.ustring.gsub(lower(respelling), "[%.%-%+]", "")
if clean_check == w_lower then
for i = 1, len(respelling) do
local c = sub(respelling, i, i)
if c == "+" then
if last ~= "i" and last ~= "í" then forbidden[idx + 1] = true end
elseif c:match("[%.%-]") then
if idx > 0 then forced[idx] = true end
else
idx = idx + 1; last = lower(c)
end
end
end
end
local map = {
['a']='a', ['aː']='á', ['æ']='ä', ['e']='e', ['eː']='é',
['i']='[iy]', ['iː']='[íý]', ['o']='o', ['oː']='ó', ['ɔ']='o', ['ɔː']='ó',
['u']='u', ['uː']='ú', ['u̯ɔ']='ô', ['ɔu̯']='ou',
['ø']='ö', ['øː']='ő', ['y']='ü', ['yː']='ű',
['ɪ̯a']='ia', ['ɪ̯e']='ie', ['ɪ̯u']='iu',
['r̩']='r', ['r̩ː']='ŕ', ['l̩']='l', ['l̩ː']='ĺ'
}
-- Helper: Find next valid nucleus (skips forbidden off-glides)
local function get_nuc(pat, start)
while true do
local s, e = find(w_lower, pat, start)
if not s or not forbidden[s] then return s, e end
start = s + 1
end
end
local args, pos = {"sk"}, 1
local seg_start = 1
for i = 1, #nuclei do
local s, e = get_nuc(map[nuclei[i].ipa] or ".", pos)
if not s then return "" end
if i < #nuclei then
local s2, _ = get_nuc(map[nuclei[i+1].ipa] or ".", e + 1)
if not s2 then return "" end
-- Default: Split before last consonant (V-CV) or between consonants (VC-CV)
local clust = sub(w_lower, e+1, s2-1)
-- If cluster has space, DO NOT split.
if find(clust, " ") then
pos = e + 1
else
local c_len = len(clust)
local off = 0
if c_len >= 2 then
local dig = sub(clust, 1, 2)
off = (dig == "ch" or dig == "dz" or dig == "dž") and (c_len > 2 and 2 or 0) or 1
end
for k = e, s2 - 1 do
if forced[k] then off = k - e; break end
end
table.insert(args, sub(word, seg_start, e + off))
seg_start = e + off + 1
pos = e + off + 1
end
else
table.insert(args, sub(word, seg_start))
end
end
return "\n*" .. frame:expandTemplate{title="hyph", args=args}
end
-- =============================================================================
-- [[ PROCESSING STEPS ]]
-- =============================================================================
function step_foreign_graphemes(word)
word = mw.ustring.lower(word)
word = mw.ustring.gsub(word, "ů", "ú")
word = mw.ustring.gsub(word, "x", "ks")
word = mw.ustring.gsub(word, "w", "v")
word = mw.ustring.gsub(word, "qu", "kv")
word = mw.ustring.gsub(word, "[,']", "")
return word
end
local function is_empty_or_space(w, k)
local x = mw.ustring.sub(w, k, k)
return x == "" or x == " "
end
function step_softening(w)
if LOAN == 1 then return w end
local soft = {['d']='ď',['t']='ť',['n']='ň',['l']='ľ'}
local sub = mw.ustring.sub
local res, len = {}, mw.ustring.len(w)
for i = 1, len do
local c = sub(w, i, i)
local s = false
if soft[c] then
local n1 = sub(w, i+1, i+1)
local n2 = sub(w, i+1, i+2)
if n2 == "ia" or n2 == "ie" or n2 == "iu" then
s = true
elseif n1 == "i" or n1 == "í" or n1 == "e" then
s = true
if HARD_SUFFIX == 1 then
local n3 = sub(w, i+1, i+3)
if (n1 == "i" or n1 == "í" or n1 == "e") and is_empty_or_space(w, i+2) then s = false
elseif n2 == "ej" and is_empty_or_space(w, i+3) then s = false
elseif (n3 == "eho" or n3 == "emu") and is_empty_or_space(w, i+4) then s = false
end
end
end
end
table.insert(res, s and soft[c] or c)
end
return table.concat(res)
end
function step_prosody(word)
-- 1. Merge Prepositions
word = mw.ustring.gsub(word, "%f[%a](%a+) +", function(w)
return prepositions_set[mw.ustring.lower(w)] and (w .. "_") or nil
end)
-- 2. Add Stress
return mw.ustring.gsub(word, "%S+", function(w)
local raw = mw.ustring.lower(mw.ustring.gsub(w, "_", ""))
-- Exclusions: Clitics OR Starts with hyphen OR Single letter OR No syllabic nucleus
if clitics[raw]
or mw.ustring.sub(w, 1, 1) == "-"
or mw.ustring.sub(w, -1) == "-"
or mw.ustring.len(w) == 1
or not mw.ustring.find(raw, "[aáäeéiíoóuúyýöőüűôrŕlĺ]") then
return w
end
return "'" .. w
end)
end
function step_simplification(word)
local patterns = { "s[tť]s[tk]", "[pd][tz]sk", "[tďdz]st", "[ztd]šť", "[zdt]sk", "ptč" }
local function resolve(w)
local c1 = mw.ustring.sub(w, 1, 1)
if c1 == "s" then return c1 .. mw.ustring.sub(w, 4)
elseif c1 == "p" then return c1 .. mw.ustring.sub(w, 3)
else
w = mw.ustring.gsub(w, "z", "")
if mw.ustring.len(w) <= 2 then return w end
local c2 = mw.ustring.sub(w, 2, 2)
local head = (c2 == "s") and "c" or "č"
return head .. mw.ustring.sub(w, 3)
end
end
for _, pat in ipairs(patterns) do
word = mw.ustring.gsub(word, pat, resolve)
end
return word
end
function step_assimilation(word)
-- [[ 1. Pre-processing & Morphological Exceptions ]] --
word = mw.ustring.gsub(word, "('[nv]ášmu)(%f[%A])", function(match)
return mw.ustring.gsub(match, "š", "ž")
end)
-- 2. sme -> zme (Only if surrounded by boundaries)
if mw.ustring.find(word, "sme") then
word = mw.ustring.gsub(mw.ustring.gsub(word, "^sme$", "zme"), "^sme(%s)", "zme%1")
word = mw.ustring.gsub(mw.ustring.gsub(word, "(%s)sme$", "%1zme"), "(%s)sme(%s)", "%1zme%2")
end
-- A. Prefix "eks-" (eks- + vowel/voiced -> egz-)
local v_set = "[aeiouyáéíóúýäôglmnňrjvzhdbžg]"
word = mw.ustring.gsub(word, "^('?)eks("..v_set..")", "%1egz%2")
word = mw.ustring.gsub(word, "([%s_])('?)eks("..v_set..")", "%1%2egz%3")
word = mw.ustring.gsub(word, "^('?)dis("..v_set..")", "%1diz%2")
word = mw.ustring.gsub(word, "([%s_])('?)dis("..v_set..")", "%1%diz%3")
-- B. Preposition Vocalization (so/ku -> zo/gu)
word = mw.ustring.gsub(word, "([%s'])([sk][ou])(_)([^%s%._]+)", function(before, prep, sep, next_w)
if not preposition_blockers[next_w] then
local voiced = (prep == "so") and "zo" or "gu"
return before .. voiced .. sep .. next_w
end
end)
-- C. Imperative Suffix "-me" (Voice preceding consonant)
if AS_IMP == 1 then
word = mw.ustring.gsub(word, "([ptsťkfcčš])me", function(c)
return (voicing_map[c] or c) .. "me"
end)
end
-- [[ 2. Main Assimilation Logic ]] --
local tokens = get_graphemes(word)
local len = #tokens
local str = table.concat(tokens)
-- Check for "-mo" variant trigger
if mw.ustring.find(word, "[ptsčškcfx]%.mo%f[%A]") or mw.ustring.find(word, "ch%.mo%f[%A]") then
IS_MO_VARIANT = true
end
-- Helpers
local function prev_C(k)
while k >= 1 do
local t = tokens[k];
if t ~= "'" and t ~= "_" and t ~= " " and t ~= "-" and t ~= "." then return k end
k = k - 1
end
end
local function is_bound(k)
return k < 1 or k > len or tokens[k] == " " or tokens[k] == "_" or tokens[k] == "-"
end
local function prev_bound(k)
local l, idx = 0, k - 1
while idx - l >= 1 do
local t = tokens[idx - l]
if t == "'" or t == "_" or t == " " or t == "-" then break end
l = l + 1
end
return l
end
local function match_prefix(start_idx, end_idx)
local diff = end_idx - start_idx + 1
if diff == 2 then return prefixes[tokens[start_idx] .. tokens[start_idx+1]]
elseif diff == 3 then return prefixes[tokens[start_idx] .. tokens[start_idx+1] .. tokens[start_idx+2]]
elseif diff == 4 then return prefixes[tokens[start_idx] .. tokens[start_idx+1] .. tokens[start_idx+2] .. tokens[start_idx+3]]
end
return false
end
local simple = not mw.ustring.find(str, "[ %-_]")
local w_offset = (simple and tokens[1] == "'") and 1 or 0
-- p_len = Exact count of characters before 'i'
local p_len = simple and (len - w_offset - 1) or prev_bound(len)
-- Reverse Loop (Regressive Assimilation)
for i = len, 2, -1 do
-- Reset state if we crossed a word boundary
if not simple and is_bound(i+1) then p_len = prev_bound(i) end
local is_prefix = false
-- A. Prefix Check
if p_len >= 2 and p_len <= 4 then
if match_prefix(i - p_len, i - 1) then
apply_assimilation(tokens, i - 1, i, true)
is_prefix = true
end
end
-- B. General Assimilation (If not prefix)
if not is_prefix then
local tgt = prev_C(i-1)
if tgt then
local is_opt = false
for k = tgt + 1, i - 1 do
if tokens[k] == " " or tokens[k] == "-" or tokens[k] == "_" then is_opt = true; break end
end
-- Handles both Devoicing (standard) and Voicing (at boundaries)
apply_assimilation(tokens, tgt, i, is_opt)
end
end
p_len = p_len - 1
end
return table.concat(tokens)
end
local function is_nuc(c)
return vowels_set[c] or diphthongs_set[c] or c == 'r' or c == 'l' or c == 'ŕ' or c == 'ĺ'
end
function step_v_f_j_realization(word)
word = mw.ustring.gsub(mw.ustring.gsub(word, "anje[lľ]", "aňɪ̯el"), "_", "")
word = mw.ustring.gsub(mw.ustring.gsub(word, "%+u", "ů"), "%+i", "I")
local syllables = split_into_syllables(word)
local res_syls = {}
local clean_chars = {}
local char_map = {}
local idx = 1
for s_i, syl in ipairs(syllables) do
char_map[s_i] = {}
for c_i = 1, mw.ustring.len(syl) do
local c = mw.ustring.sub(syl, c_i, c_i)
if c ~= "'" and c ~= "." and c ~= "-" then
clean_chars[idx] = c
char_map[s_i][c_i] = idx
idx = idx + 1
end
end
end
for s_i, syl in ipairs(syllables) do
local s_len, res = mw.ustring.len(syl), {}
local nuc_i = nil
for k = 1, s_len do
if is_nuc(mw.ustring.sub(syl, k, k)) then nuc_i = k; break end
end
for c_i = 1, s_len do
local c = mw.ustring.sub(syl, c_i, c_i)
local out = c
if c ~= "'" and c ~= "." and c ~= "-" then
local flat_idx = char_map[s_i][c_i]
local prev = clean_chars[flat_idx - 1]
local nxt = clean_chars[flat_idx + 1]
local after_nxt = clean_chars[flat_idx + 2]
-- 2. DETECT POSITIONS (Ignore ' and .)
local is_onset = (mw.ustring.gsub(mw.ustring.sub(syl, 1, c_i-1), "['%.%-]", "") == "")
local is_coda = nuc_i and (c_i > nuc_i)
if c == 'u' then
if prev == 'o' and (not nxt or nxt == ' ') then out = "ů" end
elseif c == 'v' then
if is_coda and prev and is_nuc(prev) then
local is_u_prev = (prev == 'u' or prev == 'ú')
local is_amb_next = (nxt and (nxt == 'n' or nxt == 'ň' or nxt == 'l' or nxt == 'ľ' or nxt == 'r'))
out = (is_u_prev or is_amb_next) and "U" or "ů"
else
local prev_nuc = false
if s_i > 1 then prev_nuc = prev and is_nuc(prev) or false end
local next_is_vn = (nxt and (nxt == 'n' or nxt == 'ň'))
if is_onset and (s_i == 1 or prev_nuc) and next_is_vn then
out = "W"
else
local next_is_z = (nxt == 'z')
local after_z = next_is_z and after_nxt
local vz_unv = (next_is_z and after_z and voiceless_paired[after_z])
if vz_unv or (nxt and voiceless_paired[nxt]) then out = "f"
elseif nxt and voiced_paired[nxt] and not sonorants[nxt] then out = "w"
else out = "v" end
end
end
elseif c == 'f' then
if is_coda and nxt and (voiced_paired[nxt] or sonorants[nxt]) then out = "F" end
elseif c == 'j' then
local prev_is_iy = (prev == 'i' or prev == 'í' or prev == 'y' or prev == 'ý')
if is_onset then out = "j"
elseif not vowels_set[prev] or prev_is_iy then out = "J"
else out = "I" end
end
end
table.insert(res, out)
end
table.insert(res_syls, table.concat(res))
end
return table.concat(res_syls)
end
function step_final_devoicing(word)
local tokens = get_graphemes(word)
local i = #tokens
while i > 0 do
local c = tokens[i]
if voiced_paired[c] then tokens[i] = voicing_map[c]; i = i - 1
elseif voiceless_paired[c] then i = i - 1
else break end
end
return table.concat(tokens)
end
function generate_all_transcriptions(word, original_word)
local gsub = mw.ustring.gsub
-- A. Base IPA
local base_ipa = ipa_transform_worker(word)
-- B. Phonemic
local phonemic = gsub(gsub(base_ipa, "ˈ", ""), "[vwFůWU]", "v")
phonemic = gsub(gsub(phonemic, "J", "j"), "H", "ɦ")
phonemic = gsub(gsub(phonemic, "ä", "æ"), "ľ", "ʎ")
phonemic = gsub(phonemic, "I", "j")
-- Decode Dental Clusters
phonemic = gsub(phonemic, "(%d['%s_]*%d)", function(k)
local d1, sep, d2 = mw.ustring.match(k, "(%d)(['%s_]*)(%d)")
d1, d2 = tonumber(d1), tonumber(d2)
local d1_fix = ((d1+d2)%2==1) and (d1 + (d2%2==0 and 1 or -1)) or d1
return (c1_dec[d1_fix] or "") .. sep .. (c2_dec[d2] or "")
end)
-- C. Phonetic Base
local phonetic_base = resolve_phonetic_base(base_ipa)
local roots = { phonetic_base }
-- D. Handle "-mo" Variant
if IS_MO_VARIANT and original_word then
local c = mw.ustring.sub(original_word, -4, -4) -- grab the consonant immediately preceding the ".mo" suffix
local c_voiced = voicing_map[c]
if c_voiced then
local ipa_voiceless = (ipa_map[c] or c)
local ipa_voiced = (ipa_map[c_voiced] or c_voiced)
local pattern = mw.ustring.gsub(ipa_voiceless, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1") .. "mɔ$" -- escape IPA chars for regex and target the end of string
local new_root, count = mw.ustring.gsub(phonetic_base, pattern, ipa_voiced .. "mɔ")
if count > 0 then
table.insert(roots, new_root)
end
end
end
-- E. Expand Variants
local bases_expanded = {}
for _, root in ipairs(roots) do
local expansions = expand_variants(root)
for _, e in ipairs(expansions) do table.insert(bases_expanded, e) end
end
-- F. Generate Registers
local high_res, low_res = {}, {}
for _, ipa in ipairs(bases_expanded) do
table.insert(high_res, (gsub(ipa, "æ", "ɛɐ̯")))
local l = gsub(gsub(gsub(ipa, "æ", "e"), "ʎ", "l"), "l ?ˈ?l", "lː")
table.insert(low_res, (gsub(l, "n([fvʋ])", "ɱ%1")))
end
return {
phonemic = phonemic,
high = high_res,
low = low_res
}
end
-- =============================================================================
-- [[ MAIN EXPORT ]]
-- =============================================================================
function export.show(frame)
local args = frame:getParent().args
local title = mw.title.getCurrentTitle()
local t = args["t"] or ""
NAMESPACE = title.nsText
PAGENAME = (NAMESPACE == "") and title.text or (args["pagename"] or "Pagename not specified")
LOAN = mw.ustring.find(t, "loan") and 1 or 0
HARD_SUFFIX = mw.ustring.find(t, "adj") and 1 or 0
AS_IMP = mw.ustring.find(t, "imp") and 1 or 0
IS_MO_VARIANT = false
local word = (args["1"] ~= nil) and args["1"] or PAGENAME
local original_word = word
local manual_hard = (args["1"] ~= nil and mw.ustring.find(args["1"], "[DTNL]"))
-- Display Switches
local show_rhymes = (args["r"] ~= "0")
and not mw.ustring.find(word, "^%-")
and not mw.ustring.find(word, "%-$")
local show_hyph = (args["h"] ~= "0")
local audio = args["a"] or false
local audio_accent = args["aa"] or ""
-- Pipeline
if not manual_hard then word = step_foreign_graphemes(word) end
word = step_softening(word)
if manual_hard then word = step_foreign_graphemes(word) end
word = step_prosody(word)
word = step_simplification(word)
word = step_assimilation(word)
word = step_v_f_j_realization(word)
word = step_final_devoicing(word)
local final = generate_all_transcriptions(word, original_word)
local nuclei, clean_ipa = get_ipa_nuclei(final.phonemic)
local out = format_ipa_output(final, frame)
if audio then out = out .. "\n*" .. frame:expandTemplate{title="audio", args={"sk", audio, a=audio_accent}} end
if show_rhymes then out = out .. format_rhymes(frame, nuclei, clean_ipa) end
if show_hyph then out = out .. format_hyphenation(PAGENAME, final.phonemic, frame, original_word, nuclei) end
return out
end
return export
q8ngbwbmkptruur2cs34fz3fwbwj1ji