Wîkîferheng
kuwiktionary
https://ku.wiktionary.org/wiki/Destp%C3%AAk
MediaWiki 1.47.0-wmf.24
case-sensitive
Medya
Taybet
Gotûbêj
Bikarhêner
Gotûbêja bikarhêner
Wîkîferheng
Gotûbêja Wîkîferhengê
Wêne
Gotûbêja wêneyî
MediaWiki
Gotûbêja MediaWiki
Şablon
Gotûbêja şablonê
Alîkarî
Gotûbêja alîkariyê
Kategorî
Gotûbêja kategoriyê
Pêvek
Gotûbêja pêvekê
Nimînok
Gotûbêja nimînokê
Portal
Gotûbêja portalê
Tewandin
Gotûbêja tewandinê
Jinûvesazî
Gotûbêja jinûvesaziyê
TimedText
TimedText talk
Modul
Gotûbêja modulê
Event
Event talk
mişk
0
878
6865528
6797609
2026-10-23T19:27:51Z
Wikihez
7344
Kurmancî/Navdêr: id hate guhartin, beşa «Resim» hate nûkirin (DefSideBoxes)
6865528
wikitext
text/x-wiki
{{h-bnr|misk|mîsk}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
=== Navdêr ===
[[Wêne:Micromys minutus (aka).jpg|thumb|Mişkek ({{maneno|ku|guhandar}})]]
[[Wêne:3-Tasten-Maus Microsoft.jpg|thumb|'''Mişkekî''' kompûterê ({{maneno|ku|informatîk}})]]
{{wp|Mişk (cudakirin)|Mişk}}
{{navdêr|ku|z=n}}
# {{maneid|ku|guhandar}} {{f|ku|guhandar}} [[heywan|Heywanekî]] [[biçûk]] yê [[kirîner]] e û [[şibîn|dişibe]] [[cird]]an lê [[hûrik]]tir e.
#: {{mk|ku|Kitik ne li mal e, navê '''mişkî''' Evdal e.}}
# {{maneid|ku|informatîk}} {{f|ku|informatîk}} [[amûr|Amûrekî]] [[kompûter]]ê ye ku mirov pê [[nivîs]] û [[grafîk]]an [[livandin|dilivîne]].
==== Tewandin ====
{{ku-tewîn-nav|nêr}}
==== Bi alfabeyên din ====
* {{ku-ar|مشک}}
* {{ku-kr|мьшк}}
==== Formên alternatîv ====
* {{hw|ku|mihşk}}
==== Jê ====
{{kol3|ku
| mişkane
| mişke
| mişko
| mişkok
}}
==== Etîmolojî ====
{{etîmon|ku|:drşnd|ine-pro:*múh₂s|metnna=1}}
Ji {{ji|ine-pro|ku|*múh₂s}}. Hevreha {{hv|ckb|مشک}}, [[kurdiya başûrî]] û lekî {{q|sdh|müş|sc=Latn}}, {{hv|bal|موشک|tr=muşk}} û {{g|bal|موشک|tr=mûşk}}, {{hv|pal||muşk|sc=Latn}}, {{hv|fa|موش|tr=mûş}}, {{hv|os|мыст|tr=mist}} û {{g|os|мистӕ|tr=mistæ}}, {{hv|ae|𐬨𐬏𐬱|tr=mūš}}, {{hv|sa|मूष्|tr=mū́ṣ}}, {{hv|hi|मूस|tr=mūs}}, {{hv|bg|мишка|tr=mişka}}, {{hv|ru|мыш|tr=miš}}, {{hv|cs|myš}}, {{hv|grc|μῦς|mûs}}, {{hv|la|mūs}}, {{hv|en|mouse}}, {{hv|de|Maus}}, danmarkî/norwecî/swêdî ''[[mus]]'', {{hv|hy|մուկ|tr=muk}}, albanî ''[[mi]]''...
Herfa K li dawiya peyva kurmancî nîşana [[biçûk]]kirinê ([[dîmînutîv]]) Ye ji ber ku mişk [[heywan]]ekî [[hûrik]] e.
Peyvên [[tatî]] {{g|fa|مورا}} (mûra/mora), talîşî {{g|fa|موره}} (more/morê), zazakî ''[[merre]]'', goranî/hewramî ''[[miłe]]'' û xotanî ''mûle-'' ji eynî rehî ne. Di wan de pêşî S/Ş bûye R û paşî di goranî û xotanî de R bûye L/Ł. Bi heman awayî ''[[mūs]]'' ya latînî di spanî de bûye ''[[mur]]'' û di portugalî de bûye ''[[mure]]''.
Bi maneya "amûra kompûterê" {{wergirtin|en|ku|mouse|hûr=1}}.
{{veşêre-ser|Çavkanî}}
* {{Abayev|r= 142-143}}
* {{Bailey|r= 337}}
* {{Chyet|r= 393}}
* {{Horn|r= 223}}
* {{MacKenzie|r= 129}}
* {{Lubotsky|r=752}}
* {{Tsabolov|r=669-670}}
* {{Etymonline}}
{{veşêre-bin}}
==== Bide ber ====
{{kol3|ku
| meşk
| misk
| hişk
| pişk
| rişk
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-|ckb|mişk|sc=Latn}}
* {{Z|sdh}}: {{W-|sdh|müş|sc=Latn}}
* {{Z|lki}}: {{W-|lki|müş|sc=Latn}}
* {{Z|hac}}: {{W-|hac|miłe|sc=Latn}}
* {{Z|zza}}: {{W+|zza|merre}}
==== Werger ====
{{werger-ser}}
* {{Z|ab}}: {{W-|ab|аҳәынаҧ|sc=Cyrl}}
* {{Z|af}}: {{W+|af|muis}}
* {{Z|sq}}: {{W+|sq|mi|n}}
* {{Z|de}}: {{W+|de|Maus|m}}
*: {{Z|gsw}}: {{W-|gsw|Muus|m}}
* {{Z|am}}: {{W-|am|አይጥ|sc=Ethi}}
* {{Z|ani}}: {{W-|ani|гьинкӏкӏу}}
* {{Z|an}}: {{W-|an|zorz}}
* {{Z|rup}}: {{W-|rup|shoaric}}
* {{Z|aer}}: {{W-|aer|thakwere}}
* {{Z|as}}: {{W-|as|নিগনি}}
* {{Z|ast}}: {{W+|ast|ratu|n}}, {{W+|ast|ratón|n}}, {{W+|ast|mur}}
* {{Z|tay}}: {{W-|tay|olit}}
* {{Z|av}}: {{W-|av|гӏункӏкӏ|sc=Cyrl}}
* {{Z|ay}}: {{W+|ay|achaku}}
* {{Z|ain}}: {{W-|ain|エㇾㇺ|tr=erm}}, {{W-|ain|エㇾム|tr=ermu}}, {{W-|ain|エルㇺ|tr=erum}}
* {{Z|az}}: {{W+|az|siçan}}
* {{Z|ban}}: {{W-|ban|bikul}}, {{W-|ban|jero ketut}}
* {{Z|eu}}: {{W+|eu|sagu}}
* {{Z|ba}}: {{W-|ba|сысҡан|sc=Cyrl}}
* {{Z|be}}: {{W-|be|мыш|m}}, {{W-|be|мы́шка|m}}
* {{Z|bal}}: {{W-|bal|مشک|tr=mušk}}, {{W-|bal|موشک|tr=múšk}}
* {{Z|bn}}: {{W+|bn|ইঁদুর|sc=Beng}}
* {{Z|br}}: {{W+|br|logodenn|m}}
* {{Z|bg}}: {{W+|bg|ми́шка|m}}, {{W-|bg|мишо́к|n}}
* {{Z|my}}: {{W+|my|ကြွက်|sc=Mymr}}
* {{Z|pcc}}: {{W-|pcc|ranz}}, {{W-|pcc|waauc}}
* {{Z|ccc}}: {{W-|ccc|kijsili}}
* {{Z|ce}}: {{W-|ce|дахка}}
* {{Z|cs}}: {{W+|cs|myš|m}}, {{W+|cs|myšák|n}}
* {{Z|chr}}: {{W-|chr|ᏥᏍᏕᏥ}}
* {{Z|ny}}: {{W-|ny|mbewa}}
* {{Z|cic}}: {{W-|cic|pinti'}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|老鼠|tr=lou5 syu2}}
*: {{Z|dng}}: {{W-|dng|лочў}}
*: {{Z|hak}}: {{W-|hak|老鼠|tr=lo-chhú, ló-chhú, no-chhú}}
*: {{Z|lzh}}: {{W-|lzh|鼠|tr=shǔ}}, {{W-|lzh|鼷|tr=xī}}
*: {{Z|cmn}}: {{W+|cmn|老鼠|tr=lǎoshǔ}}, {{W+|cmn|鼠|tr=shǔ}}, {{W+|cmn|耗子|tr=hàozi}}, {{W+|cmn|小鼠|tr=xiǎoshǔ}}, {{W+|cmn|鼷|tr=xī}}
*: {{Z|cdo}}: {{W-|cdo|老鼠|tr=lō̤-chṳ̄}}
*: {{Z|nan}}: {{W+|nan|鳥鼠|tr=niáu-chhí, niáu-chhú}}, {{W+|nan|鼠|tr=chhí, chhú}}, {{W+|nan|錢鼠|tr=chîⁿ-chhí, chîⁿ-chhú}}, {{W-|nan|老鼠|tr=ló͘-chhír, ló͘-chhí, niáu-chhú, niáu-chhí, niáu-chhír}}
*: {{Z|wuu}}: {{W-|wuu|老鼠}}
* {{Z|ckt}}: {{W-|ckt|пипиӄыԓгын|tr=pipiqyḷgyn}}
* {{Z|cv}}: {{W-|cv|шӑши|sc=Cyrl}}
* {{Z|dlm}}: {{W-|dlm|surco|n}}
* {{Z|da}}: {{W+|da|mus|g}}
* {{Z|dv}}: {{W-|dv|މީދާ|sc=Thaa}}
* {{Z|id}}: {{W+|id|tikus}}
* {{Z|ar}}: {{W-|ar|فَأْرَة|m}}, {{W-|ar|فَأْر|n}}
*: {{Z|arz}}: {{W-|arz|فار|tr=fār}}
*: {{Z|ary}}: {{W-|ary|فار|tr=fār}}
* {{Z|hy}}: {{W+|hy|մուկ}}
*: {{Z|xcl}}: {{W-|xcl|մուկն|sc=Armn}}
* {{Z|myv}}: {{W-|myv|чеерь}}
* {{Z|eo}}: {{W+|eo|muso}}, {{W-|eo|musino}}, {{W-|eo|musido}}, {{W-|eo|musidino}}
* {{Z|et}}: {{W+|et|hiir}}
* {{Z|eve}}: {{W-|eve|ча̄ма̇кчан}}
* {{Z|evn}}: {{W-|evn|сиңэрэкэн}}
* {{Z|ee}}: {{W-|ee|afi|nt}}
* {{Z|fa}}: {{W+|fa|موش|tr=muš}}, {{W+|fa|موشک|tr=mušk}}
* {{Z|fo}}: {{W+|fo|mús|m}}
* {{Z|fi}}: {{W+|fi|hiiri}}
* {{Z|fr}}: {{W+|fr|souris|m}}
* {{Z|fur}}: {{W-|fur|surîs|m}}
* {{Z|frr}}: {{W-|frr|müs|nt}}
* {{Z|fy}}: {{W-|fy|mûs}}
* {{Z|gd}}: {{W-|gd|luch|m}}
* {{Z|gl}}: {{W+|gl|rato|n}}
* {{Z|gu}}: {{W-|gu|ઉંદર}}
* {{Z|ka}}: {{W+|ka|თაგვი|sc=Geor}}
* {{Z|haw}}: {{W-|haw|ʻiole liʻiliʻi}}
* {{Z|ha}}: {{W-|ha|ɓera}}
* {{Z|hi}}: {{W+|hi|चूहा|n|sc=Deva}}
* {{Z|hil}}: {{W-|hil|ilaga}}, {{W-|hil|balabaw}}
* {{Z|mww}}: {{W-|mww|nas}}
* {{Z|nl}}: {{W+|nl|muis|m}}
* {{Z|he}}: {{W+|he|עַכְבָּר|n|tr=akhbár|sc=Hebr}}
* {{Z|io}}: {{W+|io|muso}}
* {{Z|ig}}: {{W-|ig|ọke}}
* {{Z|ilo}}: {{W-|ilo|bao}}
* {{Z|ang}}: {{W-|ang|mūs}}
* {{Z|en}}: {{W+|en|mouse}}
* {{Z|inh}}: {{W-|inh|дахка}}
* {{Z|ia}}: {{W-|ia|mus}}, {{W-|ia|mure}}
* {{Z|ga}}: {{W+|ga|luch|m}}, {{W+|ga|luchóg|m}}
* {{Z|it}}: {{W+|it|topo|n}}, {{W+|it|sorcio|n}}, {{W+|it|ratto|n}}
* {{Z|is}}: {{W+|is|mús|m}}
* {{Z|ja}}: {{W+|ja|鼠|tr=ねずみ, nezumi}}, {{W+|ja|ネズミ|tr=nezumi}}, {{W+|ja|ハツカネズミ|tr=hatsukanezumi|sc=Jpan}}, {{W+|ja|マウス|tr=mausu|sc=Jpan}}
* {{Z|jra}}: {{W-|jra|tơkuih}}
* {{Z|jv}}: {{W+|jv|tikus}}
* {{Z|kl}}: {{W-|kl|teriaq}}
* {{Z|xal}}: {{W-|xal|хулһн|sc=Cyrl}}
* {{Z|kn}}: {{W+|kn|ಇಲಿ|sc=Knda}}
* {{Z|kr}}: {{W-|kr|jilwa}}
* {{Z|pam}}: {{W-|pam|dagis}}
* {{Z|krl}}: {{W-|krl|hiiri}}
* {{Z|csb}}: {{W-|csb|mësz|m}}
* {{Z|ca}}: {{W+|ca|ratolí|n}}
* {{Z|qu}}: {{W-|qu|ukuca}}, {{W-|qu|ukuş}}, {{W-|qu|juk'ucha}}
* {{Z|ky}}: {{W+|ky|чычкан|sc=Cyrl}}
* {{Z|si}}: {{W-|si|මීයා|sc=Sinh}}
* {{Z|rw}}: {{W-|rw|umushushwe|c3/4}}, {{W-|rw|imbeba|c9/10}}, {{W-|rw|im-beba}}
* {{Z|ko}}: {{W+|ko|생쥐}}, {{W+|ko|쥐}}
* {{Z|kw}}: {{W-|kw|logosen|m}}
* {{Z|rop}}: {{W-|rop|brudbrud}}
* {{Z|xug}}: {{W-|xug|っゑっちゅー|tr=wwecchū}}
* {{Z|fkv}}: {{W-|fkv|hiiri}}
* {{Z|ltg}}: {{W-|ltg|pele}}
* {{Z|la}}: {{W+|la|mūs|n|m}}
* {{Z|lv}}: {{W+|lv|pele|m}}
* {{Z|lo}}: {{W+|lo|ໜູ}}
* {{Z|lez}}: {{W-|lez|кьиф}}
* {{Z|lt}}: {{W+|lt|pelė|m}}, {{W-|lt|pelėnas|n}}
* {{Z|lmo}}: {{W-|lmo|ratt}}
* {{Z|khb}}: {{W-|khb|ᦐᦴ}}
* {{Z|lg}}: {{W-|lg|emmese}}
* {{Z|lb}}: {{W+|lb|Maus|m}}
* {{Z|lut}}: {{W-|lut|k'ʷat'ad}}
* {{Z|mk}}: {{W+|mk|глушец|n}}, {{W-|mk|глувче|nt}}, {{W-|mk|миш|n}}, {{W+|mk|глушец|n}}
* {{Z|ml}}: {{W+|ml|ചുണ്ടെലി|sc=Mlym}}
* {{Z|ms}}: {{W+|ms|tikus}}, {{W-|ms|mencit}}
* {{Z|mt}}: {{W-|mt|ġurdien|n}}
* {{Z|mnc}}: {{W-|mnc|ᠰᡳᠩᡤᡝᡵᡳ}}
* {{Z|gv}}: {{W-|gv|lugh|n}}
* {{Z|mi}}: {{W+|mi|kiore}}
* {{Z|mr}}: {{W-|mr|उंन्दिर|sc=Deva}}
* Marî:
*: {{Z|mhr}}: {{W-|mhr|коля}}
*: {{Z|mrj}}: {{W-|mrj|каля}}
* {{Z|mzn}}: {{W-|mzn|گل|tr=gal}}
* {{Z|hu}}: {{W+|hu|egér}}
* {{Z|egy}}: {{W-egy|pnw|h=p:n-nw-w-F27}}
* {{Z|mvi}}: {{W-|mvi|ゆむぬ|tr=yumunu}}
* {{Z|mic}}: {{W-|mic|apigji'j|an}}, {{W-|mic|apugji'j|an}}
* {{Z|mwl}}: {{W-|mwl|rato|n}}
* {{Z|mdf}}: {{W-|mdf|шеер}}
* {{Z|mn}}: {{W+|mn|хулгана|sc=Cyrl}}, {{W+|mn|оготно|sc=Cyrl}}
* {{Z|nah}}: {{W+|nah|quimichin}}
* {{Z|gld}}: {{W-|gld|сингэрэ}}
* {{Z|nap}}: {{W-|nap|sorece|n}}
* {{Z|nv}}: {{W-|nv|naʼatsʼǫǫsí}}
* {{Z|nds}}:
*: {{Z|nds-de}}: {{W-|nds-de|Muus|m}}
* {{Z|ne}}: {{W+|ne|मुसो|sc=Deva}}
* {{Z|niv}}: {{W-|niv|мухтук|sc=Cyrl}}
* {{Z|nrf}}: {{W-|nrf|souothis|m}}
* {{Z|no}}: {{W+|no|mus|m}}
* {{Z|oj}}: {{W-|oj|waawaabiganoojiinh}}
* {{Z|ryu}}: {{W-|ryu|ゑんちゅ|tr=wenchu}}
* {{Z|oc}}: {{W+|oc|mirga|m}}, {{W-|oc|fureta|m}}, {{W+|oc|ratuga|m}}
* {{Z|or}}: {{W+|or|ମୂଷା}}
* {{Z|osa}}: {{W-|osa|įchóka}}
* {{Z|os}}: {{W-|os|мыст|sc=Cyrl}}
* {{Z|ota}}: {{W-|ota|صیچان|tr=sıçan|sc=ota-Arab}}, {{W-|ota|فأرة|tr=fare|sc=ota-Arab}}, {{W-|ota|فأر|tr=far|sc=ota-Arab}}, {{W-|ota|موش|tr=muş}}
* {{Z|ug}}: {{W-|ug|چاشقان|sc=ug-Arab}}
* {{Z|uz}}: {{W+|uz|sichqon}}
* {{Z|ps}}: {{W+|ps|موږک|n|tr=muģák|sc=ps-Arab}}
* {{Z|pdt}}: {{W-|pdt|Mus|m}}
* {{Z|pl}}: {{W+|pl|mysz|m}}, {{W+|pl|myszka|m}}
* {{Z|pt}}: {{W+|pt|rato|n}}, {{W+|pt|camundongo|n}}
* {{Z|prg}}: {{W-|prg|pelē}}
* {{Z|pa}}: {{W+|pa|ਚੂਹਾ}}
* {{Z|kk}}: {{W-|kk|тышқан|sc=Cyrl}}
* {{Z|rm}}: {{W-|rm|mieur}}
* {{Z|ro}}: {{W+|ro|șoarece|n}}
* {{Z|ru}}: {{W+|ru|мышь|m}}, {{W+|ru|мы́шка|m}}
* {{Z|ksw}}: {{W-|ksw|ယုၢ်}}
* {{Z|skb}}: {{W-|skb|หนู่}}
* {{Z|se}}: {{W-|se|sáhpán}}
* {{Z|smn}}: {{W-|smn|säplig}}
* {{Z|sms}}: {{W-|sms|sä´ppli}}
* {{Z|sm}}: {{W-|sm|'isumu}}, {{W-|sm|'iole}}
* {{Z|sa}}: {{W+|sa|मूष्|n|m}}, {{W+|sa|आखु|n}}
* {{Z|sc}}: {{W-|sc|tope}}, {{W-|sc|topi}}
*: {{Z|sc}}: {{W-|sc|medrona|m}}, {{W-|sc|topi}}, {{W-|sc|sórixi|n}}
* {{Z|ceb}}: {{W-|ceb|ilaga}}
* {{Z|scn}}: {{W+|scn|surci|n}}, {{W+|scn|sùrici|n}}
* {{Z|sd}}: {{W-|sd|ڪوئو|tr=kūʾō|cuda=ڪوُئو|sc=sd-Arab}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|ми̏ш|n}}
*: Latînî: {{W+|sh|mȉš|n}}
* {{Z|sco}}: {{W-|sco|moose}}
* {{Z|cu}}:
*: Kirîlî: {{W-|cu|мꙑшь|m}}
* {{Z|orv}}: {{W-|orv|мꙑшь|m}}
* {{Z|sk}}: {{W+|sk|myš|m}}
* {{Z|sl}}: {{W+|sl|miš|m}}
* {{Z|so}}: {{W+|so|jiir|n}}
* Sorbî:
*: {{Z|hsb}}: {{W-|hsb|myš|m}}, {{W-|hsb|myška|m}}
*: {{Z|dsb}}: {{W-|dsb|myš|m}}, {{W-|dsb|myška|}}
* {{Z|st}}: {{W+|st|tweba}}
* {{Z|es}}: {{W+|es|ratón|n}}, {{W+|es|mur|n}}
* {{Z|sw}}: {{W+|sw|kipanya}}
* {{Z|sv}}: {{W+|sv|mus|g}}
* {{Z|shn}}: {{W-|shn|ၼူ}}
* {{Z|tg}}: {{W+|tg|муш|sc=Cyrl}}
* {{Z|tl}}: {{W+|tl|daga}}
* {{Z|tht}}: {{W-|tht|dlūne}}
* {{Z|tly}}: {{W-|tly|морә|tr=morǝ}}
* {{Z|ta}}: {{W+|ta|சுண்டெலி|sc=Taml}}
* {{Z|twf}}: {{W-|twf|cìyúna}}
* {{Z|roa-tar}}: {{W-|roa-tar|topo|n}}
* {{Z|rif}}: {{W-|rif|aɣerḍa|n}}
* {{Z|blt}}: {{W-|blt|ꪘꪴ}}
* {{Z|nod}}: {{W-|nod|ᩉ᩠ᨶᩪ}}
* {{Z|th}}: {{W+|th|หนู}}
* {{Z|te}}: {{W+|te|ఎలుక|sc=Telu}}
* {{Z|crh}}: {{W-|crh|sıçan}}
* {{Z|tt}}: {{W+|tt|тычкан|sc=Cyrl}}
* {{Z|ti}}: {{W-|ti|ኣንጭዋ}}
* {{Z|tr}}: {{W+|tr|fare}}, {{W+|tr|sıçan}}
* {{Z|tk}}: {{W-|tk|syçan}}, {{W-|tk|sıçan}}
* {{Z|bo}}: {{W-|bo|ཙི་ཙི|sc=Tibt}}
* {{Z|tli}}: {{W-|tli|kagaák}}
* {{Z|tpi}}: {{W-|tpi|liklik rat}}
* {{Z|to}}: {{W-|to|kumā}}
* {{Z|tn}}: {{W-|tn|pêba}}
* {{Z|tum}}: {{W-|tum|mbewa}}
* {{Z|udm}}: {{W-|udm|шыр}}
* {{Z|ur}}: {{W-|ur|چوہا|n|tr=cūhā|sc=ur-Arab}}
* {{Z|uk}}: {{W+|uk|ми́ша|m}}, {{W-|uk|миш|m}}, {{W-|uk|ми́шка|m}}, {{W-|uk|миша́к|n}}
* {{Z|vec}}: {{W-|vec|ràto}}, {{W-|vec|sorze}}
* {{Z|vi}}: {{W+|vi|chuột}}
* {{Z|vo}}: {{W+|vo|mug}}
* {{Z|wa}}: {{W+|wa|sori|m}}
* {{Z|cy}}: {{W+|cy|llygoden|m}}, {{W+|cy|llygod|p}}
* {{Z|wym}}: {{W-|wym|maojs}}
* {{Z|wo}}: {{W-|wo|janax|cuda=janax ji}}
* {{Z|km}}: {{W-|km|កណ្ដុរស្រូវ}}, {{W+|km|កណ្ដុរ}}
* Kipchak:
*: {{Z|qwm}}: {{W-|qwm|սըչխան|tr=sïč̣χan}}
* {{Z|xh}}: {{W-|xh|ilifu}}
* {{Z|rys}}: {{W-|rys|うぇんちゅ|tr=Wenchu}}
* {{Z|yai}}: {{W-|yai|муш}}
* {{Z|sah}}: {{W-|sah|кутуйах|sc=Cyrl}}
* {{Z|yag}}: {{W-|yag|wasána}}
* {{Z|yi}}: {{W-|yi|מויז|sc=Hebr}}
* {{Z|yoi}}: {{W-|yoi|うやんとぅ|tr=uyantu}}
* {{Z|yo}}: {{W-|yo|èkúté}}, {{W-|yo|eliri}}
* {{Z|el}}: {{W+|el|ποντίκι|nt}}, {{W+|el|ποντικός|n|sc=Grek}}
*: {{Z|grc}}: {{W-|grc|μῦς|n}}, {{W-|grc|ποντικός|n}}
* {{Z|za}}: {{W-|za|nou}}
* {{Z|zu}}: {{W+|zu|igundane|c5|c6}}
{{werger-bin}}
:{{werger çavkanî|en|mouse|oldid=75626351|beş=Ev qismê Wergerê}}
==== Anagram ====
* {{anagrams|ku|a=ikmş|şimk}}
n7amxbn6xgtf1nf8so0iirmqpie5u0w
qalind
0
3333
6865529
6354385
2026-10-23T19:32:00Z
Wikihez
7344
Kurmancî/Rengdêr: beşa «Jê» hate nûkirin, beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6865529
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Rengdêr ===
{{ku-tewîn-rd|qalind}}
{{rengdêr|ku}}
# ne [[tenik]], [[stûr]], ne [[zirav]]
#: ''Ev pirtûk pirr '''qalind''' e, zêdeyî 1000 rûpelan tê de hene.
#: ''Dengê wê wisan '''qalind''' e ku dibêjî qey zelam e.''
==== Formên alternatîv ====
* [[qaling]]
==== Jê ====
{{kol3|ku|cure=Jê
|qalindî
|qalindîtî
|qalindtî
|qalind bûn<crz:lêker>
|qalind kirin<crz:lêker>
|qalindbûn<crz:navdêr>
|qalindkirin<crz:navdêr>
|qalindbûyî<crz:rengdêr>
|qalindkirî<crz:rengdêr>
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|tr:kalın}} Hevreha azerî [[qalın]], tirkmenistanî [[galyň]]. Bo zêdebûna dengê D li pey "(i)n" bide ber "[[mindal|mindał]]" ([[zarok]]) ya soranî ji erebî {{g|ar|منال}} (menal) û "[[elind]]" ya kurmancî ji erebî '''elen''.
==== Bide ber ====
* [[qelew]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|dick}}, {{W+|de|wulstig}}
* {{Z|id}}: {{W+|id|tebal}}
* {{Z|ar}}: {{W-|ar|سميك|tr=semîk}}
* {{Z|fa}}: {{W+|fa|ضخیم}}, {{W+|fa|کلفت}}
* {{Z|fi}}: {{W+|fi|paksu}}
* {{Z|fr}}: {{W+|fr|gros}} {{n}}, {{W+|fr|grosse}} {{mê}}, {{W+|fr|épais}} {{n}}, {{W+|fr|épaisse}} {{mê}}
* {{Z|nl}}: {{W+|nl|dik}}, {{W+|nl|dikke}}
* {{Z|en}}: {{W+|en|thick}}, {{W+|en|pachy-}}, {{W+|en|pycno-}}, {{W+|en|bold|guman=erê}}, {{W-|en|a bit thick|guman=erê}}
* {{Z|it}}: {{W+|it|spesso}} {{n}}, {{W+|it|spessa}} {{mê}}
* {{Z|ja}}: {{W+|ja|太い|tr=[[ふとい]], futoi}}, {{W+|ja|厚い|tr=[[あつい]], atsui}}
* {{Z|ko}}: [[두꺼운]] (dukkeo-un)
* {{Z|hu}}: {{W+|hu|vastag}}
* {{Z|pt}}: {{W+|pt|espesso}}
* {{Z|ro}}: {{W+|ro|gros}}
* {{Z|ru}}: [[толстый]] (tólstyj)
* {{Z|sh}}: {{W+|sh|debeo}}
* {{Z|sl}}: {{W+|sl|debel}}
* {{Z|es}}: {{W+|es|grueso}}
* {{Z|sv}}: {{W+|sv|tjock}}
* {{Z|te}}: [[మందమైన]] (maMdamaina)
* {{Z|tr}}: {{W+|tr|kesif}}, {{W+|tr|kalın}}, {{W+|tr|tokmak tokmak}}, {{W+|tr|yoğun|guman=erê}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=adilnq|dilqan}}
jval1t94sv7qyozxg4wt5sci0bbyzrf
karker
0
3951
6865525
6797856
2026-10-23T19:20:34Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6865525
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA|k(h)arker}}
* {{deng|ku|LL-Q36368 (kur)-Dildadil-karker.wav|dever=Amed}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-karker.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# {{f|ku|pîşe}} Kesekî ku [[kar]]ekî/[[şol]]ekî/[[şuxl]]ekî dike, kesêkî ku [[xebitîn|dixebite]] / [[şuxilîn|dişuxile]].
#* {{J:Albert Camus:Biyanî|jêgirtin=Ew herdu Ereb bi çekên taybetî bûn ên '''karkerên''' ku agir di tenûr û fıman de dadidan û ew bi aliyê me de dihatin.}}
#* {{jêder-websîte|ku|nivîskar=w:Roşan Lezgîn|websîte=Zazaki.net|sernav=Ji Zilma Partîzanan Nimûneyeka Piçûk|url=https://www.zazaki.net/kurmanci/ji-zilma-partizanan-nimuneyeka-picuk-3014.htm|tarîxa-gihiştinê=2026-09-13|tarîx=2026-05-20|jêgirtin=Ez '''karker''' im. Hatina min tenê renca destê min e, keda enîya min e.}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Hevmane ====
{{kol3|ku
|karger
|kirêkar
|kedkar
|şolker
|şuxlker
|xebatkar
}}
==== Jê ====
{{kol3|ku
|karkerî
}}
==== Etîmolojî ====
{{etîmon|ku|:qertaf|kar<id:xebat, îş>|-ker}}
==== Herwiha binêre ====
{{kol3|ku
|ked
|şol
|şuxl
|xebitîn
}}
==== Werger ====
{{werger-ser}}
* {{Z|ady}}: {{W-|ady|лэжьакӏо}}
* {{Z|af}}: {{W+|af|werker}}
* {{Z|xag}}: {{W-|xag|𐕌𐕒𐕡𐔽𐔰𐕄}}
* {{Z|sq}}: {{W+|sq|punëtor|n}}, {{W+|sq|punëtore|m}}
* {{Z|gmw-ecg}}: {{W-|gmw-ecg|Arbittr|n}}
* {{Z|de}}: {{W+|de|Arbeiter|n}}, {{W+|de|Arbeiterin|m}}, {{W+|de|Angestellter|n}}, {{W+|de|Angestellte|m}}, {{W+|de|Hackler|n}}, {{W-|de|Hacklerin|m}}, {{W+|de|Arbeitnehmer|n}}, {{W+|de|Arbeitnehmerin|m}}
* {{Z|am}}: {{W-|am|ሠራተኛ}}
* {{Z|an}}: {{W-|an|treballador}}
* {{Z|rup}}: {{W-|rup|lucrãtor|n}}, {{W-|rup|lucrãtonj|n}}, {{W-|rup|argat|n}}
* {{Z|ast}}: {{W-|ast|obreru|n}}
* {{Z|az}}: {{W+|az|işçi}}, {{W+|az|fəhlə}}, {{W-|az|zəhmətkeş}}
* {{Z|ba}}: {{W-|ba|эшсе}}
* {{Z|be}}: {{W-|be|рабо́тнік|n}}, {{W-|be|рабо́тніца|m}}, {{W-|be|працаўні́к|n}}, {{W-|be|працаўні́ца|m}}
* {{Z|bn}}: {{W-|bn|কর্মী}}
* {{Z|br}}: {{W+|br|labourer|n}}, {{W+|br|micherour|n}}
* {{Z|bg}}: {{W-|bg|рабо́тник|n}}, {{W-|bg|рабо́тничка|m}}
* {{Z|my}}: {{W+|my|အလုပ်သမား}}, {{W+|my|လုပ်သား}}
* {{Z|cs}}: {{W+|cs|dělník|n}}, {{W+|cs|dělnice|m}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|工人|tr=gōngrén}}, {{W+|cmn|工作者|tr=gōngzuòzhě}}, {{W-|cmn|勞動者|tr=láodòngzhě}}, {{W+|cmn|劳工|tr=láogōng}}
* {{Z|da}}: {{W+|da|arbejder|g}}
* {{Z|id}}: {{W+|id|buruh}}
* {{Z|ar}}: {{W+|ar|عَامِل|n}}, {{W+|ar|عَامِلَة|m}}
* {{Z|hy}}: {{W+|hy|աշխատող}}, {{W+|hy|բանվոր}}
* {{Z|eo}}: {{W-|eo|laboristo}}, {{W-|eo|laboristino}}, {{W-|eo|laboranto}}, {{W-|eo|laborantino}}, {{W-|eo|proleto}}, {{W-|eo|proletino}}, {{W-|eo|proletario}}, {{W-|eo|proletariino}}
* {{Z|et}}: {{W+|et|töötaja}}
* {{Z|evn}}: {{W-|evn|һавалимни}}
* {{Z|fa}}: {{W+|fa|کارگر|tr=kârgar}}
* {{Z|fi}}: {{W+|fi|työläinen}}
* {{Z|fr}}: {{W+|fr|travailleur|n}}, {{W+|fr|travailleuse|m}}, {{W+|fr|ouvrier|n}}, {{W+|fr|ouvrière|m}}
* {{Z|gd}}: {{W-|gd|obraiche|n}}, {{W-|gd|neach-obrach|n}}
* {{Z|gl}}: {{W+|gl|obreiro|n}}
* {{Z|got}}: {{W-|got|𐍅𐌰𐌿𐍂𐍃𐍄𐍅𐌰|n}}
* {{Z|got}}: {{W-|got|𐍅𐌰𐌿𐍂𐍃𐍄𐍅𐌰|n}}
* {{Z|ka}}: {{W-|ka|მუშა}}, {{W-|ka|მუშაკი}}
* {{Z|ht}}: {{W-|ht|travayè}}
* {{Z|hi}}: {{W+|hi|मज़दूर|n}}, {{W+|hi|कर्मी}}, {{W+|hi|कर्मचारी|n}}
* {{Z|nl}}: {{W+|nl|arbeider|n}}, {{W+|nl|arbeidster|m}}, {{W+|nl|arbeidskracht|n|m}}
* {{Z|he}}: {{W-|he|עוֹבֵד|n|tr=oved}}, {{W-|he|עובדת|m|tr=ovedet}}, {{W-|he|פּוֹעֵל|n|tr=po'él}}, {{W-|he|פּוֹעֶלֶת|m|tr=pó'élet}}
* {{Z|io}}: {{W+|io|laboristo}}
* {{Z|ang}}: {{W-|ang|wyrhta|n}}
* {{Z|en}}: {{W+|en|worker}}
* {{Z|izh}}: {{W-|izh|tööläin}}
* {{Z|ga}}: {{W+|ga|oibrí|n}}
* {{Z|it}}: {{W+|it|lavoratore}}, {{W+|it|operaio}}, {{W+|it|lavorante}}
* {{Z|is}}: {{W+|is|starfsmaður|n}}
* {{Z|ja}}: {{W+|ja|労働者|tr=ろうどうしゃ, rōdōsha}}, {{W+|ja|勤労者|tr=きんろうしゃ, kinrōsha}}, {{W+|ja|働|tr=はたらくひと, hataraku hito|cuda=働く人}}
* {{Z|ca}}: {{W+|ca|treballador|n}}, {{W+|ca|treballadora|m}}, {{W+|ca|obrer|n}}
* {{Z|qu}}: {{W-|qu|llamk'aq}}
* {{Z|ky}}: {{W+|ky|жумушчу}}
* {{Z|si}}: {{W-|si|කම්කරුවා}}, {{W-|si|වැඩකාරයා}}
* {{Z|ko}}: {{W+|ko|노동자}}, {{W+|ko|로동자}}, {{W+|ko|근로자}}
* {{Z|lld}}: {{W-|lld|laurant}}
* {{Z|lad}}: {{W-|lad|lavorante}}, {{W-|lad|ovrero}}, {{W-|lad|ovrador}}, {{W-|lad|lavurador}}
* {{Z|la}}: {{W-|la|operārius|n}}
* {{Z|lv}}: {{W+|lv|strādnieks|n}}, {{W-|lv|strādniece|m}}
* {{Z|lo}}: {{W-|lo|ກຳມະກອນ}}, {{W-|lo|ຄົນການ}}, {{W-|lo|ຄົນເຮັດ}}, {{W-|lo|ພະນັກງານ}}
* {{Z|lt}}: {{W+|lt|darbininkas|n}}, {{W-|lt|darbininkė|m}}
* {{Z|lb}}: {{W+|lb|Aarbechter}}
* {{Z|mk}}: {{W-|mk|работник|n}}, {{W-|mk|работничка|m}}
* {{Z|ms}}: {{W+|ms|pekerja}}
* {{Z|mt}}: {{W-|mt|ħaddiem|n}}, {{W-|mt|ħaddiema|m}}
* {{Z|gv}}: {{W-|gv|obbree|n}}
* {{Z|mi}}: {{W-|mi|kaimahi}}
* {{Z|hu}}: {{W+|hu|munkás}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|ажилчин}}, {{W+|mn|ажилтан}}
*: Mongolî: {{W-|mn|ᠠᠵᠢᠯᠴᠢᠨ}}
* {{Z|wmw}}: {{W-|wmw|nkolakazi}}
* {{Z|nrf}}: {{W-|nrf|ouvriyi|n}}, {{W-|nrf|travailleux|n}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|arbeider|n}}, {{W-|nb|arbeidsmann|n}}
*: {{Z|nn}}: {{W+|nn|arbeidar|n}}, {{W-|nn|arbeidsmann|n}}
* {{Z|oc}}: {{W+|oc|trabalhaire|n}}, {{W+|oc|obrièr|n}}
* {{Z|ug}}: {{W+|ug|ئىشچى}}
* {{Z|uz}}: {{W+|uz|ishchi}}
* {{Z|ps}}: {{W-|ps|کارګر|n|tr=kārgar}}, {{W-|ps|کاريګر|n|tr=kārigar}}
* {{Z|pl}}: {{W+|pl|robotnik|n}}, {{W+|pl|robotnica|m}}
* {{Z|pt}}: {{W+|pt|trabalhador|n}}
* {{Z|kk}}: {{W-|kk|жұмысшы}}
* {{Z|ro}}: {{W+|ro|lucrător|n}}, {{W+|ro|lucrătoare|m}}, {{W+|ro|muncitor|n}}, {{W+|ro|muncitoare|m}}, {{W+|ro|angajat|n}}, {{W+|ro|angajată|m}}
* {{Z|ru}}: {{W-|ru|рабо́тник|n}}, {{W-|ru|рабо́тница|m}}, {{W-|ru|рабо́чий|n}}, {{W+|ru|рабочий|m|cuda=рабо́чая}}, {{W-|ru|трудя́щийся|n}}, {{W-|ru|трудя́щаяся|m}}, {{W-|ru|тру́женик|n}}, {{W-|ru|тру́женица|m}}
* {{Z|scn}}: {{W+|scn|travagghiaturi|n}}, {{W+|scn|lavureri|n}}, {{W+|scn|opiraiu|n}}, {{W+|scn|operaiu|n}}, {{W+|scn|lavuraturi|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|ра̑днӣк|n}}, {{W-|sh|ра̑дница|m}}
*: Latînî: {{W+|sh|rȃdnīk|n}}, {{W+|sh|rȃdnica|m}}
* {{Z|sk}}: {{W-|sk|robotník|n}}, {{W-|sk|robotníčka|m}}
* {{Z|sl}}: {{W+|sl|delavec|n}}, {{W-|sl|delavka|m}}
* {{Z|es}}: {{W+|es|trabajador|n}}, {{W+|es|obrero|n}}
* {{Z|sv}}: {{W+|sv|arbetare|g}}
* {{Z|cjs}}: {{W-|cjs|ишчи|tr=işçi}}
* {{Z|tg}}: {{W+|tg|коргар}}
* {{Z|tl}}: {{W-|tl|manggagawa}}, {{W-|tl|trabahador}}
* {{Z|th}}: {{W+|th|คนงาน}}, {{W+|th|กรรมกร}}, {{W+|th|พนักงาน}}, {{W+|th|ลูกจ้าง}}, {{W-|th|คนทำงาน}}, {{W-|th|ผู้ปฏิบัติงาน}}, {{W-|th|ผู้ใช้แรงงาน}}
* {{Z|tt}}: {{W+|tt|эшче}}
* {{Z|ti}}: {{W-|ti|ሰራሕተኛ}}
* {{Z|tr}}: {{W+|tr|işçi}}, {{W+|tr|emekçi}}, {{W+|tr|amele}}, {{W+|tr|faale}}
* {{Z|tk}}: {{W-|tk|işçi}}
* {{Z|udi}}: {{W-|udi|аьшбал}}, {{W-|udi|ашбал}}
* {{Z|ur}}: {{W-|ur|مَزدُور|n|tr=mazdūr}}
* {{Z|uk}}: {{W-|uk|робітни́к|n}}, {{W-|uk|робітни́ця|m}}, {{W-|uk|працівни́к|n}}, {{W-|uk|працівни́ця|m}}, {{W-|uk|трудя́щий|n}}, {{W-|uk|трудя́ща|m}}
* {{Z|vi}}: {{W+|vi|công nhân}}
* {{Z|vo}}: {{W+|vo|voban}}, {{W-|vo|hivoban}}, {{W+|vo|jivoban}}
* {{Z|km}}: {{W+|km|បុគ្គលិក}}, {{W+|km|កម្មករ}}, {{W-|km|អ្នកធ្វើការ}}
* {{Z|yi}}: {{W-|yi|אַרבעטער|n}}, {{W-|yi|אַרבעטערין|m}}
* {{Z|el}}: {{W+|el|εργάτης|n}}, {{W+|el|εργάτρια|m}}
*: {{Z|grc}}: {{W-|grc|ἐργάτης|n}}, {{W-|grc|ἐργάτις|m}}
{{werger-bin}}
:{{werger çavkanî|en|worker|beş=Ev qismê Wergerê|oldid=75875658}}
atsxr7cqre4866qo5gwzpkp4p1akwkq
Endonezya
0
4180
6865602
6719577
2026-10-23T21:12:05Z
Wikihez
7344
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +tr:serenav:Endonezya
6865602
wikitext
text/x-wiki
{{h-bnr|Endonêzya}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
[[Wêne:LocationIndonesia.svg|thumb|250px|Endonezya (sor)]]
{{serenav|ku|z=m}}
# {{f|ku|dûgel}} [[welat|Welatek]] e li [[başûr]]-[[rojhilat]]a [[parzemîn]]a [[Asya]]yê û ji çend [[girav]]an pêk tê.
==== Formên alternatîv ====
* [[Indonezya]]
* [[Îndonezya]]
==== Têkildar ====
* [[endonezyayî]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Herwiha binêre ====
* {{lîste:dûgelên Asyayê/ku}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W-|af|Indonesië}}
* {{Z|sq}}: {{W-|sq|Indonezi|m}}, {{W+|sq|Indonezia|m}}
* {{Z|de}}: {{W+|de|Indonesien|nt}}
* {{Z|am}}: {{W-|am|ኢንዶኔዥያ}}
* {{Z|aii}}: {{W-|aii|ܐܸܢܕܘܿܢܹܣܝܼܵܐ|m|tr=indonesiya}}
* {{Z|as}}: {{W-|as|ইণ্ডোনেচিয়া}}
* {{Z|ast}}: {{W+|ast|Indonesia|m}}
* {{Z|az}}: {{W+|az|İndoneziya}}
* {{Z|ba}}: {{W-|ba|Индонезия}}
* {{Z|be}}: {{W-|be|Індане́зія|m}}, {{W-|be|Інданэ́зія|m}}
* {{Z|bn}}: {{W+|bn|ইন্দোনেশিয়া}}
* {{Z|br}}: {{W-|br|Indonezia}}
* {{Z|bg}}: {{W-|bg|Индоне́зия|m}}
* {{Z|my}}: {{W-|my|အင်ဒိုနီးရှား}}
* {{Z|cs}}: {{W+|cs|Indonésie|m}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|印尼}}, {{W-|yue|印度尼西亞}}
*: {{Z|cmn}}: {{W-|cmn|印度尼西亞}}, {{W+|cmn|印尼}}
*: {{Z|nan}}: {{W-|nan|印尼|tr=Ìn-nî}}
* {{Z|da}}: {{W+|da|Indonesien|nt}}
* {{Z|dv}}: {{W-|dv|އިންޑޮނޭޝިޔާ}}
* {{Z|id}}: {{W+|id|Indonesia}}
* {{Z|ar}}: {{W-|ar|إِنْدُونِيسِيَا|m}}
*: {{Z|acw}}: {{W-|acw|إندونيسيا|m|tr=ʔindōnīsya, ʔindunīsya}}
* {{Z|hy}}: {{W+|hy|Ինդոնեզիա}}
* {{Z|eo}}: {{W+|eo|Indonezio}}
* {{Z|et}}: {{W+|et|Indoneesia}}
* {{Z|gur}}: {{W-|gur|Indonesia}}, {{W-|gur|Indo-kʋlaɩtɩnrɔ}}
* {{Z|fa}}:
*: {{Z|fa}}: {{W-|fa|اندونیزیا|tr=indônîziyâ}}
*: {{Z|fa}}: {{W+|fa|اندونزی|tr=andonezi}}
* {{Z|fi}}: {{W+|fi|Indonesia}}
* {{Z|fr}}: {{W+|fr|Indonésie|m}}
* {{Z|gl}}: {{W+|gl|Indonesia|m}}
* {{Z|ka}}: {{W+|ka|ინდონეზია}}
* {{Z|ht}}: {{W-|ht|Endonezi}}
* {{Z|haw}}: {{W-|haw|ʻInidonesia}}
* {{Z|ha}}: {{W-|ha|Indonesiya}}
* {{Z|hi}}: {{W+|hi|इंडोनेशिया|n}}
* {{Z|nl}}: {{W+|nl|Indonesië|nt}}
* {{Z|he}}: {{W-|he|אִינְדּוֹנֶזְיָה|m|tr=indonézya}}
* {{Z|io}}: {{W+|io|Indonezia}}
* {{Z|en}}: {{W+|en|Indonesia}}
* {{Z|ia}}: {{W-|ia|Indonesia}}
* {{Z|ga}}: {{W-|ga|Indinéis|m|cuda=An Indinéis}}, {{W-|ga|Poblacht na hIndinéise|m}}
* {{Z|it}}: {{W+|it|Indonesia|m}}
* {{Z|is}}: {{W+|is|Indónesía|m}}
* {{Z|ja}}: {{W+|ja|インドネシア|tr=Indoneshia}}
* {{Z|jv}}: {{W-|jv|Indonesia}}
* {{Z|kln}}: {{W-|kln|Indonesia}}
* {{Z|kam}}: {{W-|kam|Indonesia}}
* {{Z|kn}}: {{W-|kn|ಇಂಡೋನೇಷ್ಯಾ}}
* {{Z|ca}}: {{W+|ca|Indonèsia|m}}
* {{Z|qu}}: {{W-|qu|Indunisya}}
* {{Z|ky}}: {{W+|ky|Индонезия}}
* {{Z|ki}}: {{W-|ki|Indonesia}}
* {{Z|si}}: {{W-|si|ඉන්දුනීසියාව}}
* {{Z|ko}}: {{W-|ko|^인도네시아}}, {{W-|ko|인니}}
* {{Z|la}}: {{W-|la|Indonesia|m}}
* {{Z|lv}}: {{W+|lv|Indonēzija|m}}
* {{Z|lo}}: {{W-|lo|ອິນໂດເນຊີ}}, {{W-|lo|ອິນໂດນີເຊັຽ}}, {{W+|lo|ອິນໂດເນເຊຍ}}
* {{Z|li}}: {{W+|li|Indonesië}}
* {{Z|lt}}: {{W+|lt|Indonezija|m}}
* {{Z|luy}}: {{W-|luy|Indonesia}}
* {{Z|luo}}: {{W-|luo|Indonesia}}
* {{Z|mk}}: {{W+|mk|Индонезија|m}}
* {{Z|ml}}: {{W-|ml|ഇന്തോനേഷ്യ}}
* {{Z|ms}}: {{W+|ms|Indonesia}}
* {{Z|mt}}: {{W-|mt|Indonesja|m|cuda=l-Indonesja}}
* {{Z|mi}}: {{W-|mi|Initonīhia}}
* {{Z|hu}}: {{W+|hu|Indonézia}}
* {{Z|mer}}: {{W-|mer|Indonesia}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|Индонез}}
* {{Z|nv}}: {{W-|nv|Kéyah Dah Ndaaʼeełí Łání}}
* {{Z|na}}: {{W-|na|Indonesia}}
* {{Z|nds}}:
*: {{Z|nds-de}}: {{W-|nds-de|Indonesien}}
* {{Z|ne}}: {{W-|ne|इण्डोनेशिया}}
* {{Z|nrf}}: {{W-|nrf|Îndonésie|m}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|Indonesia|nt}}
*: {{Z|nn}}: {{W+|nn|Indonesia|nt}}
* {{Z|oc}}: {{W-|oc|Indonesia|m}}
* {{Z|ug}}: {{W-|ug|ھىندونېزىيە}}
* {{Z|uz}}: {{W+|uz|Indoneziya}}
* {{Z|ps}}: {{W+|ps|اندونيزيا|m|tr=endonezya}}
* {{Z|pl}}: {{W+|pl|Indonezja|m}}
* {{Z|pt}}: {{W+|pt|Indonésia|m}}
* {{Z|kk}}: {{W-|kk|Үндінезия}}
* {{Z|ro}}: {{W+|ro|Indonezia|m}}
* {{Z|ru}}: {{W-|ru|Индоне́зия|m|tr=Indonɛ́zija}}
* {{Z|se}}: {{W-|se|Indonesia}}
* {{Z|sa}}: {{W-|sa|इन्दोनेशिया}}
* {{Z|scn}}: {{W-|scn|Indunesia|m}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|Индо̀не̄зија|m}}
*: Latînî: {{W+|sh|Indònēzija|m}}
* {{Z|sk}}: {{W-|sk|Indonézia|m}}
* {{Z|sl}}: {{W+|sl|Indonezija|m}}
* {{Z|es}}: {{W+|es|Indonesia|m}}
* {{Z|su}}: {{W-|su|Indonésia}}
* {{Z|sw}}: {{W+|sw|Indonesia}}
* {{Z|sv}}: {{W+|sv|Indonesien|nt}}
* {{Z|tg}}: {{W+|tg|Индонезия}}
* {{Z|tl}}: {{W-|tl|Indonesya}}
* {{Z|ta}}: {{W+|ta|இந்தோனேசியா}}
* {{Z|th}}: {{W+|th|อินโดนีเซีย}}, {{W-|th|อิเหนา}}
* {{Z|te}}: {{W-|te|ఇండోనేషియా}}
* {{Z|tt}}: {{W-|tt|Индонезия}}
* {{Z|tet}}: {{W-|tet|indonézia}}
* {{Z|tr}}: {{W+|tr|Endonezya}}
* {{Z|tk}}: {{W+|tk|Indoneziýa}}
* {{Z|bo}}: {{W-|bo|ཨིན་རྡུ་ནི་ཤིས་ཡ}}
* {{Z|ur}}: {{W-|ur|اِنْڈونِیشِیَا|n|tr=iṇḍoneśiyā}}
* {{Z|uk}}: {{W-|uk|Індоне́зія|m}}
* {{Z|vi}}: {{W-|vi|In-đô-nê-xi-a}}, {{W+|vi|Nam Dương}}
* {{Z|vro}}: {{W-|vro|Indoneesiä}}
* {{Z|cy}}: {{W+|cy|Indonesia}}
* {{Z|km}}: {{W-|km|ឥណ្ឌូនេស៊ី}}
* {{Z|yi}}: {{W-|yi|אינדאָנעזיע}}
* {{Z|yo}}: {{W-|yo|Indonesia}}
* {{Z|el}}: {{W+|el|Ινδονησία|m}}
* {{Z|za}}: {{W-|za|Yindunizsihya}}, {{W-|za|Yinniz}}
{{werger-bin}}
:{{werger çavkanî|en|Indonesia|beş=Ev qismê Wergerê|oldid=75736452}}
== {{ziman|tr}} ==
=== Serenav ===
{{serenav|tr}}
# {{g|ku|Endonezya}}
kfn0tuoxezom7xxxpdb3sydwf85vp87
kêşe
0
7956
6865539
6539845
2026-10-23T19:47:06Z
Wikihez
7344
/* Navdêr 2 */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865539
wikitext
text/x-wiki
{{h-bnr|kese|kesê|keşe|keşê|kêşê|kęse}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr 1 ===
{{navdêr|ku|z=m}}
# [[nakokî]], [[nelihevî]], [[dubendî]]
# [[pirsgirêk]], [[arîşe]], [[problem]], [[mişkîle]], [[krîz]], [[qeyran]], [[dîlema]], [[pirs]], [[doz]], [[bela]], [[nexweşî]]
#* {{jêder|ku|jêgirtin=Ev '''kêşe''' jî bi rêya civîneke kurdî-kurdî çareser dibin.|Hêvîdar Ehmed: ''Partiyên Rojava Konferanseke kurdî-kurdî amade dikin'', Rûdaw, Hj.106, 28/07/2011, r.2}}
#* {{J:Mîran Janbar:Qolyeya Cecileê|r=76|jêgirtin=Bi dû re dor hatibû derpiyê spîboz. Ew dibû '''kêşe''' li ber mistadanê. Kêdika bi her du destan ew jî girtibû û şiqitandibû ber bi jêrê ve.}}
#* {{jêder-pirtûk|ku|jêgirtin=Di encamê de, dîmena wêjeya Kurdî li Sûriyê di gelek '''kêşe''' û tengavan de ye...|nivîskar=Dilawarê Zengî|sernav=Bînahî|weşanxane=Çapxana Emîral|sal=2010|rûpel=77}}
# [[halet]], [[hal]], [[bûyer]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Dijmane ====
{{kol3|ku
|çare
}}
==== Jê ====
{{kol3|ku
|bêkêşe
|bêkêşetî
|bêkêşeyî
|bikêşe
|bikêşetî
|bikêşeyî
|kêşedar
|kêşedarî
|kêşehez
|kêşehezî
|kêşekirr
|kêşekirr
|kêşekirrî
|kêşesaz
|kêşesazî
|kêşeyar
|kêşeyarî
}}
==== Etîmolojî ====
Ji [[kêşan]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Konflikt|m}}, {{W|de|Streit|m}}, {{W+|de|Widerstreit}}, {{W|de|Kümmernis}}, {{W|de|Mucke|f}}, {{W|de|Problem|m}}, {{W|de|Sache|f}}, {{W|de|Sackgasse|f}}, {{W|de|Schererei}}
* {{Z|eu}}: {{W+|eu|gatazka}}
* {{Z|cs}}: {{W+|cs|konflikt}}, {{W+|cs|spor}}
* {{Z|ar}}: {{W|ar|مشاكل|guman=erê}}, {{W|ar|لوح الركمجة|guman=erê}}
* {{Z|eo}}: {{W+|eo|konflikto}}
* {{Z|fa}}: {{W+|fa|مشکل}}, {{W+|fa|مسا له}}
* {{Z|fi}}: {{W+|fi|ristiriita}}
* {{Z|fr}}: {{W+|fr|conflit}}
* {{Z|fy}}: {{W+|fy|konflikt}}
* {{Z|nl}}: {{W+|nl|conflict}}
* {{Z|io}}: {{W+|io|konflikto}}
* {{Z|en}}: {{W+|en|conflict}}, {{W+|en|clash}}, {{W+|en|confrontation}}, {{W+|en|incompatabilitiy}}, {{W+|en|matter}}, {{W+|en|case}}
* {{Z|it}}: {{W+|it|conflitto}}
* {{Z|ko}}: {{W+|ko|분쟁}}
* {{Z|pap}}: {{W+|pap|konflikto}}
* {{Z|pt}}: {{W+|pt|conflito}}
* {{Z|es}}: {{W+|es|conflicto}}
* {{Z|sv}}: {{W+|sv|konflikt}}
* {{Z|tr}}: {{W+|tr|anlaşmazlık}}, {{W+|tr|sorun}}, {{W+|tr|dara}}, {{W+|tr|problem}}
{{werger-bin}}
=== Navdêr 2 ===
{{navdêr|ku}}
# [[navgîn]]eke ser [[berf]]ê
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=eêkş|keşê|êşek}}
av8dwbk3qs4tvfrlt2mkx64aaa5r2c0
zindan
0
8603
6865542
6729027
2026-10-23T19:48:54Z
Wikihez
7344
Kurmancî/Navdêr: mane hate guhartin, beşa «Resim» hate nûkirin (DefSideBoxes)
6865542
wikitext
text/x-wiki
{{h-bnr|zîndan}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/zɪnˈdɑːn/}}
* {{kîte|ku|zin|dan}}
=== Navdêr ===
[[Wêne:Cela.jpg|thumb|250px|Odeyeke '''zindanekê'''.]]
{{navdêr|ku|z=m}}
# [[cih|Cih]]ê ku kesên [[ceza]]kirî lê dimînin.
#* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Nesîmê sinbil û sêvan</br>Seher da hebs û '''zindanê'''</br>Vebû qufla me bêmifte}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|زندان}}
==== Formên alternatîv ====
* {{g|ku|zîndan}}
==== Hevmane ====
{{kol3|ku
|dîlgeh
|dîlxane
|esîrgeh
|esîrxane
|exsîr
|exsîrgeh
|exsîrxane
|êsîrgeh
|êsîrxane
|êxsîrgeh
|êxsîrxane
|girtîgeh
|girtîxane
|hefs
|pozxane
}}
==== Jê ====
{{kol3|ku|cure=Jê
|zindanî
|zindan kirin
|zindankirin
|zindankirî
|zindan kirin<crz:lêker>
|zindankirin<crz:navdêr>
}}
==== Têkildar ====
{{kol3|ku|cure=Têkildar
|dîl
|esîr
|girtî
|hefsî
|şkence
|tazîb
}}
==== Etîmolojî ====
Ji {{ji|ira}}, [[hevreh]]a farisî {{g|fa|زندان}} (zindan), pehlewî ''zêndan'' (1. çekxane, sîlehxane 2. zindan) ji ''zên'' ([[çek]], [[sîleh]]) + [[-dan]] ([[-dank]]) anku "cihê sîlehan/çekan"). Wek "zindan" ketiye tirkî jî.
==== Werger ====
{{werger-ser}}
* {{Z|ab}}: {{W-|ab|абахҭа}}
* {{Z|adz}}: {{W-|adz|karabus}}
* {{Z|af}}: {{W+|af|tronk}}, {{W+|af|gevangenis}}
* {{Z|sq}}: {{W+|sq|burg|n}}
* {{Z|de}}: {{W+|de|Gefängnis|nt}}, {{W+|de|Knast|n}}, {{W+|de|Kerker|n}}, {{W+|de|Zuchthaus|nt}}
* {{Z|alt}}: {{W-|alt|тӱрме}}
* {{Z|am}}: {{W-|am|ከርቼሌ}}, {{W-|am|ዘብጥያ}}
* {{Z|arc}}: {{W-|arc|ܚܒܘܫܝܐ}}
* {{Z|rup}}: {{W-|rup|ncljisoari|m}}, {{W-|rup|hapsi}}, {{W-|rup|hãpsani}}, {{W-|rup|filichii}}, {{W-|rup|zundani}}, {{W-|rup|pudrumi}}
* {{Z|ast}}: {{W-|ast|cárcel|m}}, {{W-|ast|prisión|m}}
* {{Z|av}}: {{W-|av|турма}}
* {{Z|az}}: {{W-|az|türmə}}, {{W-|az|həbsxana}}
* {{Z|eu}}: {{W-|eu|espetxe}}
* {{Z|ba}}: {{W-|ba|төрмә}}
* {{Z|be}}: {{W-|be|турма́|m}}, {{W-|be|цямні́ца|m}}, {{W-|be|астро́г|n}}
* {{Z|bn}}: {{W-|bn|কারাগার}}
* {{Z|br}}: {{W+|br|prizon|n}}
* {{Z|bg}}: {{W+|bg|затво́р|n}}, {{W+|bg|дранго́лник|n}}, {{W+|bg|тъмни́ца|m}}, {{W+|bg|тюрма́|m}}, {{W+|bg|занда́н|n}}
* {{Z|my}}: {{W+|my|အကျဉ်းထောင်}}, {{W+|my|ထောင်}}
* {{Z|ce}}: {{W-|ce|набахти}}
* {{Z|cs}}: {{W+|cs|vězení|nt}}, {{W+|cs|žalář|n}}, {{W+|cs|věznice|m}}
* {{Z|chr}}: {{W-|chr|ᏗᏓᏍᏚᏗ}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|監獄|tr=gaam1 juk6}}, {{W-|yue|牢獄|tr=lou4 juk6}}, {{W-|yue|監牢|tr=gaam1 lou4}}
*: {{Z|dng}}: {{W-|dng|йүҗян}}, {{W-|dng|банфонзы}}
*: {{Z|hak}}: {{W-|hak|監獄|tr=kâm-ngiu̍k}}, {{W-|hak|監牢|tr=kâm-lò}}, {{W-|hak|囹仔|tr=lông-é}}
*: {{Z|cmn}}: {{W+|cmn|監獄|tr=jiānyù}}, {{W+|cmn|牢獄|tr=láoyù}}, {{W+|cmn|監牢|tr=jiānláo}}, {{W+|cmn|班房|tr=bānfáng}}
*: {{Z|nan}}: {{W-|nan|監牢|tr=kaⁿ-lô}}, {{W+|nan|監獄|tr=kaⁿ-ga̍k}}, {{W-|nan|櫳仔|tr=lông-á}}, {{W-|nan|櫳仔內|tr=lông-á-lāi}}
*: {{Z|wuu}}: {{W-|wuu|監獄}}
* {{Z|da}}: {{W+|da|fængsel|nt}}
* {{Z|id}}: {{W+|id|penjara}}, {{W+|id|bui}}
* {{Z|ar}}: {{W+|ar|سِجْن|n}}
*: {{Z|arz}}: {{W-|arz|سجن|n|tr=sign}}
*: {{Z|acw}}: {{W-|acw|سِجِن|n|tr=sijin}}
*: {{Z|ary}}: {{W-|ary|حبْس|n|tr=ḥəbs}}
*: {{Z|ajp}}: {{W-|ajp|سِجِن|n|tr=síjen}}
* {{Z|hy}}: {{W+|hy|բանտ}}
* {{Z|eo}}: {{W+|eo|malliberejo}}, {{W+|eo|prizono}}
* {{Z|et}}: {{W+|et|vangla}}
* {{Z|gur}}: {{W-|gur|yʋ'a deem}}
* {{Z|fa}}: {{W+|fa|زندان|tr=zendân}}
* {{Z|fo}}: {{W-|fo|fongsul|nt}}, {{W-|fo|fangahús|nt}}, {{W-|fo|gegl|nt}}
* {{Z|fi}}: {{W+|fi|vankila}}, {{W+|fi|tyrmä}}
* {{Z|fr}}: {{W+|fr|prison|m}}, {{W+|fr|cabane|m}}, {{W+|fr|geôle|m}}, {{W+|fr|pénitencier|n}}
*: {{Z|fro}}: {{W-|fro|prison|m}}
* {{Z|fy}}: {{W-|fy|finzenis}}
* {{Z|gd}}: {{W-|gd|prìosan|n}}
* {{Z|gl}}: {{W-|gl|prisión|m}}, {{W-|gl|cárcere|n}}, {{W+|gl|cadea|m}}, {{W+|gl|trenla|m}}
* {{Z|gu}}: {{W-|gu|તુરંગ}}
* {{Z|ka}}: {{W-|ka|ციხე}}, {{W-|ka|საპატიმრო}}
* {{Z|hi}}: {{W+|hi|कारागार|n}}, {{W+|hi|बन्दीघर|n}}, {{W+|hi|जेल|n}}, {{W-|hi|क़ैदख़ाना|n}}
* {{Z|hil}}: {{W-|hil|bilanggoan}}
* {{Z|nl}}: {{W+|nl|gevangenis|m}}, {{W+|nl|kerker|n}}
* {{Z|he}}: {{W+|he|כֶּלֶא|n|tr=kéle}}, {{W+|he|בֵּית כֶּלֶא|n|tr=beit kéle}}, {{W+|he|בית סוהר|n|tr=beit sóhar|cuda=בֵּית סֹהַר}}, {{W-|he|בֵּית הָאֲסוּרִים|n|tr=beit haasurím}}
* {{Z|io}}: {{W+|io|karcero}}
* {{Z|ilo}}: {{W-|ilo|pagbaludan}}
* {{Z|ang}}: {{W-|ang|cweartern|nt}}, {{W-|ang|carcern|nt}}
* {{Z|en}}: {{W+|en|prison}}
* {{Z|ga}}: {{W+|ga|príosún|n}}, {{W-|ga|carcair|m}}
* {{Z|it}}: {{W+|it|prigione|m}}, {{W+|it|carcere|n}}, {{W+|it|fresco|n}}
* {{Z|is}}: {{W+|is|fangelsi|nt}}
* {{Z|ja}}: {{W+|ja|監獄|tr=かんごく, kangoku}}, {{W+|ja|刑務所|tr=けいむしょ, keimusho}}, {{W+|ja|牢屋|tr=ろうや, rōya}}, {{W+|ja|牢|tr=ろう, rō}}
* {{Z|jra}}: {{W-|jra|sang krư̆}}, {{W-|jra|sang mơnă}}
* {{Z|kl}}: {{W-|kl|parnaarussivik}}
* {{Z|kn}}: {{W+|kn|ಜೈಲು}}
* {{Z|ca}}: {{W+|ca|presó|m}}, {{W+|ca|càrcer|n}}, {{W-|ca|presidi|n}}
* {{Z|qu}}: {{W-|qu|laqha wasi}}
* {{Z|ky}}: {{W+|ky|түрмө}}, {{W+|ky|абак}}
* {{Z|ktu}}: {{W-|ktu|boloko}}
* {{Z|si}}: {{W-|si|සිරගෙදර}}
* {{Z|ko}}: {{W+|ko|교도소}}, {{W+|ko|교화소}}, {{W+|ko|감옥}}, {{W-|ko|형무소}}
* {{Z|kw}}: {{W-|kw|prison|n}}
* {{Z|co}}: {{W-|co|prigione}}, {{W-|co|prigiò}}, {{W-|co|prighjò}}, {{W-|co|carcere}}, {{W-|co|carcera}}
* {{Z|la}}: {{W-|la|carcer|n}}
* {{Z|lv}}: {{W-|lv|cietums|n}}
* {{Z|lo}}: {{W+|lo|ຄຸກ}}, {{W-|lo|ເຮືອນຈຳກາງ}}, {{W-|lo|ພັນທະນາຄານ}}, {{W-|lo|ຕະລາງ}}, {{W-|lo|ໂຮງຣາຊທັນ}}
* {{Z|lij}}: {{W-|lij|prexon|m}}
* {{Z|lt}}: {{W-|lt|kalėjimas|n}}
* {{Z|liv}}: {{W-|liv|vizākuodā}}
* {{Z|lmo}}: {{W-|lmo|presón|n}}
* {{Z|lb}}: {{W+|lb|Prisong|n}}
* {{Z|mk}}: {{W-|mk|затвор|n}}, {{W-|mk|зандана|m}}
* {{Z|ml}}: {{W+|ml|കാരാഗൃഹം}}, {{W-|ml|ജയില്}}, {{W+|ml|തുറുങ്ക്}}
* {{Z|ms}}: {{W+|ms|penjara}}
* {{Z|mt}}: {{W-|mt|ħabs|n}}
* {{Z|gv}}: {{W-|gv|pryssoon}}
* {{Z|mi}}: {{W-|mi|whare herehere|}}
* {{Z|mr}}: {{W+|mr|तुरुंग}}
* {{Z|hu}}: {{W+|hu|börtön}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|шорон}}, {{W+|mn|гяндан}}, {{W-|mn|хар гэр}}
* {{Z|mos}}: {{W-|mos|bãens-roogo}}, {{W-|mos|bi-bees roogo}}
* {{Z|nv}}: {{W-|nv|awáalya}}
* {{Z|ne}}: {{W-|ne|थाना}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|fengsel|nt}}
*: {{Z|nn}}: {{W-|nn|fengsel|nt}}
* {{Z|oj}}: {{W-|oj|gibaakwa'odiiwigamig}}
* {{Z|ryu}}: {{W-|ryu|監獄|tr=かんぐく, kanguku}}, {{W-|ryu|牢屋|tr=るーや, rūya}}, {{W-|ryu|牢|tr=るー, rū}}
* {{Z|oc}}: {{W+|oc|preson|m}}
* {{Z|om}}: {{W-|om|[[mana hidhaa]]}}
* {{Z|os}}: {{W-|os|ахӕстон}}
* {{Z|ug}}: {{W-|ug|تۈرمە}}, {{W-|ug|قاماقخانا}}
* {{Z|uz}}: {{W+|uz|turma}}, {{W+|uz|qamoqxona}}
* {{Z|ps}}: {{W+|ps|زندان|n|tr=zendãn}}, {{W+|ps|اړتون|n|tr=aṛtǔn}}, {{W-|ps|بنديخانه|m|tr=bandixāná}}, {{W+|ps|جېل|n|tr=ǰel}}, {{W-|ps|جېلخانه|m|tr=ǰelxāná}}, {{W+|ps|قيدخانه|m|tr=qaydxāná}}, {{W-|ps|محبس|n|tr=mahbás}}
* {{Z|pms}}: {{W-|pms|përzon|m}}
* {{Z|pdt}}: {{W-|pdt|Kjarkja|n}}, {{W-|pdt|Jefenkjniss|nt}}
* {{Z|pl}}: {{W+|pl|więzienie|nt}}, {{W+|pl|pierdel|n}}
* {{Z|pt}}: {{W+|pt|prisão|m}}, {{W+|pt|cadeia|m}}, {{W+|pt|cárcere|n}}, {{W+|pt|xadrez|n}}, {{W+|pt|xilindró|n}}
* {{Z|pa}}: {{W-|pa|ਜੇਲ੍ਹ|m}}, {{W-|pa|ਬੰਦੀਖਾਨਾ|n}}, {{W-|pa|ਬੰਦੀਖ਼ਾਨਾ|n}}, {{W-|pa|ਕੈਦਖਾਨਾ|n}}, {{W-|pa|ਕੈਦਖ਼ਾਨਾ|n}}
* {{Z|kk}}: {{W-|kk|түрме}}, {{W-|kk|абақты}}
* {{Z|cop}}: {{W-|cop|ϣⲧⲉⲕⲟ|n}}, {{W-|cop|ⲕⲉⲣⲟⲩⲥⲓⲁ|m}}
* {{Z|rgn}}: {{W-|rgn|parṣôn|m}}
* {{Z|rm}}: {{W-|rm|praschun|m}}
* {{Z|ro}}: {{W+|ro|închisoare}}, {{W+|ro|pușcărie}}, {{W+|ro|temniță}}
* {{Z|ru}}: {{W+|ru|тюрьма́|m}}, {{W+|ru|темни́ца|m}}, {{W+|ru|остро́г|n}}, {{W+|ru|куту́зка|m}}, {{W+|ru|катала́жка|m}}, {{W+|ru|тюря́га|m}}, {{W+|ru|зо́на|m}}, {{W+|ru|казема́т|n}}
* {{Z|se}}: {{W-|se|fáŋgal}}, {{W-|se|ladni}}
* {{Z|sa}}: {{W+|sa|कारागृह|nt}}, {{W+|sa|कारागार|nt}}
* {{Z|ceb}}: {{W-|ceb|bilanggoan}}, {{W-|ceb|prisohan}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|за́твор|n}}, {{W-|sh|та̀мница|m}}
*: Latînî: {{W+|sh|zátvor|n}}, {{W+|sh|tàmnica|n}}
* {{Z|sco}}: {{W-|sco|preeson}}
* {{Z|cu}}:
*: Kirîlî: {{W-|cu|тьмьница|m}}, {{W-|cu|острогъ|n}}
* {{Z|orv}}: {{W-|orv|тюрма|m}}, {{W-|orv|тьмьница|m}}, {{W-|orv|острогъ|n}}
* {{Z|sk}}: {{W+|sk|väznica|m}}, {{W-|sk|väzenie|nt}}, {{W+|sk|žalár|n}}
* {{Z|sl}}: {{W+|sl|zapor|n}}, {{W-|sl|ječa|m}}
* Sorbî:
*: {{Z|dsb}}: {{W-|dsb|popajźeństwo|nt}}
*: {{Z|hsb}}: {{W-|hsb|jastwo}}
* {{Z|es}}: {{W+|es|cárcel|m}}, {{W+|es|prisión|m}}, {{W+|es|penitenciaría|m}}, {{W+|es|chirona|m}}, {{W+|es|trullo}}, {{W+|es|talego}}
* {{Z|sva}}: {{W-|sva|დჷლიგ}}
* {{Z|sw}}: {{W+|sw|jela}}, {{W+|sw|gereza|c5|c6}}
* {{Z|sv}}: {{W+|sv|fängelse|nt}}
* {{Z|tg}}: {{W+|tg|зиндон}}
* {{Z|tl}}: {{W-|tl|bilangguan}}, {{W-|tl|piitan}}
* {{Z|ta}}: {{W+|ta|சிறை}}, {{W+|ta|சிறைச்சாலை}}
* {{Z|th}}: {{W+|th|คุก}}, {{W+|th|กรงขัง}}, {{W+|th|เรือนจำ}}, {{W-|th|พันธนาคาร}}, {{W+|th|ทัณฑสถาน}}
* {{Z|te}}: {{W+|te|జైలు}}, {{W+|te|కారాగారం}}
* {{Z|tt}}: {{W+|tt|төрмә}}
* {{Z|ti}}: {{W-|ti|ቤት ማእሰርቲ}}
* {{Z|tr}}: {{W+|tr|hapis}}, {{W+|tr|hapishane}}, {{W+|tr|cezaevi}}
* {{Z|tk}}: {{W-|tk|türme}}
* {{Z|bo}}: {{W-|bo|བཙོན་ཁང}}
* {{Z|tpi}}: {{W-|tpi|kalabus}}
* {{Z|txb}}: {{W-|txb|prautke}}
* {{Z|tyv}}: {{W-|tyv|кара-бажың}}
* {{Z|ur}}: {{W-|ur|کاراگار|n|tr=kārāgār}}, {{W-|ur|بندیگھر|n|tr=bandīghar}}, {{W-|ur|جیل|n|tr=jēl}}, {{W-|ur|قیدخانہ|n|tr=qaidxānā}}
* {{Z|uk}}: {{W+|uk|в'язни́ця|m}}, {{W-|uk|тюрма́|m}}, {{W-|uk|темни́ця|m}}, {{W+|uk|остро́г|n}}
* {{Z|vec}}: {{W-|vec|presón|m}}
* {{Z|vi}}: {{W+|vi|tù}}, {{W+|vi|nhà tù}}
* {{Z|vo}}: {{W+|vo|fanäböp}}
* {{Z|wa}}: {{W+|wa|prijhon|n|m}}
* {{Z|cy}}: {{W+|cy|carchar|n}}
* {{Z|km}}: {{W+|km|គុក}}, {{W+|km|ពន្ធនាគារ}}
* {{Z|yi}}: {{W-|yi|טורמע|m}}, {{W-|yi|תּפֿיסה|m|tr=tfise}}
* {{Z|yoi}}: {{W-|yoi|牢屋|tr=どぅや, duya}}
* {{Z|el}}: {{W+|el|φυλακή|m}}, {{W+|el|δεσμωτήριο|nt}}
*: Kevn: {{W-|grc|δεσμωτήριον|nt}}, {{W-|grc|εἱρκτή|m}}
* {{Z|za}}: {{W-|za|banfuengz}}, {{W-|za|gam}}, {{W-|za|lauz}}, {{W-|za|fuengz}}
{{werger-bin}}
:{{werger çavkanî|en|prison|beş=Ev qismê Wergerê|oldid=75731551}}
== {{ziman|az}} ==
=== Navdêr ===
{{navdêr|az}}
# {{mijar|az|amûr}} {{g|ku|sindan}}
=== Çavkanî ===
{{rûpela wergerê|az|sindan}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[zindan]], [[girtîxane]], [[hefs]]
tfirz06a2oa78d3bwvxi9sxkrxxf6p6
6865603
6865542
2026-10-23T21:12:07Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6865603
wikitext
text/x-wiki
{{h-bnr|zîndan}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/zɪnˈdɑːn/}}
* {{kîte|ku|zin|dan}}
=== Navdêr ===
[[Wêne:Cela.jpg|thumb|250px|Odeyeke '''zindanekê'''.]]
{{navdêr|ku|z=m}}
# [[cih|Cihê]] ku kesên [[ceza]]kirî lê dimînin.
#* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Nesîmê sinbil û sêvan</br>Seher da hebs û '''zindanê'''</br>Vebû qufla me bêmifte}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|زندان}}
==== Formên alternatîv ====
* {{g|ku|zîndan}}
==== Hevmane ====
{{kol3|ku
|dîlgeh
|dîlxane
|esîrgeh
|esîrxane
|exsîr
|exsîrgeh
|exsîrxane
|êsîrgeh
|êsîrxane
|êxsîrgeh
|êxsîrxane
|girtîgeh
|girtîxane
|hefs
|pozxane
}}
==== Jê ====
{{kol3|ku|cure=Jê
|zindanî
|zindan kirin
|zindankirin
|zindankirî
|zindan kirin<crz:lêker>
|zindankirin<crz:navdêr>
}}
==== Têkildar ====
{{kol3|ku|cure=Têkildar
|dîl
|esîr
|girtî
|hefsî
|şkence
|tazîb
}}
==== Etîmolojî ====
Ji {{ji|ira}}, [[hevreh]]a farisî {{g|fa|زندان}} (zindan), pehlewî ''zêndan'' (1. çekxane, sîlehxane 2. zindan) ji ''zên'' ([[çek]], [[sîleh]]) + [[-dan]] ([[-dank]]) anku "cihê sîlehan/çekan"). Wek "zindan" ketiye tirkî jî.
==== Werger ====
{{werger-ser}}
* {{Z|ab}}: {{W-|ab|абахҭа}}
* {{Z|adz}}: {{W-|adz|karabus}}
* {{Z|af}}: {{W+|af|tronk}}, {{W+|af|gevangenis}}
* {{Z|sq}}: {{W+|sq|burg|n}}
* {{Z|de}}: {{W+|de|Gefängnis|nt}}, {{W+|de|Knast|n}}, {{W+|de|Kerker|n}}, {{W+|de|Zuchthaus|nt}}
* {{Z|alt}}: {{W-|alt|тӱрме}}
* {{Z|am}}: {{W-|am|ከርቼሌ}}, {{W-|am|ዘብጥያ}}
* {{Z|arc}}: {{W-|arc|ܚܒܘܫܝܐ}}
* {{Z|rup}}: {{W-|rup|ncljisoari|m}}, {{W-|rup|hapsi}}, {{W-|rup|hãpsani}}, {{W-|rup|filichii}}, {{W-|rup|zundani}}, {{W-|rup|pudrumi}}
* {{Z|ast}}: {{W-|ast|cárcel|m}}, {{W-|ast|prisión|m}}
* {{Z|av}}: {{W-|av|турма}}
* {{Z|az}}: {{W-|az|türmə}}, {{W-|az|həbsxana}}
* {{Z|eu}}: {{W-|eu|espetxe}}
* {{Z|ba}}: {{W-|ba|төрмә}}
* {{Z|be}}: {{W-|be|турма́|m}}, {{W-|be|цямні́ца|m}}, {{W-|be|астро́г|n}}
* {{Z|bn}}: {{W-|bn|কারাগার}}
* {{Z|br}}: {{W+|br|prizon|n}}
* {{Z|bg}}: {{W+|bg|затво́р|n}}, {{W+|bg|дранго́лник|n}}, {{W+|bg|тъмни́ца|m}}, {{W+|bg|тюрма́|m}}, {{W+|bg|занда́н|n}}
* {{Z|my}}: {{W+|my|အကျဉ်းထောင်}}, {{W+|my|ထောင်}}
* {{Z|ce}}: {{W-|ce|набахти}}
* {{Z|cs}}: {{W+|cs|vězení|nt}}, {{W+|cs|žalář|n}}, {{W+|cs|věznice|m}}
* {{Z|chr}}: {{W-|chr|ᏗᏓᏍᏚᏗ}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|監獄|tr=gaam1 juk6}}, {{W-|yue|牢獄|tr=lou4 juk6}}, {{W-|yue|監牢|tr=gaam1 lou4}}
*: {{Z|dng}}: {{W-|dng|йүҗян}}, {{W-|dng|банфонзы}}
*: {{Z|hak}}: {{W-|hak|監獄|tr=kâm-ngiu̍k}}, {{W-|hak|監牢|tr=kâm-lò}}, {{W-|hak|囹仔|tr=lông-é}}
*: {{Z|cmn}}: {{W+|cmn|監獄|tr=jiānyù}}, {{W+|cmn|牢獄|tr=láoyù}}, {{W+|cmn|監牢|tr=jiānláo}}, {{W+|cmn|班房|tr=bānfáng}}
*: {{Z|nan}}: {{W-|nan|監牢|tr=kaⁿ-lô}}, {{W+|nan|監獄|tr=kaⁿ-ga̍k}}, {{W-|nan|櫳仔|tr=lông-á}}, {{W-|nan|櫳仔內|tr=lông-á-lāi}}
*: {{Z|wuu}}: {{W-|wuu|監獄}}
* {{Z|da}}: {{W+|da|fængsel|nt}}
* {{Z|id}}: {{W+|id|penjara}}, {{W+|id|bui}}
* {{Z|ar}}: {{W+|ar|سِجْن|n}}
*: {{Z|arz}}: {{W-|arz|سجن|n|tr=sign}}
*: {{Z|acw}}: {{W-|acw|سِجِن|n|tr=sijin}}
*: {{Z|ary}}: {{W-|ary|حبْس|n|tr=ḥəbs}}
*: {{Z|ajp}}: {{W-|ajp|سِجِن|n|tr=síjen}}
* {{Z|hy}}: {{W+|hy|բանտ}}
* {{Z|eo}}: {{W+|eo|malliberejo}}, {{W+|eo|prizono}}
* {{Z|et}}: {{W+|et|vangla}}
* {{Z|gur}}: {{W-|gur|yʋ'a deem}}
* {{Z|fa}}: {{W+|fa|زندان|tr=zendân}}
* {{Z|fo}}: {{W-|fo|fongsul|nt}}, {{W-|fo|fangahús|nt}}, {{W-|fo|gegl|nt}}
* {{Z|fi}}: {{W+|fi|vankila}}, {{W+|fi|tyrmä}}
* {{Z|fr}}: {{W+|fr|prison|m}}, {{W+|fr|cabane|m}}, {{W+|fr|geôle|m}}, {{W+|fr|pénitencier|n}}
*: {{Z|fro}}: {{W-|fro|prison|m}}
* {{Z|fy}}: {{W-|fy|finzenis}}
* {{Z|gd}}: {{W-|gd|prìosan|n}}
* {{Z|gl}}: {{W-|gl|prisión|m}}, {{W-|gl|cárcere|n}}, {{W+|gl|cadea|m}}, {{W+|gl|trenla|m}}
* {{Z|gu}}: {{W-|gu|તુરંગ}}
* {{Z|ka}}: {{W-|ka|ციხე}}, {{W-|ka|საპატიმრო}}
* {{Z|hi}}: {{W+|hi|कारागार|n}}, {{W+|hi|बन्दीघर|n}}, {{W+|hi|जेल|n}}, {{W-|hi|क़ैदख़ाना|n}}
* {{Z|hil}}: {{W-|hil|bilanggoan}}
* {{Z|nl}}: {{W+|nl|gevangenis|m}}, {{W+|nl|kerker|n}}
* {{Z|he}}: {{W+|he|כֶּלֶא|n|tr=kéle}}, {{W+|he|בֵּית כֶּלֶא|n|tr=beit kéle}}, {{W+|he|בית סוהר|n|tr=beit sóhar|cuda=בֵּית סֹהַר}}, {{W-|he|בֵּית הָאֲסוּרִים|n|tr=beit haasurím}}
* {{Z|io}}: {{W+|io|karcero}}
* {{Z|ilo}}: {{W-|ilo|pagbaludan}}
* {{Z|ang}}: {{W-|ang|cweartern|nt}}, {{W-|ang|carcern|nt}}
* {{Z|en}}: {{W+|en|prison}}
* {{Z|ga}}: {{W+|ga|príosún|n}}, {{W-|ga|carcair|m}}
* {{Z|it}}: {{W+|it|prigione|m}}, {{W+|it|carcere|n}}, {{W+|it|fresco|n}}
* {{Z|is}}: {{W+|is|fangelsi|nt}}
* {{Z|ja}}: {{W+|ja|監獄|tr=かんごく, kangoku}}, {{W+|ja|刑務所|tr=けいむしょ, keimusho}}, {{W+|ja|牢屋|tr=ろうや, rōya}}, {{W+|ja|牢|tr=ろう, rō}}
* {{Z|jra}}: {{W-|jra|sang krư̆}}, {{W-|jra|sang mơnă}}
* {{Z|kl}}: {{W-|kl|parnaarussivik}}
* {{Z|kn}}: {{W+|kn|ಜೈಲು}}
* {{Z|ca}}: {{W+|ca|presó|m}}, {{W+|ca|càrcer|n}}, {{W-|ca|presidi|n}}
* {{Z|qu}}: {{W-|qu|laqha wasi}}
* {{Z|ky}}: {{W+|ky|түрмө}}, {{W+|ky|абак}}
* {{Z|ktu}}: {{W-|ktu|boloko}}
* {{Z|si}}: {{W-|si|සිරගෙදර}}
* {{Z|ko}}: {{W+|ko|교도소}}, {{W+|ko|교화소}}, {{W+|ko|감옥}}, {{W-|ko|형무소}}
* {{Z|kw}}: {{W-|kw|prison|n}}
* {{Z|co}}: {{W-|co|prigione}}, {{W-|co|prigiò}}, {{W-|co|prighjò}}, {{W-|co|carcere}}, {{W-|co|carcera}}
* {{Z|la}}: {{W-|la|carcer|n}}
* {{Z|lv}}: {{W-|lv|cietums|n}}
* {{Z|lo}}: {{W+|lo|ຄຸກ}}, {{W-|lo|ເຮືອນຈຳກາງ}}, {{W-|lo|ພັນທະນາຄານ}}, {{W-|lo|ຕະລາງ}}, {{W-|lo|ໂຮງຣາຊທັນ}}
* {{Z|lij}}: {{W-|lij|prexon|m}}
* {{Z|lt}}: {{W-|lt|kalėjimas|n}}
* {{Z|liv}}: {{W-|liv|vizākuodā}}
* {{Z|lmo}}: {{W-|lmo|presón|n}}
* {{Z|lb}}: {{W+|lb|Prisong|n}}
* {{Z|mk}}: {{W-|mk|затвор|n}}, {{W-|mk|зандана|m}}
* {{Z|ml}}: {{W+|ml|കാരാഗൃഹം}}, {{W-|ml|ജയില്}}, {{W+|ml|തുറുങ്ക്}}
* {{Z|ms}}: {{W+|ms|penjara}}
* {{Z|mt}}: {{W-|mt|ħabs|n}}
* {{Z|gv}}: {{W-|gv|pryssoon}}
* {{Z|mi}}: {{W-|mi|whare herehere|}}
* {{Z|mr}}: {{W+|mr|तुरुंग}}
* {{Z|hu}}: {{W+|hu|börtön}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|шорон}}, {{W+|mn|гяндан}}, {{W-|mn|хар гэр}}
* {{Z|mos}}: {{W-|mos|bãens-roogo}}, {{W-|mos|bi-bees roogo}}
* {{Z|nv}}: {{W-|nv|awáalya}}
* {{Z|ne}}: {{W-|ne|थाना}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|fengsel|nt}}
*: {{Z|nn}}: {{W-|nn|fengsel|nt}}
* {{Z|oj}}: {{W-|oj|gibaakwa'odiiwigamig}}
* {{Z|ryu}}: {{W-|ryu|監獄|tr=かんぐく, kanguku}}, {{W-|ryu|牢屋|tr=るーや, rūya}}, {{W-|ryu|牢|tr=るー, rū}}
* {{Z|oc}}: {{W+|oc|preson|m}}
* {{Z|om}}: {{W-|om|[[mana hidhaa]]}}
* {{Z|os}}: {{W-|os|ахӕстон}}
* {{Z|ug}}: {{W-|ug|تۈرمە}}, {{W-|ug|قاماقخانا}}
* {{Z|uz}}: {{W+|uz|turma}}, {{W+|uz|qamoqxona}}
* {{Z|ps}}: {{W+|ps|زندان|n|tr=zendãn}}, {{W+|ps|اړتون|n|tr=aṛtǔn}}, {{W-|ps|بنديخانه|m|tr=bandixāná}}, {{W+|ps|جېل|n|tr=ǰel}}, {{W-|ps|جېلخانه|m|tr=ǰelxāná}}, {{W+|ps|قيدخانه|m|tr=qaydxāná}}, {{W-|ps|محبس|n|tr=mahbás}}
* {{Z|pms}}: {{W-|pms|përzon|m}}
* {{Z|pdt}}: {{W-|pdt|Kjarkja|n}}, {{W-|pdt|Jefenkjniss|nt}}
* {{Z|pl}}: {{W+|pl|więzienie|nt}}, {{W+|pl|pierdel|n}}
* {{Z|pt}}: {{W+|pt|prisão|m}}, {{W+|pt|cadeia|m}}, {{W+|pt|cárcere|n}}, {{W+|pt|xadrez|n}}, {{W+|pt|xilindró|n}}
* {{Z|pa}}: {{W-|pa|ਜੇਲ੍ਹ|m}}, {{W-|pa|ਬੰਦੀਖਾਨਾ|n}}, {{W-|pa|ਬੰਦੀਖ਼ਾਨਾ|n}}, {{W-|pa|ਕੈਦਖਾਨਾ|n}}, {{W-|pa|ਕੈਦਖ਼ਾਨਾ|n}}
* {{Z|kk}}: {{W-|kk|түрме}}, {{W-|kk|абақты}}
* {{Z|cop}}: {{W-|cop|ϣⲧⲉⲕⲟ|n}}, {{W-|cop|ⲕⲉⲣⲟⲩⲥⲓⲁ|m}}
* {{Z|rgn}}: {{W-|rgn|parṣôn|m}}
* {{Z|rm}}: {{W-|rm|praschun|m}}
* {{Z|ro}}: {{W+|ro|închisoare}}, {{W+|ro|pușcărie}}, {{W+|ro|temniță}}
* {{Z|ru}}: {{W+|ru|тюрьма́|m}}, {{W+|ru|темни́ца|m}}, {{W+|ru|остро́г|n}}, {{W+|ru|куту́зка|m}}, {{W+|ru|катала́жка|m}}, {{W+|ru|тюря́га|m}}, {{W+|ru|зо́на|m}}, {{W+|ru|казема́т|n}}
* {{Z|se}}: {{W-|se|fáŋgal}}, {{W-|se|ladni}}
* {{Z|sa}}: {{W+|sa|कारागृह|nt}}, {{W+|sa|कारागार|nt}}
* {{Z|ceb}}: {{W-|ceb|bilanggoan}}, {{W-|ceb|prisohan}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|за́твор|n}}, {{W-|sh|та̀мница|m}}
*: Latînî: {{W+|sh|zátvor|n}}, {{W+|sh|tàmnica|n}}
* {{Z|sco}}: {{W-|sco|preeson}}
* {{Z|cu}}:
*: Kirîlî: {{W-|cu|тьмьница|m}}, {{W-|cu|острогъ|n}}
* {{Z|orv}}: {{W-|orv|тюрма|m}}, {{W-|orv|тьмьница|m}}, {{W-|orv|острогъ|n}}
* {{Z|sk}}: {{W+|sk|väznica|m}}, {{W-|sk|väzenie|nt}}, {{W+|sk|žalár|n}}
* {{Z|sl}}: {{W+|sl|zapor|n}}, {{W-|sl|ječa|m}}
* Sorbî:
*: {{Z|dsb}}: {{W-|dsb|popajźeństwo|nt}}
*: {{Z|hsb}}: {{W-|hsb|jastwo}}
* {{Z|es}}: {{W+|es|cárcel|m}}, {{W+|es|prisión|m}}, {{W+|es|penitenciaría|m}}, {{W+|es|chirona|m}}, {{W+|es|trullo}}, {{W+|es|talego}}
* {{Z|sva}}: {{W-|sva|დჷლიგ}}
* {{Z|sw}}: {{W+|sw|jela}}, {{W+|sw|gereza|c5|c6}}
* {{Z|sv}}: {{W+|sv|fängelse|nt}}
* {{Z|tg}}: {{W+|tg|зиндон}}
* {{Z|tl}}: {{W-|tl|bilangguan}}, {{W-|tl|piitan}}
* {{Z|ta}}: {{W+|ta|சிறை}}, {{W+|ta|சிறைச்சாலை}}
* {{Z|th}}: {{W+|th|คุก}}, {{W+|th|กรงขัง}}, {{W+|th|เรือนจำ}}, {{W-|th|พันธนาคาร}}, {{W+|th|ทัณฑสถาน}}
* {{Z|te}}: {{W+|te|జైలు}}, {{W+|te|కారాగారం}}
* {{Z|tt}}: {{W+|tt|төрмә}}
* {{Z|ti}}: {{W-|ti|ቤት ማእሰርቲ}}
* {{Z|tr}}: {{W+|tr|hapis}}, {{W+|tr|hapishane}}, {{W+|tr|cezaevi}}
* {{Z|tk}}: {{W-|tk|türme}}
* {{Z|bo}}: {{W-|bo|བཙོན་ཁང}}
* {{Z|tpi}}: {{W-|tpi|kalabus}}
* {{Z|txb}}: {{W-|txb|prautke}}
* {{Z|tyv}}: {{W-|tyv|кара-бажың}}
* {{Z|ur}}: {{W-|ur|کاراگار|n|tr=kārāgār}}, {{W-|ur|بندیگھر|n|tr=bandīghar}}, {{W-|ur|جیل|n|tr=jēl}}, {{W-|ur|قیدخانہ|n|tr=qaidxānā}}
* {{Z|uk}}: {{W+|uk|в'язни́ця|m}}, {{W-|uk|тюрма́|m}}, {{W-|uk|темни́ця|m}}, {{W+|uk|остро́г|n}}
* {{Z|vec}}: {{W-|vec|presón|m}}
* {{Z|vi}}: {{W+|vi|tù}}, {{W+|vi|nhà tù}}
* {{Z|vo}}: {{W+|vo|fanäböp}}
* {{Z|wa}}: {{W+|wa|prijhon|n|m}}
* {{Z|cy}}: {{W+|cy|carchar|n}}
* {{Z|km}}: {{W+|km|គុក}}, {{W+|km|ពន្ធនាគារ}}
* {{Z|yi}}: {{W-|yi|טורמע|m}}, {{W-|yi|תּפֿיסה|m|tr=tfise}}
* {{Z|yoi}}: {{W-|yoi|牢屋|tr=どぅや, duya}}
* {{Z|el}}: {{W+|el|φυλακή|m}}, {{W+|el|δεσμωτήριο|nt}}
*: Kevn: {{W-|grc|δεσμωτήριον|nt}}, {{W-|grc|εἱρκτή|m}}
* {{Z|za}}: {{W-|za|banfuengz}}, {{W-|za|gam}}, {{W-|za|lauz}}, {{W-|za|fuengz}}
{{werger-bin}}
:{{werger çavkanî|en|prison|beş=Ev qismê Wergerê|oldid=75731551}}
== {{ziman|az}} ==
=== Navdêr ===
{{navdêr|az}}
# {{mijar|az|amûr}} {{g|ku|sindan}}
=== Çavkanî ===
{{rûpela wergerê|az|sindan}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[zindan]], [[girtîxane]], [[hefs]]
e7wv4atgrue1gi2nu6abl5ib6qbmsm1
vîn
0
8710
6865523
6540486
2026-10-23T19:18:46Z
Wikihez
7344
/* Lêker */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865523
wikitext
text/x-wiki
{{h-bnr|Vín|Vîn|vin|vín}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{kîte|ku|vîn}}
=== Lêker ===
{{ku-tewîn-reh|vîn|form=gerguhêz|niha=vê|borî=viya}}
{{lêker|ku|c=x-gh}}
# Tişta/ê mirov [[xwestin|dixwaze]], tişta/ê mirov [[hez]] dike.
#: {{mk|ku|Min '''divê''' te bibînim.|Dixwazim te bibînim.|rêz=1}}
#* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Me '''divê''' periya dilberê<br>Lê hê li uslûba berê}}
# {{i|[[nekesanî]]}} [[gerek|Gerek bûn]], [[lazim bûn]], [[pêdivî bûn]], [[pêwîst bûn]].
#: {{dijmane|ku|dibe ku|dibe<id:gengaz>}}
#: {{mk|ku|'''Divê''' em kurd bibin yek.|Lazim e kurd bibin yek.|birêz=1}}
#* {{jêder|ku|jêgirtin=— Gelî hevalan, '''divê''' ku yek ji me têkeve hundurê qesrê. Li rewşa hundur binêre û derîye mezin.|{{w|Şivan Perwer}}, ''Çiroka newrozê'', '''1990''', r.29}}
# [[jê hez kirin|Jê hez kirin]].
# xwezî anîn, xwezî bûn
#* {{J:Xelîl Duhokî:Antolojiya Çîroka Nû ya Kurmancên Başûr|jêgirtin=Min '''diviya''' ew kiçik wekî te ba... Min '''diviya''' ji ciwanya te ba... Ji giyanê te ba...}}
==== Notên bikaranînê ====
* Mîna lêkerên [[şên]] û [[dêrîn]] (bi maneya hebûn, xwedî bûn), dema niha a neyînî li hin cihan ''na-'' nîne lê ''ne-'' ye. Wek ''divê/navê'' na lê ''divê/nevê''.
* Bi ramana xwestinê, tenê bo xwestina bûyeran, rûdanan û kirina lêkeran tê karanîn; ne ji bo xwestina bireserekê. Bo bireseran {{q|ku|xwestin}} li kar e. Anku "''ez sêvekî divêm/min sêvekî viya''" nayê gotin, "''ez divêm sêvekî bixwim/ez viyam sêvekî bixwim''".
* Bi wateya ''pêwîst bûn'', bi {{q|ku|pê}} tê bikaranîn. Wek "''bi min divê''" (bi min hewce ye, bi min pêwîst dibe); gelek caran {{q|ku|bi}} tê daqurtandin. Di vê kontekstê de, kirde dibe ê/a ku hewce ye; anku "''bi min tu divêyî''", "''bi te ez divêm''"...
==== Bi alfabeyên din ====
* {{ku-ar|ڤین}}
==== Formên alternatîv ====
* {{hw|ku|viyan}}, {{hw|ku|vîyan}}
* {{hw|ku|vêtin}}
* {{hw|ku|vên}}
==== Nêzîk ====
{{kol3|ku
| armanc
| daxwaz
| doz
| hedef
| xwestek
}}
==== Jê ====
{{kol3|ku
| bêvîn
| bêvînî
| bêvînîtî
| bêvîntî
| bivîn
| bivînî
| bivînîtî
| bivîntî
| evîn
| evîndar
| evîndarî
| evîndarîtî
| evîndartî
| vîndar
| vîndarî
| vîndarîtî
| vîndartî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
Ji epayesten a pehlewî bi wateya pêwîstin
Evîn, hevîn, hewîstin, wîstin,
==== Werger ====
{{werger-ser|tişta/ê mirov dixwaze, tişta/ê mirov hez dike}}
* {{Z|fa}}: {{W+|fa|اراده}}, {{W+|fa|آرزو کردن}}, {{W-|fa|اراده کردن}}
* {{Z|ar}}: {{W+|ar|لَازِم}}
* {{Z|fr}}: {{W+|fr|vouloir}}, {{W+|fr|souhaiter}}
* {{Z|en}}: {{W+|en|want}}, {{W+|en|desire}}, {{W+|en|like}}
* {{Z|es}}: {{W+|es|querer}}, {{W+|es|gustar}}, {{W+|es|desear}}
* {{Z|tr}}: {{W+|tr|istemek}}, {{W+|tr|dilemek}}
{{werger-bin}}
{{werger-ser|gerek bûn, lazim bûn, pêdivî bûn, pêwîst bûn}}
* {{Z|de}}: {{W+|de|müssen}}, {{W+|de|sollen}}
* {{Z|fr}}: {{W+|fr|falloir}}
* {{Z|en}}: {{W+|en|have to}}, {{W+|en|must}}, to {{W+|en|need}}, to be {{W+|en|necessary}} (that)
* {{Z|tr}}: {{W+|tr|gerekmek}}
{{werger-bin}}
{{werger-ser|jê hez kirin}}
* {{Z|en}}: to {{W+|en|love}}
* {{Z|fa}}: {{W-|fa|سفید داشتن|guman=erê}}
{{werger-bin}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tişta/ê ku mirovî [[divê]], tişta/ê mirov [[xwestin|dixwaze]], tişta/ê mirov [[hez]] dike.
#: {{hevmane|ku|îrade|daxwaz}}
#* {{J:Mîran Janbar:Qolyeya Cecileê|r=75|jêgirtin=A rast, Cécileê baş bi xwe dizanibû, hewildanên ji derveyê '''vîna''' wê ew dienitand.}}
# [[hewcebûn]], gerekbûn, pêwîstbûn
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Werger ====
{{werger-ser|îrade, daxwaz}}
* {{Z|fr}}: {{W+|fr|volition}}
* {{Z|en}}: {{W+|en|will}}, {{W+|en|volition}}
* {{Z|tr}}: {{W+|tr|irade}}, {{W+|tr|istenç}}
{{werger-bin}}
{{werger-ser|xwestin, daxwazkirin}}
* {{Z|de}}: {{W+|de|Lust|m||guman=erê}}, {{W+|de|Begehr|m||guman=erê}}, {{W+|de|Begier|guman=erê}}
* {{Z|en}}: {{W+|en|desire}}
* {{Z|tr}}: {{W+|tr|isteme}}, {{W+|tr|dileme}}, {{W+|tr|heves}}, {{W+|tr|arzu}}, {{W+|tr|istek}}
{{werger-bin}}
{{werger-ser|hewcebûn}}
* {{Z|tr}}: {{W+|tr|gerekme}}, {{W+|tr|gerektirme|guman=erê}}, {{W+|tr|terettüp}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=înv|nîv}}
hpm3bstl5i0u8p60jf7sndsducnckfa
6865524
6865523
2026-10-23T19:19:07Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865524
wikitext
text/x-wiki
{{h-bnr|Vín|Vîn|vin|vín}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{kîte|ku|vîn}}
=== Lêker ===
{{ku-tewîn-reh|vîn|form=gerguhêz|niha=vê|borî=viya}}
{{lêker|ku|c=x-gh}}
# Tişta/ê mirov [[xwestin|dixwaze]], tişta/ê mirov [[hez]] dike.
#: {{mk|ku|Min '''divê''' te bibînim.|Dixwazim te bibînim.|rêz=1}}
#* {{J:Melayê Cizîrî:Dîwana Melayê Cizîrî|jêgirtin=Me '''divê''' periya dilberê<br>Lê hê li uslûba berê}}
# {{i|[[nekesanî]]}} [[gerek|Gerek bûn]], [[lazim bûn]], [[pêdivî bûn]], [[pêwîst bûn]].
#: {{dijmane|ku|dibe ku|dibe<id:gengaz>}}
#: {{mk|ku|'''Divê''' em kurd bibin yek.|Lazim e kurd bibin yek.|birêz=1}}
#* {{jêder|ku|jêgirtin=— Gelî hevalan, '''divê''' ku yek ji me têkeve hundurê qesrê. Li rewşa hundur binêre û derîye mezin.|{{w|Şivan Perwer}}, ''Çiroka newrozê'', '''1990''', r.29}}
# [[jê hez kirin|Jê hez kirin]].
# xwezî anîn, xwezî bûn
#* {{J:Xelîl Duhokî:Antolojiya Çîroka Nû ya Kurmancên Başûr|jêgirtin=Min '''diviya''' ew kiçik wekî te ba... Min '''diviya''' ji ciwanya te ba... Ji giyanê te ba...}}
==== Notên bikaranînê ====
* Mîna lêkerên [[şên]] û [[dêrîn]] (bi maneya hebûn, xwedî bûn), dema niha a neyînî li hin cihan ''na-'' nîne lê ''ne-'' ye. Wek ''divê/navê'' na lê ''divê/nevê''.
* Bi ramana xwestinê, tenê bo xwestina bûyeran, rûdanan û kirina lêkeran tê karanîn; ne ji bo xwestina bireserekê. Bo bireseran {{q|ku|xwestin}} li kar e. Anku "''ez sêvekî divêm/min sêvekî viya''" nayê gotin, "''ez divêm sêvekî bixwim/ez viyam sêvekî bixwim''".
* Bi wateya ''pêwîst bûn'', bi {{q|ku|pê}} tê bikaranîn. Wek "''bi min divê''" (bi min hewce ye, bi min pêwîst dibe); gelek caran {{q|ku|bi}} tê daqurtandin. Di vê kontekstê de, kirde dibe ê/a ku hewce ye; anku "''bi min tu divêyî''", "''bi te ez divêm''"...
==== Bi alfabeyên din ====
* {{ku-ar|ڤین}}
==== Formên alternatîv ====
* {{hw|ku|viyan}}, {{hw|ku|vîyan}}
* {{hw|ku|vêtin}}
* {{hw|ku|vên}}
==== Nêzîk ====
{{kol3|ku
| armanc
| daxwaz
| doz
| hedef
| xwestek
}}
==== Jê ====
{{kol3|ku
| bêvîn
| bêvînî
| bêvînîtî
| bêvîntî
| bivîn
| bivînî
| bivînîtî
| bivîntî
| evîn
| evîndar
| evîndarî
| evîndarîtî
| evîndartî
| vîndar
| vîndarî
| vîndarîtî
| vîndartî
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser|tişta/ê mirov dixwaze, tişta/ê mirov hez dike}}
* {{Z|fa}}: {{W+|fa|اراده}}, {{W+|fa|آرزو کردن}}, {{W-|fa|اراده کردن}}
* {{Z|ar}}: {{W+|ar|لَازِم}}
* {{Z|fr}}: {{W+|fr|vouloir}}, {{W+|fr|souhaiter}}
* {{Z|en}}: {{W+|en|want}}, {{W+|en|desire}}, {{W+|en|like}}
* {{Z|es}}: {{W+|es|querer}}, {{W+|es|gustar}}, {{W+|es|desear}}
* {{Z|tr}}: {{W+|tr|istemek}}, {{W+|tr|dilemek}}
{{werger-bin}}
{{werger-ser|gerek bûn, lazim bûn, pêdivî bûn, pêwîst bûn}}
* {{Z|de}}: {{W+|de|müssen}}, {{W+|de|sollen}}
* {{Z|fr}}: {{W+|fr|falloir}}
* {{Z|en}}: {{W+|en|have to}}, {{W+|en|must}}, to {{W+|en|need}}, to be {{W+|en|necessary}} (that)
* {{Z|tr}}: {{W+|tr|gerekmek}}
{{werger-bin}}
{{werger-ser|jê hez kirin}}
* {{Z|en}}: to {{W+|en|love}}
* {{Z|fa}}: {{W-|fa|سفید داشتن|guman=erê}}
{{werger-bin}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tişta/ê ku mirovî [[divê]], tişta/ê mirov [[xwestin|dixwaze]], tişta/ê mirov [[hez]] dike.
#: {{hevmane|ku|îrade|daxwaz}}
#* {{J:Mîran Janbar:Qolyeya Cecileê|r=75|jêgirtin=A rast, Cécileê baş bi xwe dizanibû, hewildanên ji derveyê '''vîna''' wê ew dienitand.}}
# [[hewcebûn]], gerekbûn, pêwîstbûn
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Werger ====
{{werger-ser|îrade, daxwaz}}
* {{Z|fr}}: {{W+|fr|volition}}
* {{Z|en}}: {{W+|en|will}}, {{W+|en|volition}}
* {{Z|tr}}: {{W+|tr|irade}}, {{W+|tr|istenç}}
{{werger-bin}}
{{werger-ser|xwestin, daxwazkirin}}
* {{Z|de}}: {{W+|de|Lust|m||guman=erê}}, {{W+|de|Begehr|m||guman=erê}}, {{W+|de|Begier|guman=erê}}
* {{Z|en}}: {{W+|en|desire}}
* {{Z|tr}}: {{W+|tr|isteme}}, {{W+|tr|dileme}}, {{W+|tr|heves}}, {{W+|tr|arzu}}, {{W+|tr|istek}}
{{werger-bin}}
{{werger-ser|hewcebûn}}
* {{Z|tr}}: {{W+|tr|gerekme}}, {{W+|tr|gerektirme|guman=erê}}, {{W+|tr|terettüp}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=înv|nîv}}
oumurdx2mf6n25yhl658hpsyxcw9ume
meaş
0
12920
6865599
6799678
2026-10-23T21:00:41Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6865599
wikitext
text/x-wiki
{{h-bnr|me'aş|meas}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA|me'aş}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# [[muçe]], [[miz]], [[destheq]], [[mehane]], [[heyvane]], [[hîvane]], [[mangane]], <br> [[pare]]yê ku [[xwedîkar]] dide [[karker]]ên xwe
# {{kontrol|rêz=erê|[[heq]], [[nirx]], [[biha]], [[qîmet]], [[giranî]]}}
==== Ji wêjeya klasîk ====
* {{J:Mem û Zîn:1692|jêgirtin=Hindî ko bi ker dikî qumaşan</br>Hindî ko belav dikî '''me'aşan'''</br>Her roj bi niyyeta xezayê}}
==== Bi alfabeyên din ====
* {{ku-ar|مهعاش}}
==== Formên alternatîv ====
{{kol3|ku|cure=Herwiha
|maaş
|ma'aş
|me'aş
|miaş
|mi'aş
|muaş
|mu'aş
}}
==== Jê ====
{{kol3|ku|cure=Jê
|meaş girtin<crz:lêker>
|meaş dan<crz:lêker>
|meaş girêdan<crz:lêker>
|meaşgirtin<crz:navdêr>
|meaşdan<crz:navdêr>
|bêmeaş
|bêmeaşî
|bimeaş
|bimeaşî
|meaşdar
|meaşdarî
|meaşder
|meaşderî
|meaşdêr
|meaşdêrî
|meaşgir
|meaşgirî
|meaşstîn
|meaşstînî
|meaşwergir
|meaşwergirî
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|ar:معاش<tr:me'aş>}} Têkilî ''[[meîşet]]''.
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|lone}}
* {{Z|sq}}: {{W+|sq|rrogë|m}}
* {{Z|de}}: {{W+|de|Lohn|n}}, {{W-|de|Arbeitsentgelt|nt}}
* {{Z|az}}: {{W+|az|maaş}}
* {{Z|be}}: {{W-|be|зарабо́тная пла́та|m|sc=Cyrl}}, {{W-|be|зарпла́та|m|sc=Cyrl}}
* {{Z|bn}}: {{W-|bn|বেতন|sc=Beng}}
* {{Z|bg}}: {{W-|bg|запла́та|m|sc=Cyrl}}, {{W+|bg|надница|m}}
* {{Z|my}}: {{W+|my|လုပ်ခ|sc=Mymr}}
* {{Z|cs}}: {{W+|cs|mzda|m}}
* {{Z|zh}}:
*: {{Z|dng}}: {{W-|dng|гунчян}}
*: {{Z|cmn}}: {{W+|cmn|工資|tr=gōngzī}}, {{W+|cmn|薪金|tr=xīnjīn}}, {{W+|cmn|薪水|tr=xīnshui}}, {{W+|cmn|工錢|tr=gōngqián}}
* {{Z|da}}: {{W+|da|løn}}
* {{Z|id}}: {{W+|id|gaji}}
* {{Z|ar}}: {{W+|ar|أَجْر|n}}, {{W-|ar|رَاتِب|n}}
* {{Z|hy}}: {{W+|hy|վաստակ}}, {{W+|hy|ռոճիկ}}
* {{Z|eo}}: {{W-|eo|salajro}}
* {{Z|et}}: {{W+|et|palk}}
* {{Z|fa}}: {{W+|fa|مزد|tr=mozd}}, {{W-|fa|دستمزد|tr=dast-mozd}}, {{W+|fa|کارمزد|tr=kâr-mozd}}
* {{Z|fi}}: {{W+|fi|palkka}}
* {{Z|fr}}: {{W+|fr|salaire|n}}
* {{Z|gd}}: {{W-|gd|cosnadh|n}}, {{W-|gd|pàigh|n}}, {{W-|gd|tuarasdal|n}}
* {{Z|gl}}: {{W+|gl|paga|m}}, {{W+|gl|salario|n}}
* {{Z|gu}}: {{W-|gu|મજૂરી}}, {{W-|gu|વેતન}}
* {{Z|ka}}: {{W+|ka|ხელფასი}}
* {{Z|hi}}: {{W+|hi|वेतन|n}}, {{W-|hi|तनख़्वाह}}, {{W-|hi|मासिक वेतन}}, {{W+|hi|दिहाड़ी|m}}
* {{Z|nl}}: {{W+|nl|loon|nt}}, {{W+|nl|salaris|nt}}, {{W+|nl|arbeidsloon|nt}}
* {{Z|he}}: {{W+|he|משכורת|m|tr=maskorét|cuda=משכורת \ מַשְׂכֹּרֶת}}, {{W-|he|שָׂכָר|n|tr=sakhár}}
* {{Z|enm}}: {{W-|enm|wage}}, {{W-|enm|mede|id=reward}}, {{W-|enm|hire|id=wages}}
* {{Z|en}}: {{W+|en|wage}}, {{W+|en|salary}}
* {{Z|ia}}: {{W+|ia|paga}}, {{W+|ia|salario}}
* {{Z|it}}: {{W+|it|salario|n}}, {{W+|it|paga|m}}
* {{Z|is}}: {{W+|is|laun|nt-p}}
* {{Z|ja}}: {{W+|ja|給料|tr=きゅうりょう, kyūryō}}, {{W-|ja|サラリー|tr=sararī}}, {{W+|ja|賃金|tr=chingin|sc=Jpan}}
* {{Z|ca}}: {{W+|ca|salari|n}}, {{W+|ca|sou|n}}, {{W+|ca|paga|m}}, {{W+|ca|honorari|n}}, {{W-|ca|estipendi|n}}, {{W-|ca|remuneració|m}}, {{W+|ca|retribució|m}}
* {{Z|ky}}: {{W-|ky|кызмат акы|sc=Cyrl}}, {{W-|ky|эмгек акы|sc=Cyrl}}
* {{Z|ko}}: {{W+|ko|임금}}, {{W+|ko|급료}}, {{W+|ko|봉급}}, {{W+|ko|삯}}, {{W-|ko|품삯}}
* {{Z|ltg}}: {{W-|ltg|moksa}}, {{W-|ltg|lūne}}
* {{Z|la}}: {{W-|la|mercēs|m}}, {{W-|la|manupretium|nt}}
* {{Z|lv}}: {{W+|lv|alga|m}}
* {{Z|lo}}: {{W-|lo|ຄ່າຈ້າງ}}, {{W+|lo|ຄ່າແຮງງານ}}
* {{Z|lt}}: {{W-|lt|užmokestis|n}}, {{W+|lt|alga|m}}
* {{Z|lb}}: {{W-|lb|Loun|n}}
* {{Z|mk}}: {{W-|mk|пла́та|m|sc=Cyrl}}
* {{Z|ml}}: {{W-|ml|വേതന}}, {{W+|ml|കൂലി}}
* {{Z|ms}}: {{W+|ms|gaji}}
* {{Z|mt}}: {{W-|mt|paga|m}}
* {{Z|mnc}}: {{W-|mnc|ᠪᠠᠰᠠ}}
* {{Z|mi}}: {{W-|mi|utu-ā-haora}}
* {{Z|hu}}: {{W+|hu|bér}}, {{W+|hu|órabér}}
* {{Z|mn}}: {{W-|mn|цалин хөлс|sc=Cyrl}}, {{W+|mn|цалин|sc=Cyrl}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|lønn|n|m}}
* {{Z|oc}}: {{W+|oc|salari|n}}
* {{Z|uz}}: {{W+|uz|maosh}}, {{W+|uz|moyana}}
* {{Z|pdt}}: {{W-|pdt|Jehault|n}}
* {{Z|pl}}: {{W+|pl|pensja|m}}, {{W+|pl|płaca|m}}
* {{Z|pt}}: {{W+|pt|salário|n}}, {{W+|pt|ordenado|n}}, {{W+|pt|remuneração|m}}
* {{Z|prg}}: {{W-|prg|algā}}
* {{Z|kk}}: {{W-|kk|айлық|sc=Cyrl}}, {{W-|kk|жалақы|sc=Cyrl}}, {{W-|kk|еңбекақы|sc=Cyrl}}
* {{Z|ro}}: {{W+|ro|plată|m}}, {{W+|ro|salariu|nt}}, {{W+|ro|leafă|m}}
* {{Z|ru}}: {{W-|ru|за́работная пла́та|m}}, {{W-|ru|зарпла́та|m}}, {{W-|ru|за́работок|n}}, {{W-|ru|жа́лование|nt}}
* {{Z|ceb}}: {{W-|ceb|suhol}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|плата|m}}, {{W-|sh|плаћа|m}}
*: Latînî: {{W+|sh|plata|m}}, {{W+|sh|plaća|m}}
* {{Z|sk}}: {{W-|sk|mzda|m}}, {{W-|sk|plat|n}}
* {{Z|sl}}: {{W+|sl|plača|m}}
* {{Z|es}}: {{W+|es|salario|n}}, {{W+|es|sueldo|n}}
* {{Z|xsv}}: {{W-|xsv|alga|m}}
* {{Z|sw}}: {{W+|sw|mshahara}}
* {{Z|sv}}: {{W+|sv|lön|g}}
* {{Z|tg}}: {{W+|tg|маош|sc=Cyrl}}, {{W-|tg|моҳона|sc=Cyrl}}, {{W-|tg|музди кор|sc=Cyrl}}, {{W-|tg|дастмузд}}
* {{Z|tl}}: {{W-|tl|sahod}}, {{W-|tl|suweldo}}
* {{Z|th}}: {{W+|th|ค่าจ้าง}}
* {{Z|tr}}: {{W+|tr|ücret}}, {{W+|tr|gündelik}}, {{W+|tr|yevmiye}}, {{W+|tr|maaş}}
* {{Z|tk}}: {{W-|tk|iş haky}}, {{W+|tk|aýlyk}}
* {{Z|ur}}: {{W-|ur|ویتن|n|tr=vetan|sc=ur-Arab}}, {{W-|ur|اجرت|m|tr=ajrat|sc=ur-Arab}}
* {{Z|uk}}: {{W-|uk|заробі́тна пла́та|m|sc=Cyrl}}, {{W-|uk|зарпла́та|m|sc=Cyrl}}, {{W-|uk|запла́та|m|sc=Cyrl}}
* {{Z|vi}}: {{W+|vi|lương}}
* {{Z|km}}: {{W-|km|ប្រាក់ខែ}}, {{W-|km|បរិលាភ}}
* {{Z|el}}: {{W+|el|μισθός|n}}, {{W+|el|μεροκάματο|nt}}
*: Kevn: {{W-|grc|μισθός|n}}, {{W-|grc|ὀψώνιον|nt}}
{{werger-bin}}
:{{werger çavkanî|en|wage|beş=Ev qismê Wergerê|oldid=75872526}}
==== Anagram ====
* {{anagram|ku|a=aemş|aşem|maşe|meşa|şema}}
2rj2juelsktbyi1bnqtn9kucoxe7zsd
PB
0
20351
6865568
6549560
2026-10-23T20:34:21Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865568
wikitext
text/x-wiki
{{h-bnr|Pb}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Kurtenav ===
{{kurtenav|ku}}
# [[kurte]]ya van [[peyv]]an e:* [[polîtbîro]]
#* {{jêder|ku|jêgirtin=Îşxan Arslan Enkosî di hevdîtina li gel '''PBya''' PDKê de, bahsa rewşa kurdên Rusyayê kir û diyar kir ku hejmareka giring kurd li herêma Krasnadarê a Rûsyaya Federal heye.|Netkurd.org, 6/2006}}
cfom13s75u6h1i9k9ar724u0dge4jo6
Seqiz
0
21086
6865591
6550136
2026-10-23T20:49:28Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865591
wikitext
text/x-wiki
{{h-bnr|şeqiz}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=m}}
# [[bajar]]ek e li [[Rojhilata Kurdistanê]]
#* {{jêder|ku|Netkurd.com, 6/2006|jêgirtin=Xortekî kurd ku ji rojhilatê Kurdistanê, ji bajarê '''Seqizê''' bû û endamê grûbeka tundrê ya îslamî bû, xwe li bajarê Îraqê Rumadiyê teqandiye. <p>Li gor heftemaneya Medya wî xortî beriya demekê nameyek şandibû malê û tê de gotibû ku ji bo şehîdbûnê (xwe teqandinê) du kes li pêşiya wî mane. Dema ev name gihaştiye malê diya wî ji ber xeman (qehran) miriye. <p>Piştî ku nûçeya xweteqandina wî dighêje '''Seqizê''' bavê wî jî wek dêya wî ji xeman miriye.}}
==== Bi alfabeyên din ====
* kurdî-erebî: [[سهقز]]
==== Jê ====
* [[seqizî]]
* [[seqizîtî]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|fa}}: {{W+|fa|سقز}}
* {{Z|en}}: {{W-|en|Saqqez}}, [[Saghez]] (a city in Eastern Kurdistan, {{W-|en|Iran)}}
{{werger-bin}}
{{mijar|ku|bajar}}
5xnthebmmje17gc89xbg082sqcf23cy
PÇDK
0
21304
6865577
6131594
2026-10-23T20:36:29Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865577
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Kurtenav ===
{{kurtenav|ku}}
# Partiya Çareseriya Demokratîk ya Kurdistanê
#* {{jêder|ku|jêgirtin=Pêr êvarê saet di 22.30yê êvarê de asayişa Hewlêre avêt ser navenda Partiya Çareseriya Demokratîk (ku alîgirê PKK ê başûr e) çend kesên wan ên li navendê girt bin çav û avayiya partiyê daxist.|Netkurd.com, 7/2006}}
78hnbgbzg8onsbusi1dbuayn7bfvfij
PKE
0
39806
6865571
6378859
2026-10-23T20:35:07Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865571
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Kurtenav ===
{{kurtenav|ku}}
# [[Platform]]a [[Kurdî]] li [[Ewropa]]yê
#* {{jêder|ku|jêgirtin=Banga Platforma Kurd li Ewropayê ('''PKE''') li hemû sazî, rewşenbîr, siyasetmedar û ronakbîrên Kurdistanê ewe ku li dijî dewletên dagirker, helwesta xwe diyar bikin û li her deverên cîhanê li dijî êrişên wan ên hovane çalakiyan saz bikin..|Netkurd.com, 6/2007}}
==== Anagram ====
* {{anagram|ku|a=ekp|pek}}
0ez79xu13k3ns7f76lmi9zqkrhkqxg4
biîrade
0
44555
6865522
6380524
2026-10-23T19:18:21Z
Wikihez
7344
/* Rengdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865522
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Rengdêr ===
{{rengdêr|ku}}
# {{g|ku|vîndar}}
==== Anagram ====
* {{anagram|ku|a=abdeiîr|biîdare}}
k0ynt30r5hk3k9gph4psbtsck68i8vk
Prometheus
0
46238
6865575
6468123
2026-10-23T20:36:16Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865575
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# {{e|ku|mîtolojiya yûnanî}} [[tîtan|Tîtan]]ê ku [[agir]] ji [[xweda]]yan [[dizîn|dizî]] û [[mirov]] [[afirandin]].
#* {{jêder-websîte|ku|jêgirtin='''Prometheus''' ew xwedayê ku, ji ber ku agir ji cîhana xweda û xwedawendan diziye û ji mirovan re aniye, ji aliyê Xwedayê Xwedayan Zeûs ve tê sezakirin. Li gora sezayê Zeûs dibê '''Prometheus''' bi latan bi cincîran were girêdan û dibê her roj qertelên birçî ji laş, gurçik û dilên wî têr buxin. '''Prometheus''' her wiha tê wateya yekem car Serhildanê, yekem car Berxwedanê, li hemberî desthilatdarî û zordestiya xwedayan anjî kesên ku sîstema desthilatdariyê bi rêve dibin.|nivîskar=Siyamend Sîpan Ugurlu|sernav=Bêzarbûyîna Ziman|websîte=Xelkedondurma.com|sal=9/2007}}
==== Formên alternatîv ====
* [[Prometeus]]
==== Etîmolojî ====
Ji yewnaniya kevn
lpy95hnoe70q4c30atj70jwlfzc4gz0
6865620
6865575
2026-10-23T22:12:13Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6865620
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# {{e|ku|mîtolojiya yûnanî}} [[tîtan|Tîtanê]] ku [[agir]] ji [[xweda]]yan [[dizîn|dizî]] û [[mirov]] [[afirandin]].
#* {{jêder-websîte|ku|jêgirtin='''Prometheus''' ew xwedayê ku, ji ber ku agir ji cîhana xweda û xwedawendan diziye û ji mirovan re aniye, ji aliyê Xwedayê Xwedayan Zeûs ve tê sezakirin. Li gora sezayê Zeûs dibê '''Prometheus''' bi latan bi cincîran were girêdan û dibê her roj qertelên birçî ji laş, gurçik û dilên wî têr buxin. '''Prometheus''' her wiha tê wateya yekem car Serhildanê, yekem car Berxwedanê, li hemberî desthilatdarî û zordestiya xwedayan anjî kesên ku sîstema desthilatdariyê bi rêve dibin.|nivîskar=Siyamend Sîpan Ugurlu|sernav=Bêzarbûyîna Ziman|websîte=Xelkedondurma.com|sal=9/2007}}
==== Formên alternatîv ====
* [[Prometeus]]
==== Etîmolojî ====
Ji yewnaniya kevn
qbm2w4n2jn73iwskafsi3pdtxyp7175
mandel
0
46589
6865543
6805839
2026-10-23T19:52:00Z
Wikihez
7344
Kurmancî/Navdêr: mane, hevmane hatin guhartin (DefSideBoxes)
6865543
wikitext
text/x-wiki
{{h-bnr|Mandel}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[gotin|Gotin]] ku tiştek [[nîne]] yan ne[[rast]] e.
#: {{hevmane|ku|nikolî|şemartin|şerandin|haşatî|inkar}}
#: {{nm|ku|Tirk hebûna kurdan '''mandel''' dikin.|Tirk dibêjin ku kurd nînin.|rêz=1}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
{{kol3|ku
|mandela
|mandele
}}
==== Jê ====
{{kol3|ku
|mandel kirin<crz:lêker>
|mandelkirin<crz:navdêr>
|mandelkirî<crz:rengdêr>
|mandelkar
|mandelkarî
|mandelker
}}
==== Etîmolojî ====
{{etîmon|ku|:drşnd|tr:mandal|metnna=1}}
Bi riya {{ji|tr|ku|mandal}} yan {{ji|ota|ku|ماندال|tr=mandal}} ([[qerase]], [[bizmik]]) ji {{ji|el|ku|μάνδαλος}}. Guherîna maneya peyvê ''qerase kirin > asê kirin > rê lê girtin > qedexe kirin > red kirin > inkar kirin''. Ji heman rehî: ''[[mendel]]''.
==== Bide ber ====
* {{g|ku|mendel}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|verloëning}}, {{W+|af|selfverloëning}}
* {{Z|de}}: {{W+|de|Entsagung}}, {{W+|de|Selbstverleugnung}}
* {{Z|eo}}: {{W+|eo|malkonfeso}}, {{W+|eo|malagnosko}}, {{W+|eo|abnegacio}}
* {{Z|fa}}: {{W+|fa|دروغ}}, {{W+|fa|اشتباه}}, {{W+|fa|بیراه}}
* {{Z|fo}}: {{W+|fo|avnoktan}}, {{W+|fo|sjálvsavnoktan}}
* {{Z|fi}}: {{W+|fi|kiistäminen}}
* {{Z|fr}}: {{W+|fr|abnégation}}, {{W+|fr|renoncement}}
* {{Z|nl}}: {{W+|nl|verloochening}}, {{W+|nl|verzaking}}, {{W+|nl|ontkenning}}, {{W+|nl|abnegatie}}, {{W+|nl|zelfverloochening}}
* {{Z|io}}: {{W+|io|abnego}}
* {{Z|en}}: {{W+|en|abjuration}}, {{W+|en|denial}}, {{W+|en|disavowal}}, {{W+|en|recantation}}, {{W+|en|refusal}}, {{W+|en|abnegate}}, {{W+|en|abnegation}}, {{W+|en|repudiation}}, {{W+|en|negation}}, {{W|en|disownment|guman=erê}}, {{W|en|elenchus|guman=erê}}, {{W|en|refutal|guman=erê}}
* {{Z|is}}: {{W+|is|sjálfsafneitun}}
* {{Z|la}}: {{W+|la|abnegatio}}
* {{Z|pt}}: {{W+|pt|abnegação}}, {{W+|pt|desprendimento}}
* {{Z|ru}}: {{W+|ru|самоотвершенность}}, {{W+|ru|самоотречение}}, {{W+|ru|отрещение}}
* {{Z|es}}: {{W+|es|abnegación}}
* {{Z|tr}}: {{W+|tr|inkar}}, {{W+|tr|yokumsama}}, {{W+|tr|yadsıma}}
{{werger-bin}}
8jdua90qbr0qd14qwk2pnzwlir5g828
Pîraye
0
46591
6865579
6574261
2026-10-23T20:41:49Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865579
wikitext
text/x-wiki
{{h-bnr|pîraye}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[nav]]ek [[jin]]an e
#* {{jêder-websîte|ku|jêgirtin=Deriyên hesinî yên heft xilq, dîwarên ji hêwiyê gurî gurî, hepsiyên nûhatî û yên şenqxwarî dizanibûn ku navê jina Nazim '''Pîraye''' ye.|nivîskar=Arjen Arî|sernav=Di Helbesta Cegerxwîn De şêweyek: HELBESTNAME|websîte=Kovaraw.com|sal=11/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê|guherto=hemû}}
==== Bide ber ====
* [[pîraya]]
==== Anagram ====
* {{anagram|ku|a=aeîpry|pareyî}}
9s5xauhxdvyfbvmdynlbwcll762k6q6
Senîha
0
47669
6865590
6469574
2026-10-23T20:49:13Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865590
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[nav]]ek [[jin]]an e
#* {{jêder-websîte|ku|jêgirtin='''Senîha''' Xanim xatûnek bi firşik, hişmend û pispor e. Wê du evîn di dadgeha leşkerîyê ya faşîst de diparastin: Yek ji van evînan evîndarê wê Medet Serhat bû, ya dî jî evîna Medet Serhat bi xwe, doza Kurd û Kurdistanê bû. Wê tu car firşikê xwe kêm nekir û bi merdane û rastiyane herdu evîn jî di dadgeha leşkerî de diparastin.|nivîskar=Seîdê Sîsî|sernav=Senîha Xanim û 49an|websîte=Amidakurd.com|sal=12/2007}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
* [[Senîhe]]
==== Anagram ====
* {{anagram|ku|a=aehîns|hesîna|sanehî}}
kdnq5huwpv73u2kfdhg8a6y54xpoyed
apolet
0
48532
6865655
6806178
2026-10-24T10:44:51Z
Ziv
14071
([[c:GR|GR]]) [[File:Rank slide.jpg]] → [[File:Rank slide of a Royal Air Force sergeant.jpg]] [[c:COM:FR#FR2|Criterion 2]] · The name remained meaningless even after the first move.
6865655
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Rank slide of a Royal Air Force sergeant.jpg|thumb|rast|apoletek]]
{{navdêr|ku|z=m}}
# [[stêr|Stêrên]] [[unîforma]]yan yên li ser [[mil]]an.
#: {{hevmaneyên peyvê|ku|sermilk|qirmilk|necme}}
#* {{jêder|ku|jêgirtin=Sal duhezar û x, mexlûqek mejî di çerxa paleolîtîk de, ew bi xwe navûdeng bi postalên postmodern, bi apolet û bi unîformayê bi bêqusuriyeka dema modern hatiye çêkirin, di bin birqîya objektîfên bi huner de, bi îhtîşameka telejenîk, bi tîhniya mirovê di beriyê de rêlêwindabûyî, tasa xwînê bi remza alayê, bi [[noş]]a [[ab-i heyatê]] raberî dinê dikir û bi dilsojiyeka serdest digot “rûmet û şiyanên me hemû di vê tasê de ye, hêza xweragirtina me jî tenê ew e”.|Mihemed Evdila: ''Sal duhezar û x'', Nefel.com, 1/2008}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Formên alternatîv ====
* {{g|ku|epolet}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|fr:épaulette<etimon:qertaf<fr:épaule<t:mil>><fr:-ette>>}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Achselstück|n}}
* {{Z|fi}}: {{W+|fi|olkain}}
* {{Z|fr}}: {{W+|fr|épaulette}}
* {{Z|en}}: {{W+|en|epaulette}}, {{W+|en|epaulet}}
* {{Z|tr}}: {{W+|tr|apolet}}, {{W+|tr|omuzluk}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aelopt|palote}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# {{g|ku|apolet}}
#: {{mk|tr|subayın '''apoletleri'''|apoletin efser|rêz=1}}
iebf7a9b4q4wa90bbk7gb9hyepmbmtg
tesfiye
0
49031
6865521
6470926
2026-10-23T15:27:15Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Jê» hate nûkirin (DefSideBoxes)
6865521
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[safî]]kirin, [[xwerû]]kirin, [[zelilandin]], [[zelal]]kirin, [[paqij]]kirin, [[pak]]kirin, [[arî]]kirin, [[sade]]kirin
# [[jiholêrakirin]], [[îmha]]kirin, ne[[hêlan]]
==== Tewandin ====
{{ku-tewîn-nav|mê|guherto=hemû}}
==== Formên alternatîv ====
{{kol3|ku
|tasfiye
}}
==== Jê ====
{{kol3|ku
|tesfiye bûn<crz:lêker>
|tesfiye kirin<crz:lêker>
|tesfiyebûn<crz:navdêr>
|tesfiyekirin<crz:navdêr>
|tesfiyebûyî<crz:rengdêr>
|tesfiyekirî<crz:rengdêr>
|tesfiyeker
}}
==== Etîmolojî ====
Ji {{deyn|ar|ku}}. {{anlz|ku|te-|safî}}.
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|refinement}}
* {{Z|it}}: {{W+|it|liquidare}}, {{W+|it|liquidazione}}
* {{Z|tr}}: {{W+|tr|likidasyon}}, {{W+|tr|tasfiye}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=eefisty|tefsiye}}
6w5xdgxz19nhbow27811l2hjy17pdnu
Qalû bela
0
49588
6865580
6346843
2026-10-23T20:42:23Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865580
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Biwêj ===
{{biwêj|ku}}
# "[[gotin]] [[belê]]" <br>(ji [[ayet]]ek [[Qur'an]]ê ye ku dema Xwedê berî afirandina dinyayê ji giyanê mirovan dipirse
#:"Ma ez ne Xwedayê we me", ew jî dibêjin "belê, tu Xwedayê me yî")
#* {{jêder|ku|jêgirtin=Em ji '''qalû belayê''' vir de busulman in.|Netkurd.com, 4/2008}}
==== Têkildar ====
* [[Elest]]
e9fueozpdbq3chytz42wb1gvj242wxj
PK
0
50432
6865570
6136701
2026-10-23T20:34:52Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865570
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Kurtenav ===
{{kurtenav|ku}}
# [[parleman|Perlemana]] [[Kurdistan]]ê
#* {{jêder|ku|jêgirtin='''PK''': Biryara Bexdayê ne meşrû ye.|Netkurd.com, 7/2008}}
8c74yn36bykvtstlcgoxvcg7taiujp5
Qutas
0
51545
6865582
6473394
2026-10-23T20:42:51Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865582
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=n}}
# [[nav]]ek [[kevn]] yê [[zelam]]an e
#* {{jêder|ku|Welat.com, 2/2009|jêgirtin=Wargeha Penaberan a Mexmûrê rûspiyê xwe yê temendirêj '''Qutas''' Bulut ku wekî Mamê '''Qutas''' dihat zanîn, winda kir. Mamê '''Qutas''' pêr êvarê li mala xwe ya li wargehê seat li derdora 19.00`î koça dawî kir. Mamê '''Qutas''' ku duh beriya jiyana xwe ji dest dibe, ji malbat û zarokên xwe xwestibû ku wî bibin Saziya Malbatên Şehîdan. Mamê '''Qutas''' li saziyê cara dawî li wêneyên kesên di pevçûnan de jiyana xwe ji dest dane temaşe kiribû. Piştre jî xatir ji me xwest û koça dawî kir.}}
9p3tnej9qfxzgfheilpscsxgqpyxx3z
axîş
0
51888
6865538
6473752
2026-10-23T19:43:29Z
Wikihez
7344
/* Werger */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865538
wikitext
text/x-wiki
{{h-bnr|axis}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/ɑːˈxiːʃ/}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[hêman]], [[regez]], [[unsûr]], [[eleman]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aîşx|xaşî|xîşa|şaxî}}
=== Çavkanî ===
* Mela Xelîl Mişextî: ''Ferhenga Merg û Jî'', Aras, Hewlêr 2006
lk4ztuw9ydrn23ruo9oh9yscrsnp7g0
duşes
0
52637
6865642
6578167
2026-10-24T08:23:56Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6865642
wikitext
text/x-wiki
{{h-bnr|duşeş|dûşeş|düşes|düşeş}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[duk|Duka]] [[jin]]; [[jin]]a [[duk]]ekî.
#: {{hevmane|ku|xatûn|dukbanî}}
#* {{jêder|ku|jêgirtin='''Duşesa''' Sosret, Tişta Kej: Sylvia Plath|sernavê nivîsarek [[w:Kawa Nemir|Kawa Nemir]], ''[[w:Kovara W|Kovara W]]'', 3/2009}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Jê ====
{{kol3|ku
|duşesane
|duşesî
|duşesîtî
|duşestî
}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|fr:duchesse<etimon:qertaf<duc<etimon:mîrasmayî<la:dux>>><-esse>>}}
==== Bide ber ====
{{kol3|ku
|duşeş
}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Herzogin|f}}
* {{Z|cs}}: {{W|cs|vévodkyně|f}}
* {{Z|fi}}: {{W+|fi|herttuatar}}
* {{Z|fr}}: {{W|fr|duchesse|f}}
* {{Z|gd}}: {{W|gd|ban-diùc|f}}
* {{Z|nl}}: {{W|nl|hertogin|f}}
* {{Z|en}}: {{W+|en|duchess}}
* {{Z|ga}}: {{W|ga|bandiúc|f}}
* {{Z|it}}: {{W+|it|duchessa}}
* {{Z|mk}}: {{W|mk|војвотка|tr=vójvotka|f}}
* {{Z|hu}}: {{W+|hu|hercegné}}
* {{Z|ru}}: {{W|ru|герцогиня|tr=g'ercogín'a|f}}
* {{Z|es}}: {{W|es|duquesa|f}}
* {{Z|sv}}: {{W|sv|hertiginna|c}}
* {{Z|tr}}: {{W+|tr|düşes}}
{{werger-bin}}
021p0so3kd87gcdhyssjuuh1hqbcjas
Selmas
0
53479
6865589
6475090
2026-10-23T20:49:03Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865589
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=m}}
# [[bajar]]ek e li [[Rojhilata Kurdistanê]] li [[nêz]]î [[tixûb]]ê [[Bakurê Kurdistanê]]
#* {{jêder|ku|Azadiyawelat.com, 9/2009|jêgirtin=Li ser sînorê Îranê 3 kurdên din hatin qetilkirin. Qetilkirina 3 kurdan li ser sînorê bajarê Saqiz û '''Selmasê''' pêk hat. Di mehên dawî de rejîma Îranê bi dehan kurd li ser sînor qetil kirin.}}
==== Formên alternatîv ====
* [[Salmas]]
==== Jê ====
* [[selmasî]]
* [[selmasîtî]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Bide ber ====
* [[Selma]]
* [[Selman]]
==== Werger ====
{{werger-ser}}
* {{Z|fa}}: {{W-|fa|سلماس}}
{{werger-bin}}
46p4qz3lnrvr5epxqrpm7lya9uexl9h
Sara
0
53588
6865588
6548067
2026-10-23T20:48:51Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865588
wikitext
text/x-wiki
{{h-bnr|Sára|Sâra|sara|sarà|şara}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/sɑːˈɾɑː/}}
* {{kîte|ku|Sa|ra}}
=== Serenav ===
{{serenav|ku|z=m}}
# [[nav]]ek {{nk|ku|keç}} e
#* {{jêder|ku|jêgirtin=Ji ber qise û qiselokên xelkê, yek ji xewn û xeyalên '''Sara''' ku ew jî ajotina bisîklêt e, nayê dîtin. '''Sara''' Ebubekir a 19 salî, xwendekara qonaxa amadeyî ye, dibêje gelekî kêfa wê ji ajotina bisîklêt re tê: “Lê ji ber qise û qiselokên civakê ditirsim.” Li gor '''Sara''' ajotina bisîklêt tiştekî pir normal e û tu şermek tê de tune: “Lê di nav civaka me de, şermên bêwate zêde ne."|Netkurd.com, 9/2009}}
==== Tewandin ====
{{ku-tewîn-nav|mê|y}}
==== Têkildar ====
* [[Zehra]]
* [[Zerê]]
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|Sarah}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aars|Aras|asar|asra}}
== {{ziman|en}} ==
=== Bilêvkirin ===
* {{deng|en|LL-Q1860 (eng)-Grendelkhan-Sara.wav|Deng|dever=Connecticut, DYA}}
=== Navdêr ===
{{navdêr|en}}
# [[nav]]ekî {{nk|ku|keç}} e
tvqa5awy2xi1qdmmy6jgm3o8ihq3tjy
Roja Rabûnê
0
53616
6865586
6475168
2026-10-23T20:48:16Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865586
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{e|ku|îslam}} [[Roja Qiyametê]], [[Roja Heşirê]], [[Roja Hesabê]], <br>[[roj]]a ku li gor [[bawerî|baweriyê]] hemû [[mirî]] dê ji [[gor]]ên xwe [[hişyar]] bibin û [[qencî]] û [[xirabî|xirabiyên]] ku wan kirine dê bên [[pîvan]] û dê [[biryar]] bêt dan ku ew dê biçin [[biheşt]]ê yan [[dûjeh]]ê
#* {{jêder|ku|Qur'ana pîroz, sûretê 28. Serpêhatî, wergerrandin: Husein Muhammed, Kulturname.com, 8/2009|jêgirtin=42. Me kir ku li vê dinyayê nalet li wan bibare û li '''Roja Rabûnê''' ew nefretkirî ne.}}
21w7c76wltkph157pyvwu5nfb6fz3bp
Qeredax
0
55242
6865581
6477027
2026-10-23T20:42:36Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865581
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=m}}
# [[dever]]ek e li [[Başûrê Kurdistanê]]
#* {{jêder|ku|jêgirtin=Elî '''Qeredaxî''': Berî 15 salan min ji [Abdullah] Gul û [Necmettin] Erbakan re behsa pirsa kurdan kir.|Nefel.com, 8/2010}}
==== Jê ====
* [[qeredaxî]]
* [[qeredaxîtî]]
==== Etîmolojî ====
Yan ji tirkî [[kara]] ([[reş]]) yan jî ji ermenî ''qere'' ([[mezin]], [[gir]], [[girs]], [[gewre]]) + ji tirkî [[dağ]] ([[çiya]])
rvtc5x45s4o7sc7dq5hm6eok8i320gh
Ezraîl
0
60539
6865562
6807675
2026-10-23T20:32:52Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865562
wikitext
text/x-wiki
{{h-bnr|Ezrail}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[Giyanstîn]], [[Ruhstîn]], [[melekê mewtê]], [[firîşteyê mirinê]], <br>(li gor hin [[dîn]]an) [[firîşte]]yê ku mirovan [[mirandin|dimirîne]] anku [[ruh standin|ruha wan distîne]]
#* {{jêder|ku|jêgirtin=(TRT6) kanala dewletê ye, ya AKP ye tiştekî din, em vê pirsê deynin alîyekî. Lê kî lotikan ji bo zimanê Kurdî bavêje xizmetek baş e û wê rebbê alemê xêra wî / wê di deftera xwe de binivîse. Li gora ku '''Ezraîl''', Israfîl û Mikaîl ji me re gotiye, heçê xizmeta zimanek wek zimanê Kurdî bike, wê rasterast biçe bihuştê û bibe mêrê / jina 70 horî û nûrîyên germik û nermik. Werhasilî kelam, kekê me Zeynelabidîn Zinar zend û bendên xwe hildaye û li TRT6ê dersa Kurdî dide karmendên televizyonê. Ew û Prof. Dr. Qedrî Yildirim lotikên Kurdewarî davêjin û hewl didin ku zimanê Tirkî ji devê karmandên TRT6ê bavêjin û zimanê Kurdî di devê wan de bidirûn.|Lotikxane.com, 5/2011}}
==== Formên alternatîv ====
* [[Izraîl]]
==== Etîmolojî ====
{{etîmon|ku|:drşnd|ar:عزرائيل|metnna=1}}
Ji {{ji|ar|ku|عزرائيل}} ('Izraîl) ji {{ji|he|ku|עזראל}} (Azraêl: yê ku Xwedê alîkariya wî dike") ji îbrî ''êl'' ([[îlah]], [[Ellah]])
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W-|de|Azrael|n}}
* {{Z|az}}: {{W-|az|əzrail}}
* {{Z|ba}}: {{W-|ba|Ғазраил}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W-|cmn|亞茲拉爾|tr=Yàzīlā'ěr}}
* {{Z|ar}}: {{W+|ar|عَزْرَائِيل|n}}, {{W+|ar|عِزْرَائِيل|n}}, {{W-|ar|مَلَك اَلْمَوْت|n}}
* {{Z|eo}}: {{W-|eo|Azraelo|n}}
* {{Z|fr}}: {{W+|fr|Azraël|n}}
* {{Z|nl}}: {{W-|nl|Azraël|n}}
* {{Z|he}}: {{W-|he|עזראל}}
* {{Z|en}}: {{W+|en|Azrael}}
* {{Z|it}}: {{W-|it|Azrael|n}}
* {{Z|ja}}: {{W-|ja|アズラーイール|tr=Azurāīru|sc=Jpan}}
* {{Z|ca}}: {{W-|ca|Azrael|n}}
* {{Z|ms}}: {{W-|ms|Izrail}}
* {{Z|ug}}: {{W-|ug|ئەزرائىل}}
* {{Z|pl}}: {{W-|pl|Azrael|n}}
* {{Z|pt}}: {{W-|pt|Azrael|n}}
* {{Z|kk}}: {{W-|kk|Әзірейіл|sc=Cyrl}}
* {{Z|ro}}: {{W-|ro|Azrael|n}}
* {{Z|ru}}: {{W+|ru|Азраи́л|n}}
* {{Z|es}}: {{W-|es|Azrael|n}}
* {{Z|tr}}: {{W+|tr|Azrail}}, {{W-|tr|Ezrail}}
* {{Z|el}}: {{W-|el|Αζραήλ|n}}
{{werger-bin}}
:{{werger çavkanî|en|Azrael|beş=Ev qismê Wergerê|oldid=73079502}}
==== Anagram ====
* {{anagram|ku|a=aeîlrz|lîzera}}
q17x9nzo598jsb3525ryrhqygkxov5s
PJAK
0
61411
6865569
6346684
2026-10-23T20:34:41Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865569
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
:{{ku-tîp|pijak}}
=== Serenav ===
[[Wêne:Flag of Partiya Jiyana Azad a Kurdistanê.png|thumb|Ala PJAK'ê]]
{{serenav|ku|z=m}}
# [[kurte]]ya "Partiya Jiyana Azad li Kurdistanê", [[partî|partiyek]] [[çekdar]] ya nêzîkî [[PKK]]ê ye û dijî dewleta [[Îran]]ê [[şer]] dike
#* {{jêder|ku|jêgirtin=Zêdeyî du hefteyan e ku Komara Îslamî ya Îranê dest bi topbaran û bombardimankirina sînorên Herêma Kurdistanê kiriye û çar roj jî dibe li sînorê qezaya Pişder û quntara Çiyayê Qendîl di navbera pasdarên Îranê û çekdarên '''PJAKê''' de şerekî dijwar heye û navçe bi dijwarî tê topbaran kirin. Di encam de jî jinek li gundê Sunê birîndar bû û zêdeyî du xanî û xwendingehek jî hilweşiyan.|Rudaw.net, 7/2011}}
==== Bi alfabeyên din ====
* {{ku-ar|پژاک}}
==== Jê ====
* [[PJAK-î]]
* [[PJAK-îtî]]
1njo8p6dopcohk70v5k878en0gy4ut6
Ewrovizyon
0
61556
6865561
6807809
2026-10-23T20:32:32Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865561
wikitext
text/x-wiki
{{h-bnr|Ewrovîzyon}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=}}
# [[pêşbirk]]ek [[stran]]an e ku [[salane]] di navbera gelek [[welat]]ên [[ewropî]] de tê kirin
#* {{jêder|ku|jêgirtin=Li Tirkiyeyê, hunermenda kurd a muzîka rapê Dezz Deniz, bû namzeda Tirkiyeyê di hevrikiya strangotinê ya Ewropayê '''Eurovizyonê''' ya 2012ê de. Deniz, got, dê di strana xwe de, ku bi înglîzî ye, dê peyvên tirkî û kurdî bi kar bîne û wiha axivî, “Ez bawer im, dê bi awayekî herî baş, gelê kurd û tirk temsîl bikim.” Deniz, daxuyand, Tirkiyeyê, dê serkeftina ku heq kiriye, bi dest bîne û wiha anî ziman, “Ez dixwazim bi vê yekê, him welatê xwe temsîl bikim û him jî bi navê kurdan û tirkan, beşdarî vê hevrikiyê bibim.”|Aknews.com, 7/2011}}
# [[torr]]a [[televizyon]]î ya ku wê [[pêşbirk]]ê [[li dar xistin|li dar dixe]]
==== Formên alternatîv ====
* [[Eurovizyon]]
* [[Eurovîzyon]]
* [[Ewrovîzyon]]
==== Jê ====
* [[şamilî]]
==== Etîmolojî ====
{{etîmon|ku|:deyn|en:Eurovision|metnna=1}}
Deyn ji {{deyn|en|ku|Eurovision}}, [[kurte]]ya
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Eurovision|f}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W|cmn|歐洲電視網|sc=Hani}}, {{W|cmn|欧洲电视网|tr=Ōuzhōu Diànshìwǎng}}
* {{Z|ar}}: {{W|ar|tr=yurufijn|يوروفيجن|m}}
* {{Z|hy}}: {{W|hy|Եվրատեսիլ|tr=Evratesil}}
* {{Z|fi}}: {{W+|fi|Euroviisut}}
* {{Z|fr}}: {{W|fr|Eurovision|f}}
* {{Z|nl}}: {{W+|nl|Eurovisie}}
* {{Z|he}}: {{W+|he|אירוויזיון}}
* {{Z|en}}: [[Eurovision]] (Song Contest / TV network)
* {{Z|ia}}: {{W|ia|Eurovision|xs=Interlingua}}
* {{Z|ja}}: {{W|ja|ユーロビジョン|tr=Yūrobijon}}
* {{Z|ca}}: {{W+|ca|Eurovisió}}
* {{Z|ko}}: {{W|ko|유로비전|tr=Yurobijeon}}
* {{Z|mt}}: {{W|mt|Eurovision|cuda=Il-Eurovision}}
* {{Z|pl}}: {{W|pl|Eurowizja|f}}
* {{Z|pt}}: {{W|pt|Eurovisão|f}}
* {{Z|ru}}: {{W|ru|tr=Jevrovídenie|Евровидение|n}}
* {{Z|es}}: {{W|es|Eurovision|f}}
{{werger-bin}}
rvn7zj0u2szhqqe0c74xhnt4uspfzta
adarok
0
63368
6865633
6808071
2026-10-24T06:13:03Z
~2026-56659-35
15498
Kurmancî/Navdêr: maneyeke nû hate zêdekirin (DefSideBoxes)
6865633
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{f|ku|devokî}} [[êvar]]a [[newroz]]ê, [[20.3]]
# [[nav|Navê]] [[kulîlkek|kulîlkekê]] ye. [[jê re|Jê re]] [[gula nîsanê]] jî tê [[gotîn|gotîn]].
[[Bînêre|Bînêre]] : [[ferheng|Ferhenga]] [[navên|Navên]] [[nebat|Nebatan]] A [[Kurdî|Kurdî]].
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{etîmon|ku|:qertaf|adar|-ok}}
==== Bide ber ====
* [[savarok]]
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|papaver}}
* {{Z|nb}}: {{W+|nb|valmue}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aadkor|akorda|daroka}}
m8pub11cg8fsynvdmvbtnfnh24i44n5
6865634
6865633
2026-10-24T06:16:33Z
~2026-56659-35
15498
Kurmancî/Navdêr: maneyeke nû hate zêdekirin (DefSideBoxes)
6865634
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{f|ku|devokî}} [[êvar]]a [[newroz]]ê, [[20.3]]
# [[nav|Navê]] [[kulîlkek|kulîlkekê]] ye. [[jê re|Jê re]] [[gula nîsanê]] jî tê [[gotîn|gotîn]].
# Dî [[gotînên|gotînên]] [[pêşî|pêşîyan]] de: [[adarok|Adarokê]] [[ser|serê]] [[xwe|xwe]] [[hilda|hilda]], [[zivistan|zivistanê]] [[bar|barê]] xwe [[girêda|girêda]].
[[Bînêre|Bînêre]] : [[ferheng|Ferhenga]] [[navên|Navên]] [[nebat|Nebatan]] A [[Kurdî|Kurdî]].
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{etîmon|ku|:qertaf|adar|-ok}}
==== Bide ber ====
* [[savarok]]
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|papaver}}
* {{Z|nb}}: {{W+|nb|valmue}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aadkor|akorda|daroka}}
6jcd8n97co0cja63n95cbfut49totoa
6865641
6865634
2026-10-24T08:12:07Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6865641
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{f|ku|devokî}} [[êvar]]a [[newroz]]ê, [[20.3]]
# [[nav|Navê]] [[kulîlkek]]ê ye. [[jê re|Jê re]] [[gula nîsanê]] jî tê [[gotîn]].
# Dî [[gotînên]] [[pêşî]]yan de: [[adarok|Adarokê]] [[ser]]ê [[xwe]] [[hilda]], [[zivistan]]ê [[bar]]ê xwe [[girêda]].
[[Bînêre]] : [[ferheng|Ferhenga]] [[navên|Navên]] [[nebat|Nebatan]] A [[Kurdî]].
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{etîmon|ku|:qertaf|adar|-ok}}
==== Bide ber ====
* [[savarok]]
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|papaver}}
* {{Z|nb}}: {{W+|nb|valmue}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=aadkor|akorda|daroka}}
0dr97lhmbk4zz9aslu8p4w6r88is0mx
MediaWiki:Gadgets-definition
8
118586
6865548
6865428
2026-10-23T20:08:10Z
Wikihez
7344
wikt.translation-adder depends on wikt.edit for the shared language dropdown
6865548
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.edit]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
fxxq1znf1c06hmuvba9se6h9ti7e941
6865555
6865548
2026-10-23T20:16:16Z
Wikihez
7344
Register wikt.suggest; wikt.edit and wikt.translation-adder depend on it
6865555
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package]|wikt.suggest.js|wikt.suggest.css
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.edit,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
m9s8iiqocks5el5kt32sxrcpsvgm2xr
6865559
6865555
2026-10-23T20:19:01Z
Wikihez
7344
wikt.translation-adder no longer loads wikt.edit, only the small wikt.suggest
6865559
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package]|wikt.suggest.js|wikt.suggest.css
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
dzqclrtnomneiyene4jlmclae4a7kjc
6865566
6865559
2026-10-23T20:34:01Z
Wikihez
7344
wikt.suggest depends on mediawiki.util (it now loads the language list)
6865566
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
pymy9p1f9mc22k0qlcn6987ldc4x1sm
6865615
6865566
2026-10-23T22:09:06Z
Wikihez
7344
Register wikt.dsb-sense, wikt.dsb-subsection and wikt.dsb-pos
6865615
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
1mxmg40kodvcj2cimwgv50pmiya95wg
6865618
6865615
2026-10-23T22:09:51Z
Wikihez
7344
wikt.dsb-panels: only the dependencies the queue needs
6865618
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
f1t27cqh5tw1bfzk4qifrrxk3phe7md
6865622
6865618
2026-10-23T22:15:22Z
Wikihez
7344
Register wikt.edit-data; wikt.edit depends on it
6865622
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit-data[hidden|package]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/data-main.json|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
r78d5ntux4ypw3oqug92kmyzju34phk
6865626
6865622
2026-10-23T22:15:42Z
Wikihez
7344
wikt.edit: data-main.json now comes from wikt.edit-data
6865626
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit-data[hidden|package]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
l7k48xgtnvi34o9lnz2n975srfaqwf9
6865629
6865626
2026-10-23T22:38:49Z
Wikihez
7344
Register wikt.start-wizard-steps and wikt.start-wizard-styles; the wizard depends on them
6865629
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit-data[hidden|package]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard-styles[hidden|package]|wikt.start-wizard-styles.js|wikt.start-wizard.css
* wikt.start-wizard-steps[hidden|package]|wikt.start-wizard-steps.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.wikt.start-wizard-steps,ext.gadget.wikt.start-wizard-styles]|wikt.start-wizard.js|wikt.start-wizard.css
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
r94ffazss457oxsbaeyqm5i9c337r8b
6865631
6865629
2026-10-23T22:39:08Z
Wikihez
7344
wikt.start-wizard: the stylesheet now comes from wikt.start-wizard-styles
6865631
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit-data[hidden|package]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard-styles[hidden|package]|wikt.start-wizard-styles.js|wikt.start-wizard.css
* wikt.start-wizard-steps[hidden|package]|wikt.start-wizard-steps.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.wikt.start-wizard-steps,ext.gadget.wikt.start-wizard-styles]|wikt.start-wizard.js
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
0xvj35wass19btg5uprwtpr2b0hdf0l
6865639
6865631
2026-10-24T07:48:16Z
Wikihez
7344
Temporary: test whether a CSS-only gadget with type=general can be a dependency
6865639
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit-data[hidden|package]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard-styles[hidden|package]|wikt.start-wizard-styles.js|wikt.start-wizard.css
* wikt.typetest[hidden|type=general]|wikt.start-wizard.css
* wikt.start-wizard-steps[hidden|package]|wikt.start-wizard-steps.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.wikt.start-wizard-steps,ext.gadget.wikt.start-wizard-styles]|wikt.start-wizard.js
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
p72g7wkxi6avwm8aeubl1ptfvqi0zyy
6865640
6865639
2026-10-24T07:49:01Z
Wikihez
7344
wikt.start-wizard-styles: a CSS-only module (type=general) instead of a placeholder script; remove the test line
6865640
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.edit-data[hidden|package]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard-styles[hidden|type=general]|wikt.start-wizard.css
* wikt.start-wizard-steps[hidden|package]|wikt.start-wizard-steps.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.wikt.start-wizard-steps,ext.gadget.wikt.start-wizard-styles]|wikt.start-wizard.js
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
d7hyxtud5labfuqv3pd45b8p1834s42
6865645
6865640
2026-10-24T08:45:53Z
Wikihez
7344
Register wikt.lang-metadata; wikt.edit-data and wikt.translation-adder depend on it
6865645
wikitext
text/x-wiki
Binêre [[Taybet:Gadgets]] ji bo belgekirina amûrên li jêr.
* LegacyScripts[default|dependencies=site,mediawiki.util,mediawiki.cookie,ext.gadget.LegacyScriptsNewNode,ext.gadget.StorageUtils|type=general]|legacy.js
==Rû==
* LiveClock[dependencies=mediawiki.util]|LiveClock.js
* charinsert-styles[hidden|type=styles]|charinsert-styles.css
* LanguagesAndScripts[default]|LanguagesAndScripts.css
* jeder[dependencies=ext.gadget.VisibilityToggles|default]|jeder.css|jeder.js
* CodeLinks[default|dependencies=site,mediawiki.util]|CodeLinks.js
* Tabset[default]|Tabset.js|Tabset.css
* kuTewandin[default]|kuTewandin.js
* wikt.orange-links[dependencies=mediawiki.api,mediawiki.util,mediawiki.Title]|wikt.orange-links.js|wikt.orange-links.css
* wikt.showids[package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util]|wikt.showids.js|wikt.showids.css
* wikt.targeted-translations[default|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.storage]|wikt.targeted-translations.js|wikt.targeted-translations.css
* EtytreeZoom[default|dependencies=mediawiki.util]|EtytreeZoom.js
* PagePreviews[default|dependencies=site,mediawiki.api,mediawiki.util,mediawiki.Title|skins=vector,vector-2022,monobook,timeless,modern,cologneblue]|PagePreviews.js
* AutoRedirect[default|dependencies=user,mediawiki.util|actions=view|namespaces=0]|AutoRedirect.js
* TreeDescs[default|namespaces=0,108|peers=TreeDescs-pagestyles]|TreeDescs.js
==AmûrênGuhartinê==
* wikEd|wikEd.js
* charinsert[default|peers=charinsert-styles]|charinsert.js
* translation_editor[dependencies=mediawiki.notification|type=general]|silent_fail_storage.js|editor.js|translation_editor.js|translation_editor.css
* QQ[dependencies=mediawiki.util,oojs-ui-core,oojs-ui-widgets|type=general]|QQ.js|QQ.css
* WiktAccFormCreation[dependencies=mediawiki.util]|WiktAccFormCreation.js
* AjaxEdit[dependencies=mediawiki.util,mediawiki.user,mediawiki.api]|AjaxEdit.js
* EditHere[dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets]|EditHere.js
* clearer-edit-summary[default|actions=view]|clearer-edit-summary.js
* DeluxeHistory[dependencies=user,mediawiki.util]|DeluxeHistory.js|DeluxeHistory.css
* JumpToError[dependencies=mediawiki.notification]|JumpToError.js
* aWa[rights=autoconfirmed|dependencies=mediawiki.util,mediawiki.api,mediawiki.user,user.options,mediawiki.Title|type=general]|aWa.js|aWa.css
* purgetab[dependencies=mediawiki.api,mediawiki.util]|purgetab.js
* start[default|package|namespaces=0,2,4,108|dependencies=mediawiki.util,ext.gadget.wikt.core]|start.js
* wikt.dsb[default|package|namespaces=0,2,4,108|actions=view|dependencies=mediawiki.util,ext.gadget.wikt.core]|wikt.dsb.js|wikt.dsb.css
* linkify-toolbar[default|actions=edit,submit|dependencies=ext.gadget.linkify,jquery.textSelection,ext.wikiEditor]|linkify-toolbar.js
==Kategorî==
* HotCat|HotCat.js
* Cat-a-lot[rights=autoconfirmed]|Cat-a-lot.css|Cat-a-lot.js
==AmûrênVeşartî==
* LegacyScriptsNewNode[hidden]|LegacyScriptsNewNode.js
* StorageUtils[hidden|dependencies=mediawiki.cookie,mediawiki.storage]|StorageUtils.js
* charinsert-core[hidden|dependencies=jquery.textSelection,user,mediawiki.storage]|charinsert-core.js
* VisibilityToggles[hidden|dependencies=user,mediawiki.cookie,mediawiki.storage]|VisibilityToggles.js
* defaultVisibilityToggles[hidden|default|dependencies=ext.gadget.VisibilityToggles]|defaultVisibilityToggles.js
* Site[hidden|default]|Site.css
* catfix[hidden|default|dependencies=mediawiki.Title,mediawiki.util|categories=Pages using catfix]|catfix.js
* Palette[hidden|default]|Palette.css
* test[rights=autoconfirmed]|test.js
* citoid-helper[hidden|dependencies=mediawiki.util]|citoid-helper.js
* wikt.core[hidden|package|dependencies=mediawiki.util,mediawiki.storage]|wikt.core.js|wikt.core/sections.js|wikt.core/prefs.js|wikt.core/data-sections.json|wikt.core.css
* wikt.suggest[hidden|package|dependencies=mediawiki.util]|wikt.suggest.js|wikt.suggest.css
* wikt.lang-metadata[hidden|package]|wikt.lang-metadata.js|wikt.lang-metadata.json
* wikt.edit-data[hidden|package|dependencies=ext.gadget.wikt.lang-metadata]|wikt.edit-data.js|wikt.edit/data-main.json
* wikt.edit[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.suggest,ext.gadget.wikt.edit-data]|wikt.edit.js|wikt.edit/util.js|wikt.edit/wikitext.js|wikt.edit/langs.js|wikt.edit/labels.js|wikt.edit/posparams.js|wikt.edit/ziman-core.json|wikt.edit.css
* wikt.cite[hidden|package|dependencies=mediawiki.api,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.citoid-helper,ext.gadget.wikt.edit]|wikt.cite.js|wikt.cite/etimon.js|wikt.cite.css
* wikt.dsb-panels[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit]|wikt.dsb-panels.js|wikt.dsb-panels.css
* wikt.dsb-sense[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,ext.gadget.wikt.add-examples,oojs-ui-windows]|wikt.dsb-sense.js
* wikt.dsb-subsection[hidden|package|dependencies=ext.gadget.wikt.dsb-panels,ext.gadget.wikt.cite,oojs-ui-windows]|wikt.dsb-subsection.js
* wikt.dsb-pos[hidden|package|dependencies=ext.gadget.wikt.dsb-panels]|wikt.dsb-pos.js
* wikt.start-wizard-styles[hidden|type=general]|wikt.start-wizard.css
* wikt.start-wizard-steps[hidden|package]|wikt.start-wizard-steps.js
* wikt.start-wizard[hidden|package|dependencies=mediawiki.util,mediawiki.api,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite,ext.gadget.StorageUtils,ext.gadget.citoid-helper,oojs-ui-core,oojs-ui-widgets,oojs-ui-windows,ext.gadget.wikt.start-wizard-steps,ext.gadget.wikt.start-wizard-styles]|wikt.start-wizard.js
* wikt.add-examples[hidden|package|dependencies=oojs,oojs-ui,ext.gadget.citoid-helper,ext.gadget.StorageUtils,ext.gadget.wikt.core,ext.gadget.wikt.edit,ext.gadget.wikt.cite]|wikt.add-examples.js|wikt.add-examples.css
* wikt.translation-adder[hidden|package|dependencies=mediawiki.util,mediawiki.api,mediawiki.storage,ext.gadget.StorageUtils,ext.gadget.wikt.suggest,ext.gadget.wikt.lang-metadata]|wikt.translation-adder.js|wikt.translation-adder/editor.js|wikt.translation-adder/language-utils.js|wikt.translation-adder/data.js|wikt.translation-adder.css
* linkify[hidden|dependencies=mediawiki.api]|linkify.js
* TreeDescs-pagestyles[hidden|type=styles]|TreeDescs-pagestyles.css
305e67pfr1qa9nxompikiiujrhzy2lw
derneksel
0
131773
6865594
5797009
2026-10-23T20:54:05Z
Wikihez
7344
Tirkî: beşa «Mane» bû «Rengdêr» (DefSideBoxes)
6865594
wikitext
text/x-wiki
== {{ziman|tr}} ==
=== Rengdêr ===
{{rengdêr|tr}}
# [[komeleyî]]
kbpc1diq9j3quxzmk5d58c4i12sn33c
epigrafi
0
134949
6865608
6606403
2026-10-23T21:45:11Z
Wikihez
7344
Tirkî/Navdêr: beşa «Etîmolojî» hate zêdekirin (DefSideBoxes)
6865608
wikitext
text/x-wiki
{{h-bnr|epîgrafî}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[epîgrafî]]
==== Etîmolojî ====
{{etîmon|tr|:deyn|fr:épigraphie}}
d4e8hz6xi7tzltvazgi0z16cblzx737
kalın
0
145680
6865530
6725692
2026-10-23T19:33:56Z
Wikihez
7344
Tirkî/Rengdêr: beşa «Etîmolojî» hate zêdekirin (DefSideBoxes)
6865530
wikitext
text/x-wiki
{{h-bnr|Kalin|kalin|kalín|kalîn}}
== {{ziman|tr}} ==
=== Bilêvkirin ===
* {{deng|tr|LL-Q256 (tur)-ToprakM-kalın.wav|Deng|dever=Îzmîr, Tirkiye}}
=== Rengdêr ===
{{rengdêr|tr}}
# [[stûr]], [[qalind]]
# [[qalind]] (ji bo tiştên pehn)
#: {{mk|tr|'''Kalın''' kâğız.|Kaxizê qalind.|rêz=1}}
# [[stûr]] (ji bo tiştên tîk û gilover)
#: {{mk|tr|'''Kalın''' bir sütün.|Stûneke stûr.|rêz=1}}
# [[stûr]] (ji bo tiştê biberahî û gur)
#: {{mk|tr|'''Kalın''' kaşlar.|Biriyên qalind.|rêz=1}}
# [[tir]]
==== Etîmolojî ====
{{etîmon|tr|:mîrasmayî|ota:قالن<tr:kalın><etimon:mîrasmayî<trk-pro:*kalïŋ>>}}
==== Anagram ====
* {{anagram|tr|a=aklnı|alkın|anlık|kanlı}}
2kubbkc0fppnyquqe41bltfpcpo1wee
kanlı
0
146128
6865544
6442165
2026-10-23T19:56:58Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: sererastkirin, [[Bikarhêner:Wikihez/templateScript2.js|TS]]: +[[Şablon:îzah|îzah]], [[Bikarhêner:Wikihez/templateScript2.js|TS]]: +[[Şablon:îzah|îzah]]
6865544
wikitext
text/x-wiki
== {{ziman|tr}} ==
=== Rengdêr ===
{{rengdêr|tr}}
# [[bixwîn]], [[xwîndar]], [[xwînbar]], [[xwînewî]] {{îzah|tiştê ku xwîn tê geriyaye}}
#: {{mk|tr|'''Kanlı''' elbise.|Kincê bixwîn.|rêz=1}}
# [[bixwîn]], [[xwîndar]] {{îzah|tiştê ku xwîn tê de heye}}
#: {{mk|tr|'''Kanlı''' et.|Goştê xwîndar.|rêz=1}}
# [[bixwîn]], [[xwîndar]], [[xwînrij]] {{îzah|tiştê ku dibe sedemê xwînrijandinê}}
#: {{mk|tr|'''Kanlı''' bir savaş.|Şerekî xwîndar.|rêz=1}}
# [[xwînî]], [[xwîndar]], xwînrij {{îzah|kesê kujer, [[qatil]]}}
# [[bixwîn]], [[xwîndar]], [[xwînîbûyî]] {{îzah|tiştê ku xwînî bûye}}
#: {{mk|tr|'''Kanlı''' göz.|Çavê bixwîn.|rêz=1}}
# [[bixwîn]], xwîndar {{îzah|kesê ku xwîna wî heye}}
#: {{mk|tr|'''Kanlı''' adam.|Mirovê bixwîn.|rêz=1}}
==== Anagram ====
* {{anagram|tr|a=aklnı|alkın|anlık|kalın}}
ocab0jtbbev0rrym7c0ypic0m1xsg2a
çiğ iplik
0
175342
6865610
4672728
2026-10-23T22:02:38Z
Wikihez
7344
Tirkî/Navdêr: mane hate guhartin (DefSideBoxes)
6865610
wikitext
text/x-wiki
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[ta|tayê]] [[xav|xav]]
nkfxspfrdqbu41elypihaxqnoppujw5
6865632
6865610
2026-10-23T23:12:10Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6865632
wikitext
text/x-wiki
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# [[ta]]yê [[xav]]
ihmuhh6fzcb08qob97hkt29rwlhxcp4
sittenlos
0
245790
6865652
4687898
2026-10-24T09:29:51Z
Jeuwre
4694
bilêvkirin
6865652
wikitext
text/x-wiki
== {{ziman|de}} ==
=== Bilêvkirin ===
* {{IPA|de|[ˈzɪtn̩ˌloːs]}}
* {{deng|de|De-sittenlos.ogg|Deng|dever=Berlîn, Almanya}}
* {{kîte|de|sit|ten|los}}
=== Rengdêr ===
{{rengdêr|de}}
# [[bêexlaqî]], [[ne exlaqî]], ne etîk
bsztqlxygqfansanhs1hca5rofj3rpi
sernivîs
0
265410
6865638
6392677
2026-10-24T07:44:11Z
Wikihez
7344
Kurmancî/Çavkanî: sernav ji h3 bû h4 (DefSideBoxes)
6865638
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[sergotar]]<ref>{{Ferhenga Kamêran}}</ref>
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Aufsatz}} {{n}}
* {{Z|en}}: {{W+|en|editorial}}, {{W-|en|banner headline|guman=erê}}, {{W+|en|superscription}}
* {{Z|tr}}: {{W+|tr|bap}}, {{W+|tr|serlevha}}, {{W-|tr|yazı başlığı}}, {{W+|tr|alın yazısı}}, {{W+|tr|kader}}, {{W-|tr|baş yazı}}, {{W+|tr|başlık}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=eiînrssv|servîsin}}
==== Çavkanî ====
{{çavkanî}}
dn9uo562d31y3d3uehgk82yldju03ge
6865650
6865638
2026-10-24T09:12:06Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk ({{ku-tewîn-nav/nû}} → {{ku-tewîn-nav}}.)
6865650
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[sergotar]]<ref>{{Ferhenga Kamêran}}</ref>
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Aufsatz}} {{n}}
* {{Z|en}}: {{W+|en|editorial}}, {{W-|en|banner headline|guman=erê}}, {{W+|en|superscription}}
* {{Z|tr}}: {{W+|tr|bap}}, {{W+|tr|serlevha}}, {{W-|tr|yazı başlığı}}, {{W+|tr|alın yazısı}}, {{W+|tr|kader}}, {{W-|tr|baş yazı}}, {{W+|tr|başlık}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=eiînrssv|servîsin}}
==== Çavkanî ====
{{çavkanî}}
csngav3s2vp8j2azdilhmq26xcalru7
بابەت
0
268325
6865637
6342274
2026-10-24T07:43:44Z
Wikihez
7344
Soranî/Navdêr: mane hate guhartin (DefSideBoxes)
6865637
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Navdêr ===
{{navdêr|ckb}}
# [[mijar]], [[babet]] {{îzah|[[tişt|tiştê]] ku [[li ser]] tê [[axaftin|axaftin]], [[nivîsandin|nivîsandin]] an [[nîqaşkirin|nîqaşkirin]]}}
#: {{mînak|ckb|بابەتی ئەمڕۆ زۆر گرنگە.|tr=Babetî emro zor giringe.|werger=Mijara îro pir girîng e.}}
#: {{mînak|ckb|ئێمە باسی بابەتێکی نوێ دەکەین.|tr=Ême basî babetêkî nwê dekeyn.|werger=Em behsa mijareke nû dikin.}}
==== Hevmane ====
* {{g|ckb|مژار}}
* {{g|ckb|باس}}
==== Têkildar ====
* {{g|ckb|سەردێڕ}}
* {{g|ckb|گفتوگۆ}}
9xoopvxatmpi5nxghjgju033xktkmvd
6865651
6865637
2026-10-24T09:12:09Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6865651
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Navdêr ===
{{navdêr|ckb}}
# [[mijar]], [[babet]] {{îzah|[[tişt]]ê ku [[li ser]] tê [[axaftin]], [[nivîsandin]] an [[nîqaşkirin]]}}
#: {{mînak|ckb|بابەتی ئەمڕۆ زۆر گرنگە.|tr=Babetî emro zor giringe.|werger=Mijara îro pir girîng e.}}
#: {{mînak|ckb|ئێمە باسی بابەتێکی نوێ دەکەین.|tr=Ême basî babetêkî nwê dekeyn.|werger=Em behsa mijareke nû dikin.}}
==== Hevmane ====
* {{g|ckb|مژار}}
* {{g|ckb|باس}}
==== Têkildar ====
* {{g|ckb|سەردێڕ}}
* {{g|ckb|گفتوگۆ}}
sftk5g085f7lgnr9u6q6tig8dgdvou1
ھەزبێلە
0
270020
6865601
6533030
2026-10-23T21:05:10Z
Wikihez
7344
Soranî/Navdêr: mane hate guhartin (DefSideBoxes)
6865601
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Bilêvkirin ===
:hezbêle
=== Navdêr ===
{{navdêr|ckb}}
# [[giyaderman]], [[davûderman]] {{îzah|[[giya|giyayên]] [[wek|wek]] [[derman|derman]] [[hatin|tên]] [[bi kar anîn|bikaranîn]]}}
0cym5eh078igr7nldmucgray8zpvc4w
6865619
6865601
2026-10-23T22:12:10Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6865619
wikitext
text/x-wiki
== {{ziman|ckb}} ==
=== Bilêvkirin ===
:hezbêle
=== Navdêr ===
{{navdêr|ckb}}
# [[giyaderman]], [[davûderman]] {{îzah|[[giya]]yên [[wek]] [[derman]] [[hatin|tên]] [[bi kar anîn|bikaranîn]]}}
9fr2uihvahqabkfjhe19xfdcosq4ypd
upend
0
277819
6865595
6091025
2026-10-23T20:55:06Z
Wikihez
7344
Îngilîzî: beşa «Mane» bû «Lêker» (DefSideBoxes)
6865595
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Lêker ===
{{lêker|en}}
# [[rakirin]], çik sekinandina tiştekî
# [[bi ser xwe de kirin]] (qedeh, [[ piyal)]]
# [[serberjêr]] kir, [[qulîfatî kirin]]
# [[rêtin]], [[vala kirin]], [[rayêxistin]]
=== Bilêvkirin ===
* {{deng|en|en-us-upend.ogg|dever=DYA}}
llqmrri0wis9rwetilwtjjhe2gog42p
6865596
6865595
2026-10-23T20:55:17Z
Wikihez
7344
/* Îngilîzî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865596
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Bilêvkirin ===
* {{deng|en|en-us-upend.ogg|dever=DYA}}
=== Lêker ===
{{lêker|en}}
# [[rakirin]], çik sekinandina tiştekî
# [[bi ser xwe de kirin]] (qedeh, [[ piyal)]]
# [[serberjêr]] kir, [[qulîfatî kirin]]
# [[rêtin]], [[vala kirin]], [[rayêxistin]]
g6tuaw8l8zu32h2fp92or87ojh22i3m
RT
0
295466
6865584
6811664
2026-10-23T20:43:18Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865584
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Kurtenav ===
{{kurtenav|ku}}
# [[kurt]]kirina "[[retwît]], [[retwît]] kirin, [[vetwît]] kirin, bi [[twîter]]ê dîsa [[belav]] kirin"
#* {{jêder|ku|jêgirtin=Yên ku ev twîta min hê jî '''RT''' nekiriye, bila herin destnimêja xwe bigirin û bi destê rastê '''RT''' bikin, hevalên hêja.|Kesekî bi bernavê Egîdo li ser Twitterê.}}
==== Etîmolojî ====
{{etîmon|ku|:deyn|en:RT|metnna=1}}
Deyn ji {{deyn|en|ku|RT}}, kurteya [[retweet]]
==== Werger ====
{{werger-ser}}
* {{Z|en}}: [[RT]], [[retweet]]
{{werger-bin}}
== {{ziman|en}} ==
=== Navdêr ===
{{navdêr|en}}
# (Înternet) [[retweet]]
53swujabx9k1a4hzc1ngde3ma70dm8k
offer resistance
0
314314
6865531
5854387
2026-10-23T19:38:15Z
Wikihez
7344
Îngilîzî: beşa «Mane» bû «Lêker» (DefSideBoxes)
6865531
wikitext
text/x-wiki
== {{ziman|en}} ==
=== Lêker ===
{{lêker|en}}
# li ber rabûn. tewqe, [[teklîf]]
# tewqeya buhayê
4iuhycisrvl8pctpohddjcdbir324en
جهت دادن
0
368476
6865611
5866643
2026-10-23T22:05:24Z
Wikihez
7344
Farisî: beşa «Mane» bû «Lêker» (DefSideBoxes)
6865611
wikitext
text/x-wiki
== {{ziman|fa}} ==
=== Lêker ===
{{lêker|fa}}
# [[beralî kirin]]
eqruhi3fzn091xr0gjl2bmh3go31f05
wêr
0
380634
6865636
6841415
2026-10-24T07:42:35Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6865636
wikitext
text/x-wiki
{{h-bnr|-wer|-wer-|wer|wer-}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[îflas]], [[topavêtin]]
==== Etîmolojî ====
{{ji?|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|iflas}}
{{werger-bin}}
==== Anagram ====
* {{anagram|ku|a=êrw|êwr}}
== {{ziman|fy}} ==
=== Hoker ===
{{hoker|fy}}
# {{g|ku|ka}}, {{g|ku|li kû}}, {{g|ku|li kû derê}}
=== Çavkanî ===
{{rûpela wergerê|fy|ka}}
jhdeqxm4qpmjdx2sf1zjj74lurld3s4
qeşawîkirin
0
428223
6865533
6701801
2026-10-23T19:41:21Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865533
wikitext
text/x-wiki
{{h-bnr|qeşawî kirin}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[meheskirin]]
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{etîmon|ku|:qf|qeşawî|kirin}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|kaşağılama}}
{{werger-bin}}
qncws7kjjepkk3276w75tcco0t0f6rh
tirsayî
0
429890
6865635
6699539
2026-10-24T07:41:02Z
Wikihez
7344
Kurmancî/Navdêr: beşa «Etîmolojî» hate nûkirin (DefSideBoxes)
6865635
wikitext
text/x-wiki
{{h-bnr|tirşayî}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[toqandî]], [[bizdihayî]], [[çavtirs]], [[bizdihî]], [[lerzok]]
==== Etîmolojî ====
{{etîmon|ku|:qertaf|tirs|-ayî}}
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|panicked}}, {{W+|en|terrified}}, {{W+|en|tremulant}}, {{W+|en|tremulent}}, {{W+|en|funky}}, {{W+|en|terror-stricken}}, {{W+|en|awestricken|guman=erê}}, {{W+|en|chickened|guman=erê}}, {{W+|en|feared|guman=erê}}, {{W+|en|yellowed|guman=erê}}, {{W-|en|hand-dug|guman=erê}}, {{W+|en|have one's heart in one's boots|guman=erê}}, {{W-|en|panic stricken|guman=erê}}, {{W+|en|panic-stricken|guman=erê}}, {{W-|en|panic-struck|guman=erê}}, {{W-|en|with the tail between the legs|guman=erê}}
{{werger-bin}}
og0rmvcpldtlv78rm57nfo5l7yx0t5i
qeşawî kirin
0
433999
6865532
6701386
2026-10-23T19:40:52Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865532
wikitext
text/x-wiki
{{h-bnr|qeşawîkirin}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Lêker ===
{{lêker|ku|c=h-gh}}
# [[tîmar kirin]]
==== Etîmolojî ====
{{etîmon|ku|:qf|qeşawî|kirin}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|kaşağılamak}}
{{werger-bin}}
d3ythfzriruz6ruhk2m52q8kbxo6891
bikeyskirin
0
435855
6865560
6849265
2026-10-23T20:29:15Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865560
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{mane?|ku}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
==== Etîmolojî ====
{{etîmon|ku|:qertaf|bikeys|kirin}}
==== Werger ====
{{werger-ser}}
* {{Z|tr}}: {{W+|tr|düzeltme}}, {{W+|tr|düzenleme}}, {{W+|tr|tertip}}, {{W+|tr|toparlama}}, {{W+|tr|toplama}}, {{W+|tr|toplayış}}, {{W+|tr|yerleştirme}}, {{W+|tr|tensikat|guman=erê}}
{{werger-bin}}
5y5mvkiar8231aq0jlxnwfegz2k5s8a
sî û pênc
0
480433
6865605
6709577
2026-10-23T21:27:10Z
Wikihez
7344
Kurmancî/Hejmar: ±Tirkî (DefSideBoxes)
6865605
wikitext
text/x-wiki
{{h-bnr|sî û penc}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Hejmar ===
{{hejmar|ku}}
# [[35]], [[XXXV]]
==== Etîmolojî ====
Ji ''[[sî]] + [[û]] + [[pênc]]''
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|fünfunddreißig}}
* {{Z|tr}}: {{W+|tr|otuz beş}}
{{werger-bin}}
q8v2gbwseeun8jppvz9io6clhsx4sz5
Modul:ku-bilêvkirin
828
517780
6865516
6328395
2026-10-23T14:53:44Z
Wikihez
7344
Velar nasal after syllabification (ferheng, banka), [i] before y within a syllable, drop a rule that never matched
6865516
Scribunto
text/plain
--[[
================================================================================
Kurdish IPA Pronunciation Module
================================================================================
Description:
This module converts Kurdish words to IPA (International Phonetic Alphabet)
notation and provides syllabification for Kurdish text.
Supported Dialects:
- Kurmanji (Northern Kurdish) - Standard Latin alphabet
- Sorani (Central Kurdish) - Additional letters: ẍ, ḧ, ʿ, '
- Southern Kurdish (Xwarîn/Feylî) - Additional letters: ł, ň, ř, ü
- Arabic loanwords - Emphatic consonants: ṣ, ṭ, ẓ
Main Functions:
- export.IPA(frame) - Converts words to IPA notation with stress marks
- export.kite(frame) - Returns syllabified word with visible breaks
Authors:
- Bikarhêner:Ghybu
- Bikarhêner:Şêr
- Bikarhêner: Wikihez
Version: 2.2
Last Updated: 2026-10-23
================================================================================
--]]
local export = {}
local b = require("Modul:bingeh")
-- ============================================================================
-- CONSTANTS
-- ============================================================================
-- Consonant clusters that need special handling during syllabification
local CONSONANT_CLUSTERS = {
["xw"] = "Ꭓ",
["rr"] = "Ꮢ",
["ll"] = "Ꮮ"
}
-- The placeholder characters from CONSONANT_CLUSTERS must be included in
-- CONSONANT_PATTERN, otherwise words like "berxwedan" get syllabified
-- incorrectly (e.g. "berxwe·dan" instead of "ber·xwe·dan"), because the
-- pattern does not recognize the placeholder as a consonant.
local CLUSTER_PLACEHOLDERS = "Ꭓ" .. "Ꮢ" .. "Ꮮ"
-- Vowel and consonant definitions
local VOWELS = "aeiîuûêoAEÊIÎOUÛ"
local CONSONANTS = "bcçdfghjklmnpqrsştvwxyzẍḧłňřüṣṭẓBCÇDFGHJKLMNPQRSŞTVWXYZẌḦŁŇŘÜṢṬẒʿ'" .. CLUSTER_PLACEHOLDERS
local VOWEL_PATTERN = "[" .. VOWELS .. "]"
local CONSONANT_PATTERN = "[" .. CONSONANTS .. "]"
local SYLLABLE_PATTERN = "(" .. VOWEL_PATTERN .. ")(" .. CONSONANT_PATTERN .. "?)(" .. CONSONANT_PATTERN .. "?)(" .. VOWEL_PATTERN .. ")"
-- Syllabification iterations
local MAX_SYLLABIFICATION_ITERATIONS = 2
-- IPA phoneme mappings for Kurdish (Latin alphabet)
local PHONEME_MAP = {
-- Basic vowels
["a"] = "ɑː",
["e"] = "ɛ",
["ê"] = "eː",
["i"] = "ɘ",
["î"] = "iː",
["o"] = "oː",
["u"] = "ʊ",
["û"] = "uː",
-- Consonants
["b"] = "b",
["c"] = "d͡ʒ",
["ç"] = "t͡ʃ",
["d"] = "d",
["f"] = "f",
["g"] = "ɡ",
["h"] = "h",
["j"] = "ʒ",
["k"] = "k",
["l"] = "l",
["m"] = "m",
["n"] = "n",
["p"] = "p",
["q"] = "q",
["r"] = "ɾ",
["s"] = "s",
["ş"] = "ʃ",
["t"] = "t",
["v"] = "v",
["w"] = "w",
["x"] = "x",
["y"] = "j",
["z"] = "z",
-- Special letters (Sorani)
["ẍ"] = "ɣ", -- example: aẍa
["ḧ"] = "ħ",
["ʿ"] = "ʕ",
["'"] = "ʕ",
-- Southern Kurdish (Xwarîn)
["ł"] = "ɫ", -- see "ll"; example: bał, diɫ
["ň"] = "ŋ", -- example: maň, seň
["ř"] = "r", -- see 'rr'; example: piř
["ü"] = "yː", -- example: tü
-- Non-Kurdish letters (Arabic loanwords)
["ṣ"] = "sˤ", -- ص
["ṭ"] = "tˤ", -- ط
["ẓ"] = "zˤ"
}
-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================
--- Wraps text in no-convert tags for MediaWiki
-- @param text string The text to wrap
-- @return string The wrapped text
local function wrapInNoConvert(text)
return '-{' .. text .. '}-'
end
--- Replaces or restores consonant clusters
-- @param text string The text to process
-- @param reverse boolean If true, restore original clusters; if false, replace with placeholders
-- @return string The processed text
local function processConsonantClusters(text, reverse)
for original, placeholder in pairs(CONSONANT_CLUSTERS) do
if reverse then
text = mw.ustring.gsub(text, placeholder, original)
else
text = mw.ustring.gsub(text, original, placeholder)
end
end
return text
end
--- Adds syllable breaks to a word
-- @param word string The word to syllabify
-- @return string The syllabified word with dots marking syllable boundaries
local function syllabify(word)
-- Replace consonant clusters with placeholders
word = processConsonantClusters(word, false)
-- Replace hyphens between consonants with syllable breaks
word = mw.ustring.gsub(word, "%-(" .. CONSONANT_PATTERN .. ")%-(" .. CONSONANT_PATTERN .. ")", "%1.%2")
word = string.gsub(word, "%-", ".")
-- Add syllable breaks between vowels and consonants
for count = 1, MAX_SYLLABIFICATION_ITERATIONS do
word = mw.ustring.gsub(
word,
SYLLABLE_PATTERN,
function(a, b, c, d)
if c == "" and b ~= "" then
c, b = b, ""
end
return a .. b .. "." .. c .. d
end
)
end
-- Handle consonant clusters within syllables
word = mw.ustring.gsub(
word,
'([' .. VOWELS .. '])([' .. CONSONANTS .. '])([' .. CONSONANTS .. '])([' .. CONSONANTS .. '])',
"%1%2%3.%4"
)
-- Restore original consonant clusters
word = processConsonantClusters(word, true)
return word
end
--- Applies phonological rules to prepare word for IPA conversion
-- @param word string The word to process
-- @return string The processed word
local function applyPhonologicalRules(word)
-- Aspirated consonants
word = mw.ustring.gsub(word, "k%(h%)", "kʰ")
word = mw.ustring.gsub(word, "p%(h%)", "pʰ")
word = mw.ustring.gsub(word, "t%(h%)", "tʰ")
-- Labialized velars
word = mw.ustring.gsub(word, "x%(w%)", "xʷ")
word = mw.ustring.gsub(word, 'xw', 'xʷ') -- example: xwarin
-- Pharyngealized consonants
word = mw.ustring.gsub(word, "%('%)", "ˤ")
-- Velarized lateral
word = mw.ustring.gsub(word, 'll', 'ɫ') -- example: Sorani gull (گوڵ) vs Kurmanji gul
return word
end
--- Applies post-conversion IPA rules
-- @param ipa string The IPA string to process
-- @return string The processed IPA string
local function applyPostConversionRules(ipa)
-- Aspirated affricate
ipa = mw.ustring.gsub(ipa, "t͡ʃ%(h%)", "t͡ʃʰ")
-- Velar nasal before k and g, also across a syllable break; after syllabification since ŋ is not in CONSONANTS
ipa = mw.ustring.gsub(ipa, 'n(%.?[kɡ])', 'ŋ%1') -- example: ferheng, bank, banka
-- Word-initial r is a trill
ipa = mw.ustring.gsub(ipa, '^ɾ', "r") -- example: roj
-- Geminate r is a trill
ipa = mw.ustring.gsub(ipa, 'ɾɾ', 'r') -- example: pirr
-- The letter i is the short close front unrounded vowel [i] in front of y, within or across a syllable
ipa = mw.ustring.gsub(ipa, "ɘ(%.?j)", "i%1") -- example: çiya
return ipa
end
--- Applies stress rules to IPA transcription
-- @param ipa string The IPA string
-- @return string IPA with stress marks, string Category tag if needed
local function applyStressRules(ipa)
local category = ""
-- Check for manual stress marks and convert them
if mw.ustring.find(ipa, "\\ʕ") then
category = "[[Kategorî:Giranî (dij rêgezê)]]"
end
ipa = mw.ustring.gsub(ipa, "\\ʕ", "'")
ipa = mw.ustring.gsub(ipa, '"', "'")
-- Add stress to final syllable if no manual stress present
local words = mw.text.split(ipa, ' ')
for k, v in ipairs(words) do
local syllables = mw.text.split(v, '%.')
if not mw.ustring.find(v, "'") then
if #syllables ~= 1 then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
end
end
words[k] = table.concat(syllables, '')
end
ipa = table.concat(words, ' ')
-- Add default stress mark if not present
if not mw.ustring.find(ipa, "ˈ") then
ipa = "ˈ" .. ipa
end
return ipa, category
end
--- Converts letters to IPA phonemes, preserving unknown characters
-- @param word string The word with letters to convert
-- @return string The word with IPA phonemes
local function convertLettersToIPA(word)
return mw.ustring.gsub(word, '(.)', function(char)
return PHONEME_MAP[char] or char
end)
end
--- Converts a Kurdish word to IPA notation
-- @param word string The word to convert
-- @return table {ipa_string, category_string}
local function wordToIPA(word)
-- Normalize to lowercase
word = mw.ustring.lower(word)
-- Apply phonological rules
word = applyPhonologicalRules(word)
-- Syllabify the word
word = syllabify(word)
-- Convert letters to IPA phonemes (preserving numbers and other chars)
word = convertLettersToIPA(word)
-- Apply post-conversion rules
word = applyPostConversionRules(word)
-- Apply stress rules and get category
local ipa, category = applyStressRules(word)
return {ipa, category}
end
-- ============================================================================
-- EXPORTED FUNCTIONS
-- ============================================================================
--- Main IPA conversion function for MediaWiki templates
-- @param frame table MediaWiki frame object
-- @return string Formatted IPA output with pronunciation key
function export.IPA(frame)
local words = {}
-- Collect words from template arguments
for _, word in ipairs(frame:getParent().args) do
if word and word ~= "" then
table.insert(words, word)
end
end
-- If no arguments, use current page title
if #words == 0 then
local title = mw.title.getCurrentTitle()
if title then
words = {title.text}
else
return "[[Kategorî:IPA hatiye şaşkirin]]"
end
end
-- Convert each word to IPA
local ipaResults = {}
for _, word in ipairs(words) do
local result = wordToIPA(word)
table.insert(ipaResults, result[1] .. result[2])
end
-- Format output
local ipaString = table.concat(ipaResults, '/, /')
return '[[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>: <span class="IPA">/'
.. wrapInNoConvert(ipaString)
.. '/</span>'
.. b.bike_kategori_naverok("Bilêvkirina IPAyê bi kurmancî")
end
--- Returns syllabified version of a word with visible syllable breaks
-- @param frame table MediaWiki frame object
-- @return string The syllabified word with middle dots
function export.kite(frame)
local word
if frame:getParent().args[1] then
word = frame:getParent().args[1]
else
local title = mw.title.getCurrentTitle()
word = title and title.text or ""
end
-- Remove numbers and other non-letter characters before syllabification
word = mw.ustring.gsub(word, '%d', '') -- Remove all digits
-- Syllabify and replace dots with middle dots
local syllabified = syllabify(word)
local result = mw.ustring.gsub(syllabified, '%.', '·')
return result
end
return export
jo2pq34evc0nqczvu7uu9hs4d3ol60z
6865519
6865516
2026-10-23T14:57:23Z
Wikihez
7344
Guhartinên [[Special:Contributions/Wikihez|Wikihez]] ([[User talk:Wikihez|gotûbêj]]) hatine betalkirin, vegerand guhartoya dawî ya [[User:Şêr|Şêr]]
6328395
Scribunto
text/plain
--[[
================================================================================
Kurdish IPA Pronunciation Module
================================================================================
Description:
This module converts Kurdish words to IPA (International Phonetic Alphabet)
notation and provides syllabification for Kurdish text.
Supported Dialects:
- Kurmanji (Northern Kurdish) - Standard Latin alphabet
- Sorani (Central Kurdish) - Additional letters: ẍ, ḧ, ʿ, '
- Southern Kurdish (Xwarîn/Feylî) - Additional letters: ł, ň, ř, ü
- Arabic loanwords - Emphatic consonants: ṣ, ṭ, ẓ
Main Functions:
- export.IPA(frame) - Converts words to IPA notation with stress marks
- export.kite(frame) - Returns syllabified word with visible breaks
Authors:
- Bikarhêner:Ghybu
- Bikarhêner:Şêr
- Bikarhêner: Wikihez
Version: 2.1 (Fix: consonant cluster placeholders are now correctly
recognized as consonants, so words like "berxwedan"
are syllabified as ber·xwe·dan instead of berxwe·dan)
Last Updated: 2026-08-09
================================================================================
--]]
local export = {}
local b = require("Modul:bingeh")
-- ============================================================================
-- CONSTANTS
-- ============================================================================
-- Consonant clusters that need special handling during syllabification
local CONSONANT_CLUSTERS = {
["xw"] = "Ꭓ",
["rr"] = "Ꮢ",
["ll"] = "Ꮮ"
}
-- The placeholder characters from CONSONANT_CLUSTERS must be included in
-- CONSONANT_PATTERN, otherwise words like "berxwedan" get syllabified
-- incorrectly (e.g. "berxwe·dan" instead of "ber·xwe·dan"), because the
-- pattern does not recognize the placeholder as a consonant.
local CLUSTER_PLACEHOLDERS = "Ꭓ" .. "Ꮢ" .. "Ꮮ"
-- Vowel and consonant definitions
local VOWELS = "aeiîuûêoAEÊIÎOUÛ"
local CONSONANTS = "bcçdfghjklmnpqrsştvwxyzẍḧłňřüṣṭẓBCÇDFGHJKLMNPQRSŞTVWXYZẌḦŁŇŘÜṢṬẒʿ'" .. CLUSTER_PLACEHOLDERS
local VOWEL_PATTERN = "[" .. VOWELS .. "]"
local CONSONANT_PATTERN = "[" .. CONSONANTS .. "]"
local SYLLABLE_PATTERN = "(" .. VOWEL_PATTERN .. ")(" .. CONSONANT_PATTERN .. "?)(" .. CONSONANT_PATTERN .. "?)(" .. VOWEL_PATTERN .. ")"
-- Syllabification iterations
local MAX_SYLLABIFICATION_ITERATIONS = 2
-- IPA phoneme mappings for Kurdish (Latin alphabet)
local PHONEME_MAP = {
-- Basic vowels
["a"] = "ɑː",
["e"] = "ɛ",
["ê"] = "eː",
["i"] = "ɘ",
["î"] = "iː",
["o"] = "oː",
["u"] = "ʊ",
["û"] = "uː",
-- Consonants
["b"] = "b",
["c"] = "d͡ʒ",
["ç"] = "t͡ʃ",
["d"] = "d",
["f"] = "f",
["g"] = "ɡ",
["h"] = "h",
["j"] = "ʒ",
["k"] = "k",
["l"] = "l",
["m"] = "m",
["n"] = "n",
["p"] = "p",
["q"] = "q",
["r"] = "ɾ",
["s"] = "s",
["ş"] = "ʃ",
["t"] = "t",
["v"] = "v",
["w"] = "w",
["x"] = "x",
["y"] = "j",
["z"] = "z",
-- Special letters (Sorani)
["ẍ"] = "ɣ", -- example: aẍa
["ḧ"] = "ħ",
["ʿ"] = "ʕ",
["'"] = "ʕ",
-- Southern Kurdish (Xwarîn)
["ł"] = "ɫ", -- see "ll"; example: bał, diɫ
["ň"] = "ŋ", -- example: maň, seň
["ř"] = "r", -- see 'rr'; example: piř
["ü"] = "yː", -- example: tü
-- Non-Kurdish letters (Arabic loanwords)
["ṣ"] = "sˤ", -- ص
["ṭ"] = "tˤ", -- ط
["ẓ"] = "zˤ"
}
-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================
--- Wraps text in no-convert tags for MediaWiki
-- @param text string The text to wrap
-- @return string The wrapped text
local function wrapInNoConvert(text)
return '-{' .. text .. '}-'
end
--- Replaces or restores consonant clusters
-- @param text string The text to process
-- @param reverse boolean If true, restore original clusters; if false, replace with placeholders
-- @return string The processed text
local function processConsonantClusters(text, reverse)
for original, placeholder in pairs(CONSONANT_CLUSTERS) do
if reverse then
text = mw.ustring.gsub(text, placeholder, original)
else
text = mw.ustring.gsub(text, original, placeholder)
end
end
return text
end
--- Adds syllable breaks to a word
-- @param word string The word to syllabify
-- @return string The syllabified word with dots marking syllable boundaries
local function syllabify(word)
-- Replace consonant clusters with placeholders
word = processConsonantClusters(word, false)
-- Replace hyphens between consonants with syllable breaks
word = mw.ustring.gsub(word, "%-(" .. CONSONANT_PATTERN .. ")%-(" .. CONSONANT_PATTERN .. ")", "%1.%2")
word = string.gsub(word, "%-", ".")
-- Add syllable breaks between vowels and consonants
for count = 1, MAX_SYLLABIFICATION_ITERATIONS do
word = mw.ustring.gsub(
word,
SYLLABLE_PATTERN,
function(a, b, c, d)
if c == "" and b ~= "" then
c, b = b, ""
end
return a .. b .. "." .. c .. d
end
)
end
-- Handle consonant clusters within syllables
word = mw.ustring.gsub(
word,
'([' .. VOWELS .. '])([' .. CONSONANTS .. '])([' .. CONSONANTS .. '])([' .. CONSONANTS .. '])',
"%1%2%3.%4"
)
-- Restore original consonant clusters
word = processConsonantClusters(word, true)
return word
end
--- Applies phonological rules to prepare word for IPA conversion
-- @param word string The word to process
-- @return string The processed word
local function applyPhonologicalRules(word)
-- Aspirated consonants
word = mw.ustring.gsub(word, "k%(h%)", "kʰ")
word = mw.ustring.gsub(word, "p%(h%)", "pʰ")
word = mw.ustring.gsub(word, "t%(h%)", "tʰ")
-- Labialized velars
word = mw.ustring.gsub(word, "x%(w%)", "xʷ")
word = mw.ustring.gsub(word, 'xw', 'xʷ') -- example: xwarin
-- Pharyngealized consonants
word = mw.ustring.gsub(word, "%('%)", "ˤ")
-- Velar nasal before g and k
word = mw.ustring.gsub(word, 'nɡ', 'ŋɡ') -- example: ferheng
word = mw.ustring.gsub(word, 'nk', 'ŋk') -- example: bank
-- Velarized lateral
word = mw.ustring.gsub(word, 'll', 'ɫ') -- example: Sorani gull (گوڵ) vs Kurmanji gul
return word
end
--- Applies post-conversion IPA rules
-- @param ipa string The IPA string to process
-- @return string The processed IPA string
local function applyPostConversionRules(ipa)
-- Aspirated affricate
ipa = mw.ustring.gsub(ipa, "t͡ʃ%(h%)", "t͡ʃʰ")
-- Word-initial r is a trill
ipa = mw.ustring.gsub(ipa, '^ɾ', "r") -- example: roj
-- Geminate r is a trill
ipa = mw.ustring.gsub(ipa, 'ɾɾ', 'r') -- example: pirr
-- The letter i standing for the short close front unrounded vowel [i] in front of y
ipa = mw.ustring.gsub(ipa, "ɘ%.j", "i.j")
-- iy sequences become long i
ipa = mw.ustring.gsub(ipa, 'ɪj', 'iː')
return ipa
end
--- Applies stress rules to IPA transcription
-- @param ipa string The IPA string
-- @return string IPA with stress marks, string Category tag if needed
local function applyStressRules(ipa)
local category = ""
-- Check for manual stress marks and convert them
if mw.ustring.find(ipa, "\\ʕ") then
category = "[[Kategorî:Giranî (dij rêgezê)]]"
end
ipa = mw.ustring.gsub(ipa, "\\ʕ", "'")
ipa = mw.ustring.gsub(ipa, '"', "'")
-- Add stress to final syllable if no manual stress present
local words = mw.text.split(ipa, ' ')
for k, v in ipairs(words) do
local syllables = mw.text.split(v, '%.')
if not mw.ustring.find(v, "'") then
if #syllables ~= 1 then
syllables[#syllables] = "ˈ" .. syllables[#syllables]
end
end
words[k] = table.concat(syllables, '')
end
ipa = table.concat(words, ' ')
-- Add default stress mark if not present
if not mw.ustring.find(ipa, "ˈ") then
ipa = "ˈ" .. ipa
end
return ipa, category
end
--- Converts letters to IPA phonemes, preserving unknown characters
-- @param word string The word with letters to convert
-- @return string The word with IPA phonemes
local function convertLettersToIPA(word)
return mw.ustring.gsub(word, '(.)', function(char)
return PHONEME_MAP[char] or char
end)
end
--- Converts a Kurdish word to IPA notation
-- @param word string The word to convert
-- @return table {ipa_string, category_string}
local function wordToIPA(word)
-- Normalize to lowercase
word = mw.ustring.lower(word)
-- Apply phonological rules
word = applyPhonologicalRules(word)
-- Syllabify the word
word = syllabify(word)
-- Convert letters to IPA phonemes (preserving numbers and other chars)
word = convertLettersToIPA(word)
-- Apply post-conversion rules
word = applyPostConversionRules(word)
-- Apply stress rules and get category
local ipa, category = applyStressRules(word)
return {ipa, category}
end
-- ============================================================================
-- EXPORTED FUNCTIONS
-- ============================================================================
--- Main IPA conversion function for MediaWiki templates
-- @param frame table MediaWiki frame object
-- @return string Formatted IPA output with pronunciation key
function export.IPA(frame)
local words = {}
-- Collect words from template arguments
for _, word in ipairs(frame:getParent().args) do
if word and word ~= "" then
table.insert(words, word)
end
end
-- If no arguments, use current page title
if #words == 0 then
local title = mw.title.getCurrentTitle()
if title then
words = {title.text}
else
return "[[Kategorî:IPA hatiye şaşkirin]]"
end
end
-- Convert each word to IPA
local ipaResults = {}
for _, word in ipairs(words) do
local result = wordToIPA(word)
table.insert(ipaResults, result[1] .. result[2])
end
-- Format output
local ipaString = table.concat(ipaResults, '/, /')
return '[[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>: <span class="IPA">/'
.. wrapInNoConvert(ipaString)
.. '/</span>'
.. b.bike_kategori_naverok("Bilêvkirina IPAyê bi kurmancî")
end
--- Returns syllabified version of a word with visible syllable breaks
-- @param frame table MediaWiki frame object
-- @return string The syllabified word with middle dots
function export.kite(frame)
local word
if frame:getParent().args[1] then
word = frame:getParent().args[1]
else
local title = mw.title.getCurrentTitle()
word = title and title.text or ""
end
-- Remove numbers and other non-letter characters before syllabification
word = mw.ustring.gsub(word, '%d', '') -- Remove all digits
-- Syllabify and replace dots with middle dots
local syllabified = syllabify(word)
local result = mw.ustring.gsub(syllabified, '%.', '·')
return result
end
return export
sxrjlqnwjseqcklblkkcx7th5zb7wdk
Wîkîferheng:IPA kurmancî
4
518939
6865511
6265556
2026-10-23T13:26:47Z
Wikihez
7344
Add the interactive IPA chart ({{tabloya IPA ya kurmancî}})
6865511
wikitext
text/x-wiki
;Alfabeya Fonetîk a Navneteweyî (AFN)
{{tabloya IPA ya kurmancî}}
{| style="background:none;"
|- style="vertical-align:top;"
|
{| class="wikitable" style="margin: 1em"
|+ [[bêdeng|Bêdeng]] <small>([[konsonant]])</small>
! IPA !! Deng !! Tîpên latînî !! Tîpên erebî !! Mînak !! Bilêvkirina îngilîzî
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/b/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-b.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|B}} {{g|ku|b}}
| style="text-align: center;" | {{g|ku|sc=Arab|ب|tr=-}}
| '''b'''ira
| '''b'''rother
|-
| style="text-align: center;" | {{IPAchar|/d͡ʒ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-c.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|C}} {{g|ku|c}}
| style="text-align: center;" | {{g|ku|sc=Arab|ج|tr=-}}
| '''c'''erg
| '''j'''oy
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t͡ʃ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ç (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ç}} {{g|ku|ç}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|چ|tr=-}}
| '''ç'''av, '''ç'''îrok
| '''ch'''ur'''ch'''
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t͡ʃʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ç (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ç}} {{g|ku|ç}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|چ|tr=-}}
| '''ç'''adir, '''ç'''end
|
|-
| style="text-align: center;" | {{IPAchar|/d/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-d.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|D}} {{g|ku|d}}
| style="text-align: center;" | {{g|ku|sc=Arab|د|tr=-}}
| '''d'''erga
| '''d'''oor
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/f/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-f.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|F}} {{g|ku|f}}
| style="text-align: center;" | {{g|ku|sc=Arab|ف|tr=-}}
| '''f'''irîn
| '''f'''amily
|-
| style="text-align: center;" | {{IPAchar|/ɡ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-g.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|G}} {{g|ku|g}}
| style="text-align: center;" | {{g|ku|sc=Arab|گ|tr=-}}
| '''g'''und
| '''g'''ood
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/h/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-h.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|H}} {{g|ku|h}}
| style="text-align: center;" | {{g|ku|sc=Arab|ھ|tr=-}}
| '''h'''eval
| '''h'''ouse
|-
| style="text-align: center;" | {{IPAchar|/ħ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ḧ.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ḧ}} {{g|ku|ḧ}}
| style="text-align: center;" | {{g|ku|sc=Arab|ح|tr=-}}
| '''ḧ'''erf
| ''Ne nameya îngilîzî ye''
|-
| style="text-align: center;" | {{IPAchar|/ʒ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-j.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|J}} {{g|ku|j}}
| style="text-align: center;" | {{g|ku|sc=Arab|ژ|tr=-}}
| '''j'''or
| plea'''s'''ure
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/k/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-k (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|K}} {{g|ku|k}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|ک|tr=-}}
| '''k'''evn, '''k'''êm, çe'''k''', ra'''k'''irin
| '''k'''ind
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/kʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-k (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|K}} {{g|ku|k}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|ک|tr=-}}
| '''k'''ar, '''K'''urdistan
| '''k'''eep
|-
| style="text-align: center;" | {{IPAchar|/l/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-l (zirav).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|L}} {{g|ku|l}} (zirav)
| style="text-align: center;" | {{g|ku|sc=Arab|ل|tr=-}}
| '''l'''aw, ma'''l''', '''l'''îmon
| '''l'''emon
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɫ/}} / {{IPAchar|/lˁ/}} <ref name="ɫ">Peyva-di destpêkê de ne pêk e</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-l (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|L}} {{g|ku|l}} (qelew)
| style="text-align: center;" | {{g|ku|sc=Arab|ڵ|tr=-}}
| we'''l'''at, sa'''l'''
| bu'''ll'''
|-
| style="text-align: center;" | {{IPAchar|/m/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-m.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|M}} {{g|ku|m}}
| style="text-align: center;" | {{g|ku|sc=Arab|م|tr=-}}
| '''m'''êr
| '''m'''other
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/n/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-n.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|N}} {{g|ku|n}}
| style="text-align: center;" | {{g|ku|sc=Arab|ن|tr=-}}
| '''n'''îsk
| '''n'''ode
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/ŋ/}} <ref name="ng">Bikaranîn, li derdora zaravayên ku li Silêmaniyê, Seqiz, Sine û cîhê din de heye. Hinekên din jî mîna normal ew bi lêv [n] bi nameyê [g] di şûna wê fonema [ŋ], e.g. maň/mang.</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ň}} {{g|ku|ň}} (ng)
| style="text-align: center;" |
| ma'''ň'''
| lo'''ng'''
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/p/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-p (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|پ|tr=-}}
| '''p'''ênc, '''p'''ayîz
| '''p'''olicy
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/pʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-p (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|پ|tr=-}}
| '''p'''arastin, '''p'''ere
| '''p'''ack
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/pˤ/}}
| style="text-align: center;" | <phonos file="LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (stûr/qelew)
| style="text-align: center;" |
| '''p'''ez, '''p'''iling
|
|-
| style="text-align: center;" | {{IPAchar|/q/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-q.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Q}} {{g|ku|q}}
| style="text-align: center;" | {{g|ku|sc=Arab|ق|tr=-}}
| '''q'''azî
| ''No English equivalent''
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɾ/}} <ref name="r">Peyva-di destpêkê de ne pêk e</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-r (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|R}} {{g|ku|r}}
| style="text-align: center;" | {{g|ku|sc=Arab|ر|tr=-}}
| ka'''r'''wan
| bette'''r''' in American English
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/r/}} <ref name="rr">Hemû peyva-destpêkê r ya pir kêfxweş in, tu çawa ew hatiye nivîsandin bi.</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-r (qelew).wav">Deng</phonos><br><phonos file="LL-Q36368 (kur)-Pispor-r (zirav).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ř}} {{g|ku|ř}} (rr)
| style="text-align: center;" | {{g|ku|sc=Arab|ڕ|tr=-}}
| pi'''ř'''
| ''Ne miqabilê îngilîzî''; trill ''r'' ku Spanî pe'''rr'''o
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/s/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-s.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|S}} {{g|ku|s}}
| style="text-align: center;" | {{g|ku|sc=Arab|س|tr=-}}
| '''s'''ard
| '''c'''inema
|-
| style="text-align: center;" | {{IPAchar|/ʃ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ş.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ş}} {{g|ku|ş}}
| style="text-align: center;" | {{g|ku|sc=Arab|ش|tr=-}}
| '''ş'''în
| '''sh'''eep
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''aze
| '''t'''ea
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/tʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''a ([[sarma]] yan [[germa]]), '''t'''am (hemî, giş), '''t'''ek (bi tenê)
| ci'''t'''y (brîtanî)
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/tˤ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (stûr/qelew)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''a ([[çeq]] yan [[şax]]), '''t'''am (çêj), '''t'''eng
| bi erebî {{g|ar|طَالِب}} tîpa {{g|ar|ط}}
|-
| style="text-align: center;" | {{IPAchar|/v/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-v.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|V}} {{g|ku|v}}
| style="text-align: center;" | {{g|ku|sc=Arab|ڤ|tr=-}}
| '''v'''iyan
| '''v'''ote
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/w/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-w.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|W}} {{g|ku|w}}
| style="text-align: center;" | {{g|ku|sc=Arab|و|tr=-}}
| '''w'''erz
| '''w'''eb
|-
| style="text-align: center;" | {{IPAchar|/x/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-x.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|X}} {{g|ku|x}}
| style="text-align: center;" | {{g|ku|sc=Arab|خ|tr=-}}
| '''x'''eyall
| lo'''ch''' in Scottish English
|-
| style="text-align: center;" | {{IPAchar|/ɣ/}}
| style="text-align: center;" | <phonos file="LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ẍ}} {{g|ku|ẍ}} (gh)
| style="text-align: center;" | {{g|ku|sc=Arab|غ|tr=-}}
| a'''ẍ'''a
| ''Ne herfeke îngilîzî ye''
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/j/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-y.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Y}} {{g|ku|y}}
| style="text-align: center;" | {{g|ku|sc=Arab|ی|tr=-}}
| '''y'''ar
| '''y'''ear
|-
| style="text-align: center;" | {{IPAchar|/z/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-z (zirav).wav">Deng</phonos><br><phonos file="LL-Q36368 (kur)-Pispor-z (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Z}} {{g|ku|z}}
| style="text-align: center;" | {{g|ku|sc=Arab|ز|tr=-}}
| '''z'''îrek
| '''z'''oo
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ʕ/}}
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|'}} / {{g|ku|ʿ}}
| style="text-align: center;" | {{g|ku|sc=Arab|ع|tr=-}}
| '''ʿ'''ezîz
| ''Ne herfeke îngilîzî ye''
|}
|
{| class="wikitable" style="margin: 1em"
|+ [[dengdêr|Dengdêr]] <small>([[vokal]])</small>
! IPA !! Deng !! Tîpên latînî !! Tîpên erebî !! Mînak !! Bilêvkirina îngilîzî
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɑː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-a.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|A}} {{g|ku|a}}
| style="text-align: center;" | {{g|ku|sc=Arab|ا|tr=-}}
| m'''a'''sî / '''a'''r
| h'''a'''t / l'''a'''va
|-
| style="text-align: center;" | {{IPAchar|/ɛ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-e.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|E}} {{g|ku|e}}
| style="text-align: center;" | {{g|ku|sc=Arab|ە|tr=-}}
| b'''e'''rx / '''e'''sp
| b'''e'''d / g'''u'''ide
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/eː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ê.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ê}} {{g|ku|ê}}
| style="text-align: center;" | {{g|ku|sc=Arab|ێ|tr=-}}
| b'''ê'''deng
| b'''ea'''r; ''bi fransî '''é'''t'''é'''''
|-
| style="text-align: center;" | {{IPAchar|/ɘ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-i.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|I}} {{g|ku|i}}
| style="text-align: center;" |
| Kurd'''i'''stan
|-
| style="text-align: center;" | {{IPAchar|/i/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-a.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|I}} {{g|ku|i}}
| style="text-align: center;" |
| ç'''i'''ya
|
|-
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/iː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-î.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Î}} {{g|ku|î}}
| style="text-align: center;" | {{g|ku|sc=Arab|ی|tr=-}}
| b'''î'''r
| s'''ee'''
|-
| style="text-align: center;" | {{IPAchar|/oː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-o.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|O}} {{g|ku|o}}
| style="text-align: center;" | {{g|ku|sc=Arab|ۆ|tr=-}}
| r'''o'''j
| '''o'''racle
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/øː/}} <ref name="ö">Bi hebûnên piranî [[Soranî#Zaravayê|Jafi]] bilêvkirin.</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ö}} {{g|ku|ö}}
| style="text-align: center;" |
| x'''ö'''
| ''Ne herfeke îngilîzî ye''
|-
| style="text-align: center;" | {{IPAchar|/ʊ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-u.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|U}} {{g|ku|u}}
| style="text-align: center;" | {{g|ku|sc=Arab|و|tr=-}}
| g'''u'''lan
| n'''u'''ke
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/uː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-û.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Û}} {{g|ku|û}}
| style="text-align: center;" | {{g|ku|sc=Arab|وو|tr=-}}
| s'''û'''r
| r'''oo'''t
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/yː/}} <ref name="ü">Bi piranî bilêvkirina [[Başûrê Kurdistanê]].</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ü}} {{g|ku|ü}}
| style="text-align: center;" | {{g|ku|sc=Arab|ۊ|tr=-}}
| k'''ü'''
| ch'''oo'''se (li îngilîziya skotyayî)
|}
:{{legend2|#F0FFF0|border=1px solid #aaa|Bi tîpên latînî ya [[kurdiya başûrî]] (<tt>sdh</tt>)}}
|}
== Mijarên têkildar ==
* '''[[Pêvek:Dengnasî]]'''
* [[Modul:ku-bilêvkirin]]
* [[w:Şablon:Dengdarên kurdî]]
* [[w:Şablon:Dengdêrên kurdî]]
* [[w:Kurmancî#Dengnasiya kurmancî|Dengnasiya kurmancî]]
* [[Wîkîferheng:Dîwan/arşîv 2019#Deng bi kurdî]]
== Çavkanî ==
{{çavkanî|2}}
== Girêdanên derve ==
* [//www.kurdishacademy.org/?q=node/3 www.kurdishacademy.org: ''The Kurdish Unified Alphabet'']
* Muhamadreza Bahadur: [//www.academia.edu/12911202/Kirma%C5%9Fan%C3%AE_Alphabet_and_Pronunciation_Guide?auto=download ''Kirmaşanî Alphabet and Pronunciation Guide'']
__NOTOC__
[[Kategorî:Dengnasî|*]]
6pq89igppxalzndk09oqj17jy9blk0w
6865514
6865511
2026-10-23T14:14:05Z
Wikihez
7344
([[m:Special:MyLanguage/User:Jon Harald Søby/diffedit|diffedit]])
6865514
wikitext
text/x-wiki
;Alfabeya Fonetîk a Navneteweyî (AFN)
{| style="background:none;"
|- style="vertical-align:top;"
|
{| class="wikitable" style="margin: 1em"
|+ [[bêdeng|Bêdeng]] <small>([[konsonant]])</small>
! IPA !! Deng !! Tîpên latînî !! Tîpên erebî !! Mînak !! Bilêvkirina îngilîzî
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/b/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-b.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|B}} {{g|ku|b}}
| style="text-align: center;" | {{g|ku|sc=Arab|ب|tr=-}}
| '''b'''ira
| '''b'''rother
|-
| style="text-align: center;" | {{IPAchar|/d͡ʒ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-c.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|C}} {{g|ku|c}}
| style="text-align: center;" | {{g|ku|sc=Arab|ج|tr=-}}
| '''c'''erg
| '''j'''oy
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t͡ʃ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ç (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ç}} {{g|ku|ç}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|چ|tr=-}}
| '''ç'''av, '''ç'''îrok
| '''ch'''ur'''ch'''
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t͡ʃʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ç (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ç}} {{g|ku|ç}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|چ|tr=-}}
| '''ç'''adir, '''ç'''end
|
|-
| style="text-align: center;" | {{IPAchar|/d/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-d.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|D}} {{g|ku|d}}
| style="text-align: center;" | {{g|ku|sc=Arab|د|tr=-}}
| '''d'''erga
| '''d'''oor
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/f/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-f.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|F}} {{g|ku|f}}
| style="text-align: center;" | {{g|ku|sc=Arab|ف|tr=-}}
| '''f'''irîn
| '''f'''amily
|-
| style="text-align: center;" | {{IPAchar|/ɡ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-g.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|G}} {{g|ku|g}}
| style="text-align: center;" | {{g|ku|sc=Arab|گ|tr=-}}
| '''g'''und
| '''g'''ood
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/h/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-h.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|H}} {{g|ku|h}}
| style="text-align: center;" | {{g|ku|sc=Arab|ھ|tr=-}}
| '''h'''eval
| '''h'''ouse
|-
| style="text-align: center;" | {{IPAchar|/ħ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ḧ.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ḧ}} {{g|ku|ḧ}}
| style="text-align: center;" | {{g|ku|sc=Arab|ح|tr=-}}
| '''ḧ'''erf
| ''Ne nameya îngilîzî ye''
|-
| style="text-align: center;" | {{IPAchar|/ʒ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-j.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|J}} {{g|ku|j}}
| style="text-align: center;" | {{g|ku|sc=Arab|ژ|tr=-}}
| '''j'''or
| plea'''s'''ure
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/k/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-k (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|K}} {{g|ku|k}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|ک|tr=-}}
| '''k'''evn, '''k'''êm, çe'''k''', ra'''k'''irin
| '''k'''ind
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/kʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-k (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|K}} {{g|ku|k}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|ک|tr=-}}
| '''k'''ar, '''K'''urdistan
| '''k'''eep
|-
| style="text-align: center;" | {{IPAchar|/l/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-l (zirav).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|L}} {{g|ku|l}} (zirav)
| style="text-align: center;" | {{g|ku|sc=Arab|ل|tr=-}}
| '''l'''aw, ma'''l''', '''l'''îmon
| '''l'''emon
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɫ/}} / {{IPAchar|/lˁ/}} <ref name="ɫ">Peyva-di destpêkê de ne pêk e</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-l (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|L}} {{g|ku|l}} (qelew)
| style="text-align: center;" | {{g|ku|sc=Arab|ڵ|tr=-}}
| we'''l'''at, sa'''l'''
| bu'''ll'''
|-
| style="text-align: center;" | {{IPAchar|/m/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-m.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|M}} {{g|ku|m}}
| style="text-align: center;" | {{g|ku|sc=Arab|م|tr=-}}
| '''m'''êr
| '''m'''other
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/n/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-n.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|N}} {{g|ku|n}}
| style="text-align: center;" | {{g|ku|sc=Arab|ن|tr=-}}
| '''n'''îsk
| '''n'''ode
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/ŋ/}} <ref name="ng">Bikaranîn, li derdora zaravayên ku li Silêmaniyê, Seqiz, Sine û cîhê din de heye. Hinekên din jî mîna normal ew bi lêv [n] bi nameyê [g] di şûna wê fonema [ŋ], e.g. maň/mang.</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ň}} {{g|ku|ň}} (ng)
| style="text-align: center;" |
| ma'''ň'''
| lo'''ng'''
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/p/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-p (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|پ|tr=-}}
| '''p'''ênc, '''p'''ayîz
| '''p'''olicy
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/pʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-p (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|پ|tr=-}}
| '''p'''arastin, '''p'''ere
| '''p'''ack
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/pˤ/}}
| style="text-align: center;" | <phonos file="LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (stûr/qelew)
| style="text-align: center;" |
| '''p'''ez, '''p'''iling
|
|-
| style="text-align: center;" | {{IPAchar|/q/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-q.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Q}} {{g|ku|q}}
| style="text-align: center;" | {{g|ku|sc=Arab|ق|tr=-}}
| '''q'''azî
| ''No English equivalent''
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɾ/}} <ref name="r">Peyva-di destpêkê de ne pêk e</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-r (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|R}} {{g|ku|r}}
| style="text-align: center;" | {{g|ku|sc=Arab|ر|tr=-}}
| ka'''r'''wan
| bette'''r''' in American English
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/r/}} <ref name="rr">Hemû peyva-destpêkê r ya pir kêfxweş in, tu çawa ew hatiye nivîsandin bi.</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-r (qelew).wav">Deng</phonos><br><phonos file="LL-Q36368 (kur)-Pispor-r (zirav).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ř}} {{g|ku|ř}} (rr)
| style="text-align: center;" | {{g|ku|sc=Arab|ڕ|tr=-}}
| pi'''ř'''
| ''Ne miqabilê îngilîzî''; trill ''r'' ku Spanî pe'''rr'''o
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/s/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-s.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|S}} {{g|ku|s}}
| style="text-align: center;" | {{g|ku|sc=Arab|س|tr=-}}
| '''s'''ard
| '''c'''inema
|-
| style="text-align: center;" | {{IPAchar|/ʃ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ş.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ş}} {{g|ku|ş}}
| style="text-align: center;" | {{g|ku|sc=Arab|ش|tr=-}}
| '''ş'''în
| '''sh'''eep
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''aze
| '''t'''ea
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/tʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''a ([[sarma]] yan [[germa]]), '''t'''am (hemî, giş), '''t'''ek (bi tenê)
| ci'''t'''y (brîtanî)
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/tˤ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (stûr/qelew)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''a ([[çeq]] yan [[şax]]), '''t'''am (çêj), '''t'''eng
| bi erebî {{g|ar|طَالِب}} tîpa {{g|ar|ط}}
|-
| style="text-align: center;" | {{IPAchar|/v/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-v.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|V}} {{g|ku|v}}
| style="text-align: center;" | {{g|ku|sc=Arab|ڤ|tr=-}}
| '''v'''iyan
| '''v'''ote
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/w/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-w.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|W}} {{g|ku|w}}
| style="text-align: center;" | {{g|ku|sc=Arab|و|tr=-}}
| '''w'''erz
| '''w'''eb
|-
| style="text-align: center;" | {{IPAchar|/x/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-x.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|X}} {{g|ku|x}}
| style="text-align: center;" | {{g|ku|sc=Arab|خ|tr=-}}
| '''x'''eyall
| lo'''ch''' in Scottish English
|-
| style="text-align: center;" | {{IPAchar|/ɣ/}}
| style="text-align: center;" | <phonos file="LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ẍ}} {{g|ku|ẍ}} (gh)
| style="text-align: center;" | {{g|ku|sc=Arab|غ|tr=-}}
| a'''ẍ'''a
| ''Ne herfeke îngilîzî ye''
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/j/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-y.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Y}} {{g|ku|y}}
| style="text-align: center;" | {{g|ku|sc=Arab|ی|tr=-}}
| '''y'''ar
| '''y'''ear
|-
| style="text-align: center;" | {{IPAchar|/z/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-z (zirav).wav">Deng</phonos><br><phonos file="LL-Q36368 (kur)-Pispor-z (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Z}} {{g|ku|z}}
| style="text-align: center;" | {{g|ku|sc=Arab|ز|tr=-}}
| '''z'''îrek
| '''z'''oo
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ʕ/}}
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|'}} / {{g|ku|ʿ}}
| style="text-align: center;" | {{g|ku|sc=Arab|ع|tr=-}}
| '''ʿ'''ezîz
| ''Ne herfeke îngilîzî ye''
|}
|
{| class="wikitable" style="margin: 1em"
|+ [[dengdêr|Dengdêr]] <small>([[vokal]])</small>
! IPA !! Deng !! Tîpên latînî !! Tîpên erebî !! Mînak !! Bilêvkirina îngilîzî
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɑː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-a.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|A}} {{g|ku|a}}
| style="text-align: center;" | {{g|ku|sc=Arab|ا|tr=-}}
| m'''a'''sî / '''a'''r
| h'''a'''t / l'''a'''va
|-
| style="text-align: center;" | {{IPAchar|/ɛ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-e.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|E}} {{g|ku|e}}
| style="text-align: center;" | {{g|ku|sc=Arab|ە|tr=-}}
| b'''e'''rx / '''e'''sp
| b'''e'''d / g'''u'''ide
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/eː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ê.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ê}} {{g|ku|ê}}
| style="text-align: center;" | {{g|ku|sc=Arab|ێ|tr=-}}
| b'''ê'''deng
| b'''ea'''r; ''bi fransî '''é'''t'''é'''''
|-
| style="text-align: center;" | {{IPAchar|/ɘ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-i.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|I}} {{g|ku|i}}
| style="text-align: center;" |
| Kurd'''i'''stan
|-
| style="text-align: center;" | {{IPAchar|/i/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-a.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|I}} {{g|ku|i}}
| style="text-align: center;" |
| ç'''i'''ya
|
|-
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/iː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-î.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Î}} {{g|ku|î}}
| style="text-align: center;" | {{g|ku|sc=Arab|ی|tr=-}}
| b'''î'''r
| s'''ee'''
|-
| style="text-align: center;" | {{IPAchar|/oː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-o.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|O}} {{g|ku|o}}
| style="text-align: center;" | {{g|ku|sc=Arab|ۆ|tr=-}}
| r'''o'''j
| '''o'''racle
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/øː/}} <ref name="ö">Bi hebûnên piranî [[Soranî#Zaravayê|Jafi]] bilêvkirin.</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ö}} {{g|ku|ö}}
| style="text-align: center;" |
| x'''ö'''
| ''Ne herfeke îngilîzî ye''
|-
| style="text-align: center;" | {{IPAchar|/ʊ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-u.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|U}} {{g|ku|u}}
| style="text-align: center;" | {{g|ku|sc=Arab|و|tr=-}}
| g'''u'''lan
| n'''u'''ke
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/uː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-û.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Û}} {{g|ku|û}}
| style="text-align: center;" | {{g|ku|sc=Arab|وو|tr=-}}
| s'''û'''r
| r'''oo'''t
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/yː/}} <ref name="ü">Bi piranî bilêvkirina [[Başûrê Kurdistanê]].</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ü}} {{g|ku|ü}}
| style="text-align: center;" | {{g|ku|sc=Arab|ۊ|tr=-}}
| k'''ü'''
| ch'''oo'''se (li îngilîziya skotyayî)
|}
:{{legend2|#F0FFF0|border=1px solid #aaa|Bi tîpên latînî ya [[kurdiya başûrî]] (<tt>sdh</tt>)}}
|}
== Mijarên têkildar ==
* '''[[Pêvek:Dengnasî]]'''
* [[Modul:ku-bilêvkirin]]
* [[w:Şablon:Dengdarên kurdî]]
* [[w:Şablon:Dengdêrên kurdî]]
* [[w:Kurmancî#Dengnasiya kurmancî|Dengnasiya kurmancî]]
* [[Wîkîferheng:Dîwan/arşîv 2019#Deng bi kurdî]]
== Çavkanî ==
{{çavkanî|2}}
== Girêdanên derve ==
* [//www.kurdishacademy.org/?q=node/3 www.kurdishacademy.org: ''The Kurdish Unified Alphabet'']
* Muhamadreza Bahadur: [//www.academia.edu/12911202/Kirma%C5%9Fan%C3%AE_Alphabet_and_Pronunciation_Guide?auto=download ''Kirmaşanî Alphabet and Pronunciation Guide'']
__NOTOC__
[[Kategorî:Dengnasî|*]]
7aqsaqdpcqweriqatcft0ngobdsefhy
6865515
6865514
2026-10-23T14:17:13Z
Wikihez
7344
6865515
wikitext
text/x-wiki
;Alfabeya Fonetîk a Navneteweyî (AFN)
{| style="background:none;"
|- style="vertical-align:top;"
|
{| class="wikitable" style="margin: 1em"
|+ [[bêdeng|Bêdeng]] <small>([[konsonant]])</small>
! IPA !! Deng !! Tîpên latînî !! Tîpên erebî !! Mînak !! Bilêvkirina îngilîzî
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/b/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-b.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|B}} {{g|ku|b}}
| style="text-align: center;" | {{g|ku|sc=Arab|ب|tr=-}}
| '''b'''ira
| '''b'''rother
|-
| style="text-align: center;" | {{IPAchar|/d͡ʒ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-c.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|C}} {{g|ku|c}}
| style="text-align: center;" | {{g|ku|sc=Arab|ج|tr=-}}
| '''c'''erg
| '''j'''oy
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t͡ʃ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ç (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ç}} {{g|ku|ç}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|چ|tr=-}}
| '''ç'''av, '''ç'''îrok
| '''ch'''ur'''ch'''
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t͡ʃʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ç (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ç}} {{g|ku|ç}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|چ|tr=-}}
| '''ç'''adir, '''ç'''end
|
|-
| style="text-align: center;" | {{IPAchar|/d/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-d.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|D}} {{g|ku|d}}
| style="text-align: center;" | {{g|ku|sc=Arab|د|tr=-}}
| '''d'''erga
| '''d'''oor
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/f/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-f.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|F}} {{g|ku|f}}
| style="text-align: center;" | {{g|ku|sc=Arab|ف|tr=-}}
| '''f'''irîn
| '''f'''amily
|-
| style="text-align: center;" | {{IPAchar|/ɡ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-g.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|G}} {{g|ku|g}}
| style="text-align: center;" | {{g|ku|sc=Arab|گ|tr=-}}
| '''g'''und
| '''g'''ood
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/h/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-h.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|H}} {{g|ku|h}}
| style="text-align: center;" | {{g|ku|sc=Arab|ھ|tr=-}}
| '''h'''eval
| '''h'''ouse
|-
| style="text-align: center;" | {{IPAchar|/ħ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ḧ.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ḧ}} {{g|ku|ḧ}}
| style="text-align: center;" | {{g|ku|sc=Arab|ح|tr=-}}
| '''ḧ'''erf
| ''Ne nameya îngilîzî ye''
|-
| style="text-align: center;" | {{IPAchar|/ʒ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-j.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|J}} {{g|ku|j}}
| style="text-align: center;" | {{g|ku|sc=Arab|ژ|tr=-}}
| '''j'''or
| plea'''s'''ure
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/k/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-k (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|K}} {{g|ku|k}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|ک|tr=-}}
| '''k'''evn, '''k'''êm, çe'''k''', ra'''k'''irin
| '''k'''ind
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/kʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-k (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|K}} {{g|ku|k}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|ک|tr=-}}
| '''k'''ar, '''K'''urdistan
| '''k'''eep
|-
| style="text-align: center;" | {{IPAchar|/l/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-l (zirav).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|L}} {{g|ku|l}} (zirav)
| style="text-align: center;" | {{g|ku|sc=Arab|ل|tr=-}}
| '''l'''aw, ma'''l''', '''l'''îmon
| '''l'''emon
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɫ/}} / {{IPAchar|/lˁ/}} <ref name="ɫ">Peyva-di destpêkê de ne pêk e</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-l (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|L}} {{g|ku|l}} (qelew)
| style="text-align: center;" | {{g|ku|sc=Arab|ڵ|tr=-}}
| we'''l'''at, sa'''l'''
| bu'''ll'''
|-
| style="text-align: center;" | {{IPAchar|/m/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-m.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|M}} {{g|ku|m}}
| style="text-align: center;" | {{g|ku|sc=Arab|م|tr=-}}
| '''m'''êr
| '''m'''other
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/n/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-n.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|N}} {{g|ku|n}}
| style="text-align: center;" | {{g|ku|sc=Arab|ن|tr=-}}
| '''n'''îsk
| '''n'''ode
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/ŋ/}} <ref name="ng">Bikaranîn, li derdora zaravayên ku li Silêmaniyê, Seqiz, Sine û cîhê din de heye. Hinekên din jî mîna normal ew bi lêv [n] bi nameyê [g] di şûna wê fonema [ŋ], e.g. maň/mang.</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ň}} {{g|ku|ň}} (ng)
| style="text-align: center;" |
| ma'''ň'''
| lo'''ng'''
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/p/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-p (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|پ|tr=-}}
| '''p'''ênc, '''p'''ayîz
| '''p'''olicy
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/pʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-p (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|پ|tr=-}}
| '''p'''arastin, '''p'''ere
| '''p'''ack
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/pˤ/}}
| style="text-align: center;" | <phonos file="LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|P}} {{g|ku|p}} (stûr/qelew)
| style="text-align: center;" |
| '''p'''ez, '''p'''iling
|
|-
| style="text-align: center;" | {{IPAchar|/q/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-q.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Q}} {{g|ku|q}}
| style="text-align: center;" | {{g|ku|sc=Arab|ق|tr=-}}
| '''q'''azî
| ''No English equivalent''
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɾ/}} <ref name="r">Peyva-di destpêkê de ne pêk e</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-r (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|R}} {{g|ku|r}}
| style="text-align: center;" | {{g|ku|sc=Arab|ر|tr=-}}
| ka'''r'''wan
| bette'''r''' in American English
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/r/}} <ref name="rr">Hemû peyva-destpêkê r ya pir kêfxweş in, tu çawa ew hatiye nivîsandin bi.</ref>
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-r (qelew).wav">Deng</phonos><br><phonos file="LL-Q36368 (kur)-Pispor-r (zirav).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ř}} {{g|ku|ř}} (rr)
| style="text-align: center;" | {{g|ku|sc=Arab|ڕ|tr=-}}
| pi'''ř'''
| ''Ne miqabilê îngilîzî''; trill ''r'' ku Spanî pe'''rr'''o
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/s/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-s.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|S}} {{g|ku|s}}
| style="text-align: center;" | {{g|ku|sc=Arab|س|tr=-}}
| '''s'''ard
| '''c'''inema
|-
| style="text-align: center;" | {{IPAchar|/ʃ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ş.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ş}} {{g|ku|ş}}
| style="text-align: center;" | {{g|ku|sc=Arab|ش|tr=-}}
| '''ş'''în
| '''sh'''eep
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/t/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (nerm).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (nerm)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''aze
| '''t'''ea
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/tʰ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (req).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (req)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''a ([[sarma]] yan [[germa]]), '''t'''am (hemî, giş), '''t'''ek (bi tenê)
| ci'''t'''y (brîtanî)
|- bgcolor="#ffe4c4"
| style="text-align: center;" | {{IPAchar|/tˤ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-t (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|T}} {{g|ku|t}} (stûr/qelew)
| style="text-align: center;" | {{g|ku|sc=Arab|ت|tr=-}}
| '''t'''a ([[çeq]] yan [[şax]]), '''t'''am (çêj), '''t'''eng
| bi erebî {{g|ar|طَالِب}} tîpa {{g|ar|ط}}
|-
| style="text-align: center;" | {{IPAchar|/v/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-v.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|V}} {{g|ku|v}}
| style="text-align: center;" | {{g|ku|sc=Arab|ڤ|tr=-}}
| '''v'''iyan
| '''v'''ote
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/w/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-w.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|W}} {{g|ku|w}}
| style="text-align: center;" | {{g|ku|sc=Arab|و|tr=-}}
| '''w'''erz
| '''w'''eb
|-
| style="text-align: center;" | {{IPAchar|/x/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-x.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|X}} {{g|ku|x}}
| style="text-align: center;" | {{g|ku|sc=Arab|خ|tr=-}}
| '''x'''eyall
| lo'''ch''' in Scottish English
|-
| style="text-align: center;" | {{IPAchar|/ɣ/}}
| style="text-align: center;" | <phonos file="LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ẍ}} {{g|ku|ẍ}} (gh)
| style="text-align: center;" | {{g|ku|sc=Arab|غ|tr=-}}
| a'''ẍ'''a
| ''Ne herfeke îngilîzî ye''
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/j/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-y.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Y}} {{g|ku|y}}
| style="text-align: center;" | {{g|ku|sc=Arab|ی|tr=-}}
| '''y'''ar
| '''y'''ear
|-
| style="text-align: center;" | {{IPAchar|/z/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-z (zirav).wav">Deng</phonos><br><phonos file="LL-Q36368 (kur)-Pispor-z (qelew).wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Z}} {{g|ku|z}}
| style="text-align: center;" | {{g|ku|sc=Arab|ز|tr=-}}
| '''z'''îrek
| '''z'''oo
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ʕ/}}
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|'}} / {{g|ku|ʿ}}
| style="text-align: center;" | {{g|ku|sc=Arab|ع|tr=-}}
| '''ʿ'''ezîz
| ''Ne herfeke îngilîzî ye''
|}
|
{| class="wikitable" style="margin: 1em"
|+ [[dengdêr|Dengdêr]] <small>([[vokal]])</small>
! IPA !! Deng !! Tîpên latînî !! Tîpên erebî !! Mînak !! Bilêvkirina îngilîzî
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/ɑː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-a.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|A}} {{g|ku|a}}
| style="text-align: center;" | {{g|ku|sc=Arab|ا|tr=-}}
| m'''a'''sî / '''a'''r
| h'''a'''t / l'''a'''va
|-
| style="text-align: center;" | {{IPAchar|/ɛ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-e.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|E}} {{g|ku|e}}
| style="text-align: center;" | {{g|ku|sc=Arab|ە|tr=-}}
| b'''e'''rx / '''e'''sp
| b'''e'''d / g'''u'''ide
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/eː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-ê.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Ê}} {{g|ku|ê}}
| style="text-align: center;" | {{g|ku|sc=Arab|ێ|tr=-}}
| b'''ê'''deng
| b'''ea'''r; ''bi fransî '''é'''t'''é'''''
|-
| style="text-align: center;" | {{IPAchar|/ɘ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-i.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|I}} {{g|ku|i}}
| style="text-align: center;" |
| Kurd'''i'''stan
|-
| style="text-align: center;" | {{IPAchar|/i/}}
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|I}} {{g|ku|i}}
| style="text-align: center;" |
| ç'''i'''ya
|
|-
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/iː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-î.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Î}} {{g|ku|î}}
| style="text-align: center;" | {{g|ku|sc=Arab|ی|tr=-}}
| b'''î'''r
| s'''ee'''
|-
| style="text-align: center;" | {{IPAchar|/oː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-o.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|O}} {{g|ku|o}}
| style="text-align: center;" | {{g|ku|sc=Arab|ۆ|tr=-}}
| r'''o'''j
| '''o'''racle
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/øː/}} <ref name="ö">Bi hebûnên piranî [[Soranî#Zaravayê|Jafi]] bilêvkirin.</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ö}} {{g|ku|ö}}
| style="text-align: center;" |
| x'''ö'''
| ''Ne herfeke îngilîzî ye''
|-
| style="text-align: center;" | {{IPAchar|/ʊ/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-u.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|U}} {{g|ku|u}}
| style="text-align: center;" | {{g|ku|sc=Arab|و|tr=-}}
| g'''u'''lan
| n'''u'''ke
|- bgcolor="white"
| style="text-align: center;" | {{IPAchar|/uː/}}
| style="text-align: center;" | <phonos file="LL-Q36368 (kur)-Pispor-û.wav">Deng</phonos>
| style="text-align: center;" | {{g|ku|Û}} {{g|ku|û}}
| style="text-align: center;" | {{g|ku|sc=Arab|وو|tr=-}}
| s'''û'''r
| r'''oo'''t
|- bgcolor="#F0FFF0"
| style="text-align: center;" | {{IPAchar|/yː/}} <ref name="ü">Bi piranî bilêvkirina [[Başûrê Kurdistanê]].</ref>
| style="text-align: center;" |
| style="text-align: center;" | {{g|ku|Ü}} {{g|ku|ü}}
| style="text-align: center;" | {{g|ku|sc=Arab|ۊ|tr=-}}
| k'''ü'''
| ch'''oo'''se (li îngilîziya skotyayî)
|}
:{{legend2|#F0FFF0|border=1px solid #aaa|Bi tîpên latînî ya [[kurdiya başûrî]] (<tt>sdh</tt>)}}
|}
== Mijarên têkildar ==
* '''[[Pêvek:Dengnasî]]'''
* [[Modul:ku-bilêvkirin]]
* [[w:Şablon:Dengdarên kurdî]]
* [[w:Şablon:Dengdêrên kurdî]]
* [[w:Kurmancî#Dengnasiya kurmancî|Dengnasiya kurmancî]]
* [[Wîkîferheng:Dîwan/arşîv 2019#Deng bi kurdî]]
== Çavkanî ==
{{çavkanî|2}}
== Girêdanên derve ==
* [//www.kurdishacademy.org/?q=node/3 www.kurdishacademy.org: ''The Kurdish Unified Alphabet'']
* Muhamadreza Bahadur: [//www.academia.edu/12911202/Kirma%C5%9Fan%C3%AE_Alphabet_and_Pronunciation_Guide?auto=download ''Kirmaşanî Alphabet and Pronunciation Guide'']
__NOTOC__
[[Kategorî:Dengnasî|*]]
ry5bmqxtrta81ni8z8h9xbi1vwonou3
carek din
0
534589
6865520
6857626
2026-10-23T15:22:02Z
Wikihez
7344
Kurmancî: beşa «Mane» bû «Hoker» (DefSideBoxes)
6865520
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Hoker ===
{{hoker|ku}}
# [[cardin]], [[dîsa]], dokî di
{{kontrolWergerr}}
==== Etîmolojî ====
{{etîmon|ku|:qertaf|carek|din}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W-|de|noch einmal}}
* {{Z|tr}}: {{W+|tr|bir daha}}, {{W-|tr|bir dahaki sefere|guman=erê}}
{{werger-bin}}
{{kontrol}}
pfwcrhezhhww54st7ge202tyosxw3iu
Nezîf
0
613021
6865565
6574933
2026-10-23T20:33:52Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865565
wikitext
text/x-wiki
{{h-bnr|nezif|nezîf}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[nav]]ekî [[mêr]]an e
#* {{jêder|ku|jêgirtin=Mihemed '''Nezîf''' Qadirî diyar dike, ku eger Îran di nava xaka Başûrê Kurdistanê de êrîşî wan bike, ev yek tê wateya destpêkirina şer û li hember vê yekê jî ew dê ji bo parastina xwe û bersivdana Îranê, destê xwe dirêjî nava xaka Rojhilatê Kurdistanê bikin.|Rudaw.net/kurmanci, 9/2017}}
==== Formên alternatîv ====
* [[Nazîf]] (di bin tesîra tirkî de)
==== Etîmolojî ====
ji peyva [[nezîf]]
p7j71gzkr22tqjt6i4iokc9p2no7ewm
Nêrwe
0
613081
6865567
6713787
2026-10-23T20:34:04Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865567
wikitext
text/x-wiki
{{h-bnr|ñerwe}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=m}}
# [[dever]]ek e li [[Amêdî|Amêdiyê]] li [[parêzgeh]]a [[Dihok]]ê li [[Başûrê Kurdistanê]]
#* {{jêder|ku|jêgirtin=Hêjayê gotinê ye, ku duh devera '''Nêrwe''' û Rêkan a bajarokê Şêladizê ya ser bi navçeya Amêdiye ya Duhokê ji aliyê balafirên Tirkiyê ve hatine bombebaran kirin. Di encamê de 7 hemwelatiyan canê xwe ji dest da û hemwelatiyek jî bêserûşûn bû.|Rudaw.net/kurmanci, 9/2017}}
==== Formên alternatîv ====
* [[Nêrweh]]
==== Jê ====
* [[nêrweyî]]
* [[nêrwehî]]
* [[nêrweyîtî]]
* [[nêrwehîtî]]
==== Anagram ====
* {{anagram|ku|a=eênrw|ewrên|newêr|rewên}}
r9fab2w7myr8x447iqrfx1qf6bff0hc
Rêkan
0
613082
6865587
6713789
2026-10-23T20:48:29Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865587
wikitext
text/x-wiki
{{h-bnr|rekan|rêkan}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=m}}
# [[dever]]ek e li [[Amêdî|Amêdiyê]] li [[parêzgeh]]a [[Dihok]]ê li [[Başûrê Kurdistanê]]
#* {{jêder|ku|jêgirtin=Hêjayê gotinê ye, ku duh devera Nêrwe û '''Rêkan''' a bajarokê Şêladizê ya ser bi navçeya Amêdiye ya Duhokê ji aliyê balafirên Tirkiyê ve hatine bombebaran kirin. Di encamê de 7 hemwelatiyan canê xwe ji dest da û hemwelatiyek jî bêserûşûn bû.|Rudaw.net/kurmanci, 9/2017}}
==== Jê ====
* [[nêrweyî]]
* [[nêrwehî]]
* [[nêrweyîtî]]
* [[nêrwehîtî]]
==== Anagram ====
* {{anagram|ku|a=aêknr|karên|kêran}}
nnfke9sp7x1bl4yitr1vn4qm2dr96lf
Mehma
0
619007
6865563
6725712
2026-10-23T20:33:23Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865563
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[nav]]ekî [[zilam]]an e
#* {{jêder|ku|jêgirtin=Di yekemîn karê xwe de hêzên Iraqî qaymiqamê Şingalê guhertin û di dewsa '''Mehma''' Xelîl de, kesekî bi navê Fehd Hamid danîn. Herwiha li şûna Nayif Seydo jî, berpirsekî Heşda Şeibî yê bi navê Xwedêda Cukê kirin rêveberê bajarokê Sinûnê, Mîlhem Îlyas jî weke rêveberê bajarokÊ Girizêr hate destnîşankirin.|Rudaw.net/kurmanci, 11/2017}}
==== Formên alternatîv ====
* [[Mihema]]
=== Bidin ber===
{{kol3|ku
|Mehmed
|Miheme
|Mihemed
|Memed
|Mihê
|Miho
|Mihyedîn
}}
==== Etîmolojî ====
Ji {{ji|ar}}
==== Anagram ====
* {{anagram|ku|a=aehmm|hemam}}
gdrpv7w7hlughuhs4m960x9m70iyrhy
Naîf
0
619011
6865564
6569922
2026-10-23T20:33:41Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865564
wikitext
text/x-wiki
{{h-bnr|naîf|naïf}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[nav]]ekî [[zilam]]an e
#* {{jêder|ku|jêgirtin=Rêveberê berê yê bajarokê Sinûnê '''Nayif''' Seydo jî got: "Em niha li Duhokê karê xwe berdewam dikin. Ji sedî 90ê xelkê Şingalê koçber in û em di nava wan de xizmetê dikin. Ji xwe em newêrin herin Şingalê, çimkî hertim gefan li me dixwin."|Rudaw.net/kurmanci, 11/2017}}
==== Formên alternatîv ====
* [[Nayif]]
* [[Nayîf]]
* [[Nayf]]
==== Etîmolojî ====
Ji {{ji|ar}}
==== Anagram ====
* {{anagram|ku|a=afîn|fanî|fîna}}
ojkl2eewjm0m11lnacjbk3msuhe0qbn
Seydo
0
619012
6865592
6385274
2026-10-23T20:49:36Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865592
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[nav]]ekî [[zelam]]an e, <br>[[bernav]]ê [[Seyfedîn]]
#* {{jêder|ku|jêgirtin=Rêveberê berê yê bajarokê Sinûnê Nayif '''Seydo''' jî got: "Em niha li Duhokê karê xwe berdewam dikin. Ji sedî 90ê xelkê Şingalê koçber in û em di nava wan de xizmetê dikin. Ji xwe em newêrin herin Şingalê, çimkî hertim gefan li me dixwin."|Rudaw.net/kurmanci 11/2017}}
==== Formên alternatîv ====
* [[Sêdo]]
* [[Sîdo]]
==== Etîmolojî ====
Ji {{ji|ar}}
====Bide ber====
* [[Zêdo]]
==== Anagram ====
* {{anagram|ku|a=deosy|deyos|dosye}}
4jiws0dcf5rmtw6hl9divzzl1h1eleh
6865593
6865592
2026-10-23T20:50:22Z
Wikihez
7344
([[m:Special:MyLanguage/User:Jon Harald Søby/diffedit|diffedit]])
6865593
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[nav]]ekî [[zelam]]an e, <br>[[bernav]]ê [[Seyfedîn]]
#* {{jêder|ku|jêgirtin=Rêveberê berê yê bajarokê Sinûnê Nayif '''Seydo''' jî got: "Em niha li Duhokê karê xwe berdewam dikin. Ji sedî 90ê xelkê Şingalê koçber in û em di nava wan de xizmetê dikin. Ji xwe em newêrin herin Şingalê, çimkî hertim gefan li me dixwin."|Rudaw.net/kurmanci, 11/2017}}
==== Formên alternatîv ====
* [[Sêdo]]
* [[Sîdo]]
==== Etîmolojî ====
Ji {{ji|ar}}
====Bide ber====
* [[Zêdo]]
==== Anagram ====
* {{anagram|ku|a=deosy|deyos|dosye}}
hcyu6cybftu71hxdxnhcyjv3oovptqs
Rehîmawa
0
629006
6865585
6405473
2026-10-23T20:47:41Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865585
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
*Re[[ḧ]]îmawa, Re[[ḧ]]îm-awa
=== Serenav ===
{{serenav|ku|z=m}}
# [[tax]] anku [[mehel]]ek e li [[bajar]]ê [[Kerkûk]]ê li [[Başûrê Kurdistanê]]
#* {{jêder|ku|jêgirtin=Li gor zanyariyên BasNewsê duh 22.11.2017ê milîsên heşdî şeibî davêjin ser binkeyê komîteya '''Rehîmawa''' ya bajarê Kerkûkê û li wî binkeyî lêgerînê dikin.|ku.hawarnews.com, 3/2018}}
==== Bi alfabeyên din ====
* {{ku-ar|ڕهحیماوا}}, {{g|ku|sc=Arab|ڕهحیمئاوا}}
==== Etîmolojî ====
[[Rehîm]] + [[-awa]] ([[-ava]])
==== Anagram ====
* {{anagram|ku|a=aaehîmrw|hewramîa}}
pgv0l52afmnmd8bt3eecp90gdrpfd4b
Qêsim
0
670567
6865583
6346870
2026-10-23T20:43:07Z
Wikihez
7344
sererastkirina nimûneyên ji edebiyatê (via JWB)
6865583
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Serenav ===
{{serenav|ku|z=n}}
# [[Qasim]]î, <br>[[form]]a [[çemandî]] anku [[tewandî]] ji [[nav]]ê "[[Qasim]]"
#* {{jêder|ku|jêgirtin=EFRÎN - Koçberên gundê '''Qêsim''' vedigerin warê xwe|Rudaw.net/kurmanci, 4/2019}}
==== Formên alternatîv ====
*[[Qasimî]]
6r8n72or76yylxoq1l85nqtgg2c4vi9
epaulette
0
968866
6865656
6863844
2026-10-24T10:44:52Z
Ziv
14071
([[c:GR|GR]]) [[File:Rank slide.jpg]] → [[File:Rank slide of a Royal Air Force sergeant.jpg]] [[c:COM:FR#FR2|Criterion 2]] · The name remained meaningless even after the first move.
6865656
wikitext
text/x-wiki
{{h-bnr|épaulette}}
== {{ziman|en}} ==
=== Bilêvkirin ===
* {{IPA|en|/ˈɛpəlɛt/|/ˌɛpəˈlɛt/}}
=== Navdêr ===
[[Wêne:Rank slide of a Royal Air Force sergeant.jpg|thumb|The '''epaulette''' of a British Army pullover.]]
{{en-navdêr}}
# [[apolet]]
==== Formên alternatîv ====
* {{herwiha|en|epaulet}}
==== Etîmolojî ====
{{etîmon|en|:deyn|fr:épaulette<etimon:qertaf<fr:épaule<t:[[mil]]>><fr:-ette>>}}
==== Herwiha binêre ====
* {{wpedî|ziman=en}}
9k121uy1lidy71i30up1xtfnmuxl1yt
MediaWiki:Gadget-wikt.edit.js
8
1359157
6865623
6426103
2026-10-23T22:15:27Z
Wikihez
7344
Data tables from wikt.edit-data
6865623
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit.js]]
*
* The shared code that only matters while editing: widgets, the wikitext parser, language lists and the
* full data tables (from wikt.edit-data). Required by wikt.dsb-panels and the wizard, never by the view path.
*
* var edit = require("ext.gadget.wikt.edit");
*/
var data = require("ext.gadget.wikt.edit-data");
// The two tables the view path needs live only in wikt.core's data-sections.json, and join the rest here.
var sectionsData = require("ext.gadget.wikt.core").sections.data;
data.posChoices = sectionsData.posChoices;
data.pageSubsections = sectionsData.pageSubsections;
// Say once which tables are missing rather than failing obscurely later in whichever form needs one.
var REQUIRED_DATA = ["posChoices", "templateAliases", "senseGroups", "pageSubsections",
"subsectionOrder", "wordlistTemplates", "leadingTplOrder", "lekturChoices", "lekturFormChoices",
"maneTags", "etymTpls", "metadata", "genderLabels", "numberLabels"];
var missing = REQUIRED_DATA.filter(function (key) {
return !data[key];
});
if (missing.length) {
mw.notify("wikt.edit: data-main.json kêm e — " + missing.join(", "), { type: "error" });
}
// Package file names as Gadgets-definition lists them: the files live under "wikt.edit/".
module.exports = {
util: require("./wikt.edit/util.js"),
wt: require("./wikt.edit/wikitext.js"),
langs: require("./wikt.edit/langs.js"),
labels: require("./wikt.edit/labels.js"),
posparams: require("./wikt.edit/posparams.js"),
data: data
};
// </nowiki>
qjgf2prs5kr1tfsam67jkd1het3k6wi
MediaWiki:Gadget-wikt.edit/wikitext.js
8
1359159
6865624
6865397
2026-10-23T22:15:32Z
Wikihez
7344
Data tables from wikt.edit-data
6865624
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/wikitext.js]]
*
*/
// Wikitext parsing and serialisation: template calls, kol/col word lists, template redirects and
// TemplateData lookups. No DOM, no wizard state.
(function () {
"use strict";
var util = require("./util.js");
var sections = require("ext.gadget.wikt.core").sections;
var startData = require("ext.gadget.wikt.edit-data");
var escapeRegExp = util.escapeRegExp;
var SubmitAbort = util.SubmitAbort;
var MANE_SECTION = sections.MANE_SECTION;
// The "needs a POS" maintenance tag under a placeholder "Mane" heading, under every name it is called by
// on real pages (data-main.json's maneTags). A space inside a tag name matches any run of whitespace.
var MANE_TAG_REGEX = new RegExp(
"^\\{\\{\\s*(?:" + startData.maneTags.map(function (tag) {
return escapeRegExp(tag).replace(/ +/g, "\\s+");
}).join("|") + ")\\s*(?:\\|[^{}]*)?\\}\\}\\s*$", "i");
var HEADING_LINE = /^(={3,6})[ ]*(.+?)[ ]*\1\s*$/;
// A numbered group heading ("=== 3. roj ===") with the maintenance tag before its first definition is a
// "Mane" section under another name, as sections.isManeHeading() reads it on the page. Its depth, or 0.
function numberedManeDepth(lines, index) {
var m = HEADING_LINE.exec(lines[index] || "");
if (!m || !sections.isNumberedHeadingText(m[2])) {
return 0;
}
for (var i = index + 1; i < lines.length && !/^[#=]/.test(lines[i]); i++) {
if (MANE_TAG_REGEX.test(lines[i])) {
return m[1].length;
}
}
return 0;
}
// The numbered group heading one level up that a section sits straight under, blank lines aside
// ("=== 2. dîn ===" then "==== Mane ===="), as a line index; -1 when there is none.
function numberedGroupAbove(lines, index, depth) {
var at = index - 1;
while (at >= 0 && !$.trim(lines[at])) {
at--;
}
var m = at >= 0 && HEADING_LINE.exec(lines[at]);
return m && m[1].length === depth - 1 && sections.isNumberedHeadingText(m[2]) ? at : -1;
}
var templateDataApi = new mw.Api();
// Templates that mean the same thing. The first entry of each row is what gets written; every entry is
// recognised when reading, so an existing call keeps the spelling the page already uses.
// Matched differently, so listed separately: WORDLIST_TEMPLATES and MANE_TAG_REGEX.
var TEMPLATE_ALIASES = startData.templateAliases;
// Every alias -> the name this gadget uses for it.
var TEMPLATE_CANONICAL = {};
Object.keys(TEMPLATE_ALIASES).forEach(function (key) {
TEMPLATE_ALIASES[key].forEach(function (alias) {
TEMPLATE_CANONICAL[alias] = key;
});
});
function canonicalTemplateKey(name) {
var trimmed = $.trim(name || "");
return TEMPLATE_CANONICAL[trimmed] || trimmed;
}
// Regex alternation matching any spelling of one key. Callers wrap it in their own group.
function aliasPattern(key) {
return (TEMPLATE_ALIASES[key] || [key]).map(escapeRegExp).join("|");
}
// {{kol}}, {{kol2}}, {{kol3}} and their col* spellings are the same column word-list template. New sections
// are written as kol3; any of the six is recognized when parsing an existing one.
var WORDLIST_TEMPLATES = startData.wordlistTemplates;
// The leading run of "{{...}}" calls on a definition line; "rest" is the gloss text after them.
function splitLeadingCalls(afterMarker) {
var calls = [];
var rest = afterMarker;
var leadingRegex = /^\{\{\s*([^{}|]+?)\s*\|([^{}]*)\}\}\s*/;
var m;
while ((m = leadingRegex.exec(rest)) !== null) {
calls.push({ key: m[1], params: m[2].split("|") });
rest = rest.slice(m[0].length);
}
return { calls: calls, rest: rest };
}
// Splits a template call's inner text on its top-level "|", ignoring pipes nested in {{...}} or [[...]].
function splitTemplateParams(inner) {
var parts = [];
var buf = "";
var tpl = 0;
var link = 0;
for (var i = 0; i < inner.length; i++) {
var two = inner.substr(i, 2);
if (two === "{{" || two === "}}" || two === "[[" || two === "]]") {
if (two === "{{") { tpl++; } else if (two === "}}") { tpl--; } else if (two === "[[") { link++; } else { link--; }
buf += two;
i++;
continue;
}
if (inner.charAt(i) === "|" && tpl === 0 && link === 0) {
parts.push(buf);
buf = "";
continue;
}
buf += inner.charAt(i);
}
parts.push(buf);
return parts;
}
// Every top-level "{{...}}" call in a string, and whether anything else (bar whitespace) sits between them.
function parseTemplateCalls(text) {
var calls = [];
var otherContent = false;
var plainFrom = 0;
var i = 0;
while (i < text.length) {
if (text.substr(i, 2) !== "{{") {
i++;
continue;
}
if ($.trim(text.slice(plainFrom, i))) {
otherContent = true;
}
var depth = 0;
var close = -1;
for (var j = i; j < text.length - 1; j++) {
var two = text.substr(j, 2);
if (two === "{{") {
depth++;
j++;
} else if (two === "}}") {
depth--;
j++;
if (depth === 0) {
close = j + 1;
break;
}
}
}
if (close === -1) {
otherContent = true; // unbalanced braces: not something to offer a form for
break;
}
var parts = splitTemplateParams(text.slice(i + 2, close - 2));
calls.push({ name: $.trim(parts[0]), params: parts.slice(1), start: i, end: close });
i = close;
plainFrom = close;
}
if ($.trim(text.slice(plainFrom))) {
otherContent = true;
}
return { calls: calls, otherContent: otherContent };
}
// "ku", "îngilîzî", "id=ziman" -> positional values in order plus a named map, keeping the keys the call
// actually uses (a positional value is keyed by its 1-based position, matching TemplateData).
function splitCallParams(params) {
var values = {};
var used = [];
var positional = 0;
params.forEach(function (raw) {
// Anything but | = { } can be a parameter name; Kurdish ones carry diacritics ("rêz=", "jêder=").
var m = /^\s*([^|={}\n]+?)\s*=([\s\S]*)$/.exec(raw);
var key;
if (m) {
key = m[1];
values[key] = $.trim(m[2]);
} else {
positional++;
key = String(positional);
values[key] = $.trim(raw);
}
used.push(key);
});
return { values: values, used: used };
}
// Rebuilds a call from the form's values. Positional parameters keep their slots even when blanked --
// dropping "1" would silently promote "2" into its place.
function buildTemplateCall(name, values, keys) {
var numeric = keys.filter(function (k) {
return /^\d+$/.test(k);
}).sort(function (a, b) {
return a - b;
});
var lastFilled = 0;
numeric.forEach(function (k) {
if ($.trim(values[k] || "")) {
lastFilled = Math.max(lastFilled, parseInt(k, 10));
}
});
var out = "{{" + name;
numeric.forEach(function (k) {
if (parseInt(k, 10) <= lastFilled) {
out += "|" + $.trim(values[k] || "");
}
});
keys.forEach(function (k) {
if (/^\d+$/.test(k)) {
return;
}
var v = $.trim(values[k] || "");
if (v) {
out += "|" + k + "=" + v;
}
});
return out + "}}";
}
// TemplateData labels and descriptions are {langCode: text} objects; prefer Kurdish, then the wiki's own
// content language, then whatever is present.
function tdText(value) {
if (!value) {
return "";
}
if (typeof value === "string") {
return value;
}
return value.ku || value[mw.config.get("wgContentLanguage")] || value[Object.keys(value)[0]] || "";
}
// Cached per template name. A template with no TemplateData resolves to null, not an error -- the form
// still renders, just with raw parameter names for labels.
var templateDataCache = {};
// Fetches TemplateData for many templates in as few requests as the API's 50-title cap allows, filling
// the same cache fetchTemplateData() reads -- so a picker with hundreds of entries (Kategorî:Çavkanî,
// say) costs a handful of requests instead of one per template. Every requested name is cached (a miss
// as null) by the time this resolves; a name already cached, pending or not, is left untouched.
function fetchTemplateDataBatch(names) {
var missing = [];
(names || []).forEach(function (name) {
if (!name || templateDataCache[name] || missing.indexOf(name) !== -1) {
return;
}
// Baked-in entries answer here too, so a picker row for one costs nothing.
var baked = bakedTemplateData(name);
if (baked) {
templateDataCache[name] = $.Deferred().resolve(baked).promise();
return;
}
missing.push(name);
});
if (!missing.length) {
return $.Deferred().resolve().promise();
}
var chunks = [];
for (var i = 0; i < missing.length; i += 50) {
chunks.push(missing.slice(i, i + 50));
}
function fetchChunk(chunk) {
return templateDataApi.get({
action: "templatedata",
titles: chunk.map(function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
// "pages" is an object keyed by page id even under formatversion=2 -- this endpoint (a
// third-party extension, not a core action) doesn't follow the "pages as array" convention
// core actions do -- and a title with no TemplateData at all is simply absent, not listed
// with an empty entry.
var byTitle = {};
$.each((data && data.pages) || {}, function (_, page) {
if (page && page.title) {
byTitle[page.title] = page;
}
});
// A redirect's/normalization's own target may itself need one more hop.
((data && data.redirects) || []).forEach(function (r) {
if (byTitle[r.to] && !byTitle[r.from]) {
byTitle[r.from] = byTitle[r.to];
}
});
((data && data.normalized) || []).forEach(function (n) {
if (byTitle[n.to] && !byTitle[n.from]) {
byTitle[n.from] = byTitle[n.to];
}
});
chunk.forEach(function (name) {
templateDataCache[name] = $.Deferred().resolve(byTitle["Şablon:" + name] || null).promise();
});
}, function () {
chunk.forEach(function (name) {
templateDataCache[name] = $.Deferred().resolve(null).promise();
});
});
}
return $.when.apply($, chunks.map(fetchChunk));
}
// data-main.json's own copy of the parameter lists for the templates whose dialogs open constantly, so
// those need no request at all. Shaped like the API's own answer, so nothing downstream can tell the
// difference; a template that is not in the table is fetched as before.
function bakedTemplateData(name) {
var baked = (startData.templateParams || {})[name];
if (!baked) {
return null;
}
var params = {};
(baked.order || []).forEach(function (key) {
params[key] = { label: (baked.labels || {})[key] || null };
});
return { title: "Şablon:" + name, params: params, paramOrder: baked.order || [] };
}
function fetchTemplateData(name) {
if (!templateDataCache[name]) {
var baked = bakedTemplateData(name);
if (baked) {
templateDataCache[name] = $.Deferred().resolve(baked).promise();
return templateDataCache[name];
}
}
if (!templateDataCache[name]) {
templateDataCache[name] = templateDataApi.get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = data && data.pages;
var key = pages && Object.keys(pages)[0];
return key ? pages[key] : null;
}, function () {
return null;
});
}
return templateDataCache[name];
}
// One line of a TemplateData description, capped so a picker row stays a picker row rather than
// wrapping. Shared so every picker that shows descriptions trims them the same way.
// Numbers repeated part-of-speech headings inside one language section: two "=== Rengdêr ===" are
// indistinguishable on the page, in the table of contents and to anything linking at one. Every
// repeated heading is numbered 1..n, so an existing lone "Rengdêr" becomes "Rengdêr 1" the moment a
// second is added, and a heading that stops being repeated loses its number again.
//
// Numbers already on a heading are stripped before counting, so the result depends only on what is
// there now -- removing the middle one of three closes the gap instead of leaving 1, 3.
// Only "===" headings are touched: "====" subsections and the "==" language header are left alone.
function numberRepeatedPosHeadings(sectionText) {
var HEADING = /^(===[ \t]*)([^=\n]+?)([ \t]*===)[ \t]*$/gm;
function base(name) {
return name.replace(/\s+\d+$/, "");
}
var counts = {};
var m;
HEADING.lastIndex = 0;
while ((m = HEADING.exec(sectionText)) !== null) {
var b = base(m[2]);
counts[b] = (counts[b] || 0) + 1;
}
var seen = {};
HEADING.lastIndex = 0;
return sectionText.replace(HEADING, function (all, open, name, close) {
var key = base(name);
if (counts[key] < 2) {
return open + key + close;
}
seen[key] = (seen[key] || 0) + 1;
return open + key + " " + seen[key] + close;
});
}
function shortDesc(text, maxLen) {
maxLen = maxLen || 90;
var line = $.trim(String(text || "").split("\n")[0]);
return line.length > maxLen ? line.slice(0, maxLen) + "…" : line;
}
// Pulls a {{kol3|ku|cure=Jê|word|word}}-style call apart into the bits an edit has to preserve (template
// name, language, named params) plus the positional word list. Null if the body has no such call.
// Every word-list call in a section body, in order with absolute offsets. A section commonly holds
// several: one per "sernav=" group, often alongside an unheaded one.
function parseWordListCalls(text) {
var calls = [];
var offset = 0;
for (;;) {
var call = parseWordListCall(text.slice(offset));
if (!call) {
return calls;
}
call.start += offset;
call.end += offset;
calls.push(call);
offset = call.end;
}
}
// A "name=" parameter; Kurdish names carry diacritics ("veşêre=").
var NAMED_ARG = new RegExp("^[\\p{L}][\\p{L}\\p{N}_-]*\\s*=", "u");
function parseWordListCall(text) {
var m = new RegExp("\\{\\{\\s*(" + WORDLIST_TEMPLATES.join("|") + ")\\s*[|}]", "i").exec(text);
if (!m) {
return null;
}
var open = m.index;
var depth = 0;
var close = -1;
for (var i = open; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++;
i++;
} else if (two === "}}") {
depth--;
i++;
if (depth === 0) {
close = i + 1;
break;
}
}
}
if (close === -1) {
return null;
}
var parts = splitTemplateParams(text.slice(open + 2, close - 2));
var named = [];
var words = [];
for (var pi = 2; pi < parts.length; pi++) {
var val = $.trim(parts[pi]);
if (!val) {
continue;
}
if (NAMED_ARG.test(val)) {
named.push(val);
} else {
words.push(val);
}
}
return {
name: $.trim(parts[0]),
lang: parts.length > 1 ? $.trim(parts[1]) : "",
named: named,
words: words,
start: open,
end: close
};
}
// Named parameters of a kol/col call are carried around as raw "key=value" strings ("cure=Jê").
// Modul:columns reads "sernav" as an alias of "title", so either spelling is the one parameter.
var NAMED_PARAM_SPELLINGS = { sernav: ["sernav", "title"] };
// Matches "key =" under any spelling of key; group 1 is the spelling used.
function namedParamPrefix(key) {
return new RegExp("^\\s*(" + (NAMED_PARAM_SPELLINGS[key] || [key]).map(escapeRegExp).join("|") + ")\\s*=");
}
function namedParam(named, key) {
var prefix = namedParamPrefix(key);
for (var i = 0; i < named.length; i++) {
if (prefix.test(named[i])) {
return $.trim(named[i].replace(prefix, ""));
}
}
return "";
}
// A copy of `named` with key set, under the spelling the call already uses, or with it removed when the
// value is blank.
function withNamedParam(named, key, value) {
var prefix = namedParamPrefix(key);
var spelling = key;
var out = named.filter(function (param) {
var m = prefix.exec(param);
if (m) {
spelling = m[1];
}
return !m;
});
if ($.trim(value || "")) {
out.push(spelling + "=" + $.trim(value));
}
return out;
}
// Canonical multi-line layout, matching how existing ku.wiktionary lists are written (one word per line).
function buildWordListCall(name, lang, named, words) {
var head = "{{" + name + "|" + lang;
named.forEach(function (n) {
head += "|" + n;
});
return words.length ? head + "\n|" + words.join("\n|") + "\n}}" : head + "\n}}";
}
// A word-list section body written as plain bullets -- "* [[kelîme]]" or "* {{g|ku|kelîme}}" -- read as a word
// list so it can be edited in the normal widget and saved back as a real {{kol3}}. Null for anything
// richer (a piped link, a {{g}} carrying extra parameters, prose between the bullets), which is left as
// raw wikitext rather than guessed at.
// A "{{mj|a|b}}" label at one end of a bullet. Several labels become one comma-joined modifier value,
// which is how the word-list templates read "<e:...>" anyway.
var MJ_LEADING = /^\{\{\s*mj\s*\|([^{}]*)\}\}\s*/;
var MJ_TRAILING = /\s*\{\{\s*mj\s*\|([^{}]*)\}\}$/;
function mjValue(params) {
return params.split("|").map(function (part) {
return $.trim(part);
}).filter(Boolean).join(", ");
}
/**
* One bullet's word, or null if the line is carrying something this cannot express.
*
* "[[kelîme]]" and "{{g|ku|kelîme}}" are the plain forms. A "{{mj|...}}" label in front of the word becomes
* an "<e:...>" modifier and one after it "<ee:...>" -- the same distinction the label templates make
* between a label that reads before the word and one that reads after it.
*/
function parseBulletWord(item) {
var lead = "";
var tail = "";
var m = MJ_LEADING.exec(item);
if (m) {
lead = mjValue(m[1]);
item = $.trim(item.slice(m[0].length));
}
m = MJ_TRAILING.exec(item);
if (m) {
tail = mjValue(m[1]);
item = $.trim(item.slice(0, item.length - m[0].length));
}
var word = null;
var link = /^\[\[([^\[\]|]+)\]\]$/.exec(item);
if (link) {
word = $.trim(link[1]);
} else {
var parsed = parseTemplateCalls(item);
if (parsed.otherContent || parsed.calls.length !== 1) {
return null;
}
var call = parsed.calls[0];
// {{g|lang|kelîme}} exactly: a third parameter means it is carrying something this cannot express.
if (canonicalTemplateKey(call.name) !== "g" || call.params.length !== 2) {
return null;
}
word = $.trim(call.params[1]);
}
if (!word) {
return null;
}
return word + (lead ? "<e:" + lead + ">" : "") + (tail ? "<ee:" + tail + ">" : "");
}
function parseBulletWordList(body) {
var lines = (body || "").split("\n");
var words = [];
for (var i = 0; i < lines.length; i++) {
var line = $.trim(lines[i]);
if (!line) {
continue;
}
var bullet = /^\*\s*(.+)$/.exec(line);
if (!bullet) {
return null;
}
var word = parseBulletWord($.trim(bullet[1]));
if (word === null) {
return null;
}
words.push(word);
}
return words.length ? words : null;
}
// "{{h-bnr|a|b}}" — the "herwiha binêre" hatnote that sits above the first language heading. Empty
// entries are dropped; an empty list has no call at all. name keeps whichever of the seven spellings
// the page already uses, so editing one never renames it.
function buildHbnrCall(pages, name) {
var kept = $.grep(pages || [], function (p) { return !!$.trim(p); });
return kept.length ? "{{" + (name || "h-bnr") + "|" + $.map(kept, $.trim).join("|") + "}}" : "";
}
// The "{{herwiha binêre|...}}" call on a page, under any of its names, or null. subpage is set when
// the call names one -- "{{h-bnr/-ane}}" -- because those hold a list shared by every page that
// transcludes them, which lives on the template rather than here.
function findHbnrCall(pageContent) {
var lines = (pageContent || "").split("\n");
var pattern = new RegExp("^\\{\\{\\s*(" + aliasPattern("h-bnr") + ")(/[^|}]*)?\\s*([|}])", "i");
for (var i = 0; i < lines.length; i++) {
if (/^==[^=]/.test(lines[i])) {
break; // the hatnote sits above the first language heading
}
var m = pattern.exec($.trim(lines[i]));
if (m) {
var call = parseTemplateCalls($.trim(lines[i])).calls[0];
return {
lineIndex: i,
name: m[1] + (m[2] || ""),
subpage: m[2] ? m[1] + m[2] : null,
words: (call && call.params) || []
};
}
}
return null;
}
// One "[[Wêne:...|thumb|...]]" line. alt is left out when it is empty rather than written blank.
// extra is the line's own display keywords ("thumb", "left", "frameless", ...) in the order it had them,
// kept so editing a caption never drops them; a new image gets "thumb". size ("250px", "upright=1.2")
// has its own box, so it is written after them.
function buildImageLine(image) {
var file = $.trim((image && image.file) || "");
if (!file) {
return "";
}
var extra = (image.extra && image.extra.length) ? image.extra : ["thumb"];
var size = $.trim(image.size || "");
var alt = $.trim(image.alt || "");
return "[[Wêne:" + file + "|" + extra.join("|") + (size ? "|" + size : "") +
(alt ? "|alt=" + alt : "") + "|" + $.trim(image.caption || "") + "]]";
}
var IMAGE_SIZE = /^\s*(?:\d+\s*px|x\s*\d+\s*px|\d+\s*x\s*\d+\s*px|upright(?:\s*=\s*[\d.]+)?)\s*$/i;
var IMAGE_KEYWORD = /^\s*(?:thumb|thumbnail|frame|framed|frameless|border|right|left|center|centre|none|baseline|middle|top|bottom)\s*$/i;
// The {file, caption, alt} behind one "[[Wêne:...|thumb|...]]" line, or null for anything else. The
// caption is the last parameter; "alt=" and the sizing/placement keywords are read off the ones before
// it, so a hand-written line with "|left|200px" still comes back editable.
//
// Split on top-level pipes only: a caption routinely holds "{{w|Riya Teze (rojname)|Riya Teze}}" or
// "[[kaxiz|kaxizên]]", and a plain split would take their pipes for the image's own.
function parseImageLine(line) {
var m = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:([\s\S]*)\]\]$/i.exec($.trim(line || ""));
if (!m || !closesOnlyAtEnd(m[1])) {
return null;
}
var parts = splitTemplateParams(m[1]);
var file = $.trim(parts.shift());
if (!file) {
return null;
}
var alt = "";
var caption = "";
var size = "";
var extra = [];
$.each(parts, function (_, part) {
var altMatch = /^\s*alt\s*=\s*([\s\S]*)$/i.exec(part);
if (altMatch) {
alt = $.trim(altMatch[1]);
return;
}
if (IMAGE_SIZE.test(part)) {
size = $.trim(part);
return;
}
if (IMAGE_KEYWORD.test(part)) {
extra.push($.trim(part));
return;
}
caption = $.trim(part);
});
return { file: file, caption: caption, alt: alt, size: size, extra: extra };
}
// Whether a link's inner text never closes the link early: "a.jpg|x]] [[Wêne:b.jpg|y" would otherwise
// pass for one image holding the text of two.
function closesOnlyAtEnd(inner) {
var depth = 0;
for (var i = 0; i < inner.length; i++) {
var two = inner.substr(i, 2);
if (two === "[[") {
depth++;
i++;
} else if (two === "]]") {
depth--;
i++;
if (depth < 0) {
return false;
}
}
}
return depth === 0;
}
// A caption on a non-Kurdish entry is written in that language, so it is wrapped for the reader --
// unless whoever typed it already wrapped it themselves.
function captionForLang(caption, lang) {
var text = $.trim(caption || "");
if (!text || !lang || lang === "ku" || /^\{\{\s*zim\s*\|/i.test(text)) {
return text;
}
return "{{zim|" + lang + "|" + text + "}}";
}
// One "* {{dû|lang|kelîme|kelîme}}" line of a Dûnde list.
function buildDundeLine(lang, words) {
var kept = $.grep(words || [], function (w) { return !!$.trim(w); });
return kept.length ? "* {{dû|" + lang + "|" + $.map(kept, $.trim).join("|") + "}}" : "";
}
// Regex source for a "== {{ziman|CODE}} ==" heading; codePattern is a regex fragment. No leading
// newline: callers that want one prepend it, and callers that index into the page must not have it.
function langHeaderSource(codePattern) {
return "==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
/**
* One TemplateData page in the shape the pickers actually want: the description, and one entry per
* documented parameter in the template's own paramOrder. Labels and descriptions can be language maps,
* so tdText picks the right one; null when the template has no TemplateData at all, which is common.
*
* Shaped once and kept, so the wizard's ⓘ popup, the citation form and DefSideBoxes' template dialog
* share one request and one copy per template.
*/
var shapedCache = {};
function shapeTemplateDoc(doc) {
if (!doc) {
return null;
}
var params = [];
(doc.paramOrder || Object.keys(doc.params || {})).forEach(function (key) {
var p = (doc.params || {})[key];
if (!p) {
return;
}
params.push({
key: key,
label: tdText(p.label) || "",
required: !!p.required,
deprecated: !!p.deprecated
});
});
return { desc: tdText(doc.description) || "", params: params };
}
function templateDoc(name) {
if (Object.prototype.hasOwnProperty.call(shapedCache, name)) {
return $.Deferred().resolve(shapedCache[name]).promise();
}
return fetchTemplateData(name).then(function (doc) {
shapedCache[name] = shapeTemplateDoc(doc);
return shapedCache[name];
});
}
// Many at once, so a picker full of rows costs one request. Resolves once every name is shaped and in
// the cache, which is what a caller that then reads templateDocNow() depends on.
function templateDocBatch(names) {
var wanted = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(shapedCache, n);
});
if (!wanted.length) {
return $.Deferred().resolve().promise();
}
return fetchTemplateDataBatch(wanted).then(function () {
return $.when.apply($, wanted.map(function (n) {
return templateDoc(n);
}));
});
}
// What is already known, without asking: null for a template nobody has fetched yet. For drawing a
// list that must not wait, after templateDocBatch() has filled the cache.
function templateDocNow(name) {
return Object.prototype.hasOwnProperty.call(shapedCache, name) ? shapedCache[name] : null;
}
// ---- locating a section, and a definition inside it ----
//
// One copy, shared: DefSideBoxes and add-examples both write into a numbered sense of a numbered
// section, and both find it here.
// Start and end offsets of one language section within the page. Throws when the page has no such
// section, naming the code that was looked for.
function langSectionRange(pageContent, language) {
var langSectionIndex = pageContent.search(new RegExp(langHeaderSource(escapeRegExp(language))));
if (langSectionIndex === -1) {
// Quoted: a stray space or wrong case in the code is invisible otherwise.
throw new SubmitAbort("Xetayeke bi koda zimanî heye. Koda zimanî: " + JSON.stringify(language));
}
var afterLangHeading = pageContent.indexOf("\n", langSectionIndex);
afterLangHeading = afterLangHeading === -1 ? pageContent.length : afterLangHeading + 1;
var nextLangSectionMatch = /\n==[^=\n][^\n]*==/.exec(pageContent.slice(afterLangHeading));
return {
start: langSectionIndex,
end: nextLangSectionMatch ? afterLangHeading + nextLangSectionMatch.index : pageContent.length
};
}
/**
* The line index of one "=== ... ===" heading, and the lines to read it from.
*
* The heading is found by section key + ordinal, never by a DOM id number: MediaWiki's page-wide
* anchor uniquification can attach a number to a heading that has nothing to do with its position.
* The closing run is anchored to the opening one, or "=== Navdêra pirjimar ===" would count as a
* "Navdêr" here while sectionTypeOf() rejects it, and the two ordinals would drift apart.
*
* A part of speech is usually "===", but an entry that groups its senses under numbered etymologies
* writes it one level deeper ("=== Etîmolojî 1 ===" then "==== Lêker ===="). The depth is read off
* the heading rather than assumed, and comes back with it: a subsection of that section is one
* deeper again.
*
* lines covers the whole rest of the page from the language heading onward, not just this language's
* own section: callers rebuild the saved page out of it, and truncating here would drop every later
* language. Only the heading search itself is bounded, so a same-named heading in the next language
* can never be matched by mistake.
*/
function locateSection(pageContent, language, sectionType, sectionOrdinal) {
var headingNames = sections.headingNamesFor(sectionType);
if (!headingNames) {
throw new SubmitAbort("Navê beşê xelet e: " + JSON.stringify(sectionType));
}
var langRange = langSectionRange(pageContent, language);
var langSectionIndex = langRange.start;
var lines = pageContent.slice(langSectionIndex).split("\n");
var searchEndLine = pageContent.slice(langSectionIndex, langRange.end).split("\n").length;
// Every recognised heading name, so the ordinal counts headings of this kind and no others.
var namePattern = Object.keys(sections.sectionNames).concat([MANE_SECTION])
.map(escapeRegExp).join("|");
var headingRegex = new RegExp("^(={3,6})[ ]*(" + namePattern + ")\\s*(\\d+)?[ ]*\\1");
var posLineIndex;
var occurrence = 0;
var depth = 3;
for (posLineIndex = 0; posLineIndex < searchEndLine; posLineIndex++) {
var m = headingRegex.exec(lines[posLineIndex]);
var numbered = m ? 0 : numberedManeDepth(lines, posLineIndex);
var name = m ? m[2] : (numbered ? MANE_SECTION : null);
if (name && headingNames.indexOf(name) !== -1) {
occurrence++;
if (occurrence === sectionOrdinal) {
depth = m ? m[1].length : numbered;
break;
}
}
}
if (posLineIndex === searchEndLine) {
throw new SubmitAbort("Di navê beşê de xetayek heye. Mihtemelen çend beş bi vî navî hene.");
}
return { langSectionIndex: langSectionIndex, lines: lines, posLineIndex: posLineIndex, depth: depth };
}
// The "=" run for a heading depth, and a matcher for any heading at or above one (a smaller run is a
// bigger section, so "at or above" means "this deep or shallower").
function headingMarks(depth) {
return new Array(depth + 1).join("=");
}
function headingAtOrAbove(depth) {
return new RegExp("^={2," + depth + "}[^=]");
}
function headingAtDepth(depth) {
var marks = headingMarks(depth);
return new RegExp("^" + marks + "\\s*([^=]+?)\\s*" + marks + "\\s*$");
}
/**
* One heading found by its own text, and the lines around it.
*
* The section keys locateSection() knows cover the parts of speech; a heading like
* "=== Etîmolojî ===" that sits beside them rather than under one is matched by its own text
* instead, at any depth from "===" down. The ordinal counts headings carrying that same text within
* the language, which is what the DOM side counts too.
*
* bodyEnd is the next heading of any depth: a section's own text is what lies between its heading and
* the next one, so editing "=== Etîmolojî 1 ===" never reaches the parts of speech under it.
*/
function locateHeadingByText(pageContent, language, header, ordinal) {
var langRange = langSectionRange(pageContent, language);
var langSectionIndex = langRange.start;
var lines = pageContent.slice(langSectionIndex).split("\n");
var searchEndLine = pageContent.slice(langSectionIndex, langRange.end).split("\n").length;
var headingRegex = /^(={3,6})[ ]*(.+?)[ ]*\1\s*$/;
var nextHeading = headingAtOrAbove(6);
var occurrence = 0;
for (var i = 0; i < searchEndLine; i++) {
var m = headingRegex.exec(lines[i]);
if (!m || m[2] !== header) {
continue;
}
occurrence++;
if (occurrence !== (ordinal || 1)) {
continue;
}
var end = lines.length;
for (var j = i + 1; j < lines.length; j++) {
if (nextHeading.test(lines[j])) {
end = j;
break;
}
}
return {
langSectionIndex: langSectionIndex,
lines: lines,
headingLineIndex: i,
depth: m[1].length,
bodyStart: i + 1,
bodyEnd: end
};
}
throw new SubmitAbort(util.msg.sectionMissing + header);
}
/**
* The line index of one definition under a section heading.
*
* definitionLevel is the shape both gadgets already pass around: element 0 is the "type|ordinal"
* string locateSection() has resolved, and the rest are 0-based indices, one per nesting level, so
* ["Navdêr|1", 1, 0] is the first sub-sense of the second sense. Only "#" lines count -- "#:", "#*"
* and "##" belong to a sense rather than starting one.
*/
function findDefinitionLine(lines, posLineIndex, definitionLevel) {
var defIndex = -1;
var level = 1;
for (var i = posLineIndex; i < lines.length; i++) {
// The senses end where the next heading starts; counting on would find a sense of another
// section and edit that one instead.
if (i > posLineIndex && /^==/.test(lines[i])) {
break;
}
var m = /^(#+)[^*#:]/.exec(lines[i]);
if (m && m[1].length === level) {
defIndex++;
if (definitionLevel[level] === defIndex) {
if (level === definitionLevel.length - 1) {
return i;
}
level++;
defIndex = -1;
}
}
}
throw new SubmitAbort("Nikare rêza vê maneyê di metnê de bibîne.");
}
// Parameter names a headword template reads as another: Modul:rêziman declares "z" alias_of "c", so
// "z=m" and "c=m" are one gender. A numbered one maps the same way ("z2" is "c2").
var PARAM_ALIASES = { z: "c" };
function canonicalParamName(name) {
var trimmed = $.trim(name || "");
var m = /^([a-z]+)(\d*)$/i.exec(trimmed);
return m && Object.prototype.hasOwnProperty.call(PARAM_ALIASES, m[1])
? PARAM_ALIASES[m[1]] + m[2]
: trimmed;
}
// A named parameter of the first call in a line, read with the parser above rather than a pattern, so
// a "c=" inside a nested template is never taken for the call's own. Names match canonically, so
// asking for "c" also finds "z=". Empty when the line holds no closed call or the parameter is unset.
function callParamValue(text, name) {
var call = parseTemplateCalls($.trim(text || "")).calls[0];
if (!call) {
return "";
}
var values = splitCallParams(call.params).values;
var want = canonicalParamName(name);
var found = "";
Object.keys(values).forEach(function (key) {
if (!found && values[key] && canonicalParamName(key) === want) {
found = values[key];
}
});
return found;
}
module.exports = {
canonicalParamName: canonicalParamName,
callParamValue: callParamValue,
langHeaderSource: langHeaderSource,
langSectionRange: langSectionRange,
MANE_TAG_REGEX: MANE_TAG_REGEX,
locateSection: locateSection,
numberedGroupAbove: numberedGroupAbove,
locateHeadingByText: locateHeadingByText,
headingMarks: headingMarks,
headingAtOrAbove: headingAtOrAbove,
headingAtDepth: headingAtDepth,
findDefinitionLine: findDefinitionLine,
canonicalTemplateKey: canonicalTemplateKey,
aliasPattern: aliasPattern,
splitLeadingCalls: splitLeadingCalls,
splitTemplateParams: splitTemplateParams,
parseTemplateCalls: parseTemplateCalls,
splitCallParams: splitCallParams,
buildTemplateCall: buildTemplateCall,
buildHbnrCall: buildHbnrCall,
findHbnrCall: findHbnrCall,
buildImageLine: buildImageLine,
parseImageLine: parseImageLine,
captionForLang: captionForLang,
buildDundeLine: buildDundeLine,
tdText: tdText,
fetchTemplateData: fetchTemplateData,
templateDoc: templateDoc,
templateDocBatch: templateDocBatch,
templateDocNow: templateDocNow,
fetchTemplateDataBatch: fetchTemplateDataBatch,
shortDesc: shortDesc,
numberRepeatedPosHeadings: numberRepeatedPosHeadings,
parseWordListCall: parseWordListCall,
parseWordListCalls: parseWordListCalls,
namedParam: namedParam,
withNamedParam: withNamedParam,
buildWordListCall: buildWordListCall,
parseBulletWordList: parseBulletWordList
};
// </nowiki>
}());
ddx1iggcsykc737cjwi0im6zi9xs9rt
MediaWiki:Gadget-wikt.edit/langs.js
8
1359160
6865534
6533063
2026-10-23T19:43:01Z
Wikihez
7344
Language fields: the translation adder's dropdown (names and codes searched) instead of a datalist
6865534
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/langs.js]]
*/
// ~300 common languages ship with the gadget; the remaining ~3400 are fetched from the translation
// editor's copy only when a search finds nothing in the short list.
( function () {
"use strict";
var core = require( "./ziman-core.json" );
var FULL_PAGE = "MediaWiki:Gadget-translation editor.js/ziman.json";
var full = null;
var pending = null;
var listeners = [];
// {code: name} -- the full list once it has arrived, the short one until then.
function names() {
return full || core;
}
function isFull() {
return !!full;
}
// Resolves with the full list, fetched once; falls back to the short list rather than rejecting.
function ensureFull() {
if ( full ) {
return $.Deferred().resolve( full ).promise();
}
if ( pending ) {
return pending;
}
pending = $.get( {
url: mw.util.getUrl( FULL_PAGE, { action: "raw" } ),
dataType: "text"
} ).then( function ( text ) {
try {
full = $.extend( {}, core, JSON.parse( text ) );
} catch ( e ) {
full = core;
}
pending = null;
listeners.forEach( function ( fn ) {
fn( full );
} );
return full;
}, function () {
pending = null;
return core;
} );
return pending;
}
// Called with the full list when it lands, so a picker built from the short one can rebuild itself.
function onFull( fn ) {
if ( full ) {
fn( full );
} else {
listeners.push( fn );
}
}
var PICKER_SIZE = 12;
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// A name without its diacritics, so "ital" finds "îtalî".
function fold( s ) {
return s.toLowerCase().normalize( "NFD" ).replace( /[̀-ͯ]/g, "" );
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey( s ) {
return Array.from( s.toLowerCase().replace( /['ʼ", -]/g, "" ) ).map( function ( ch ) {
var i = ALPHABET.indexOf( ch );
if ( i !== -1 ) {
return String.fromCharCode( 0x100 + i * 2 );
}
i = ALPHABET.indexOf( ch.normalize( "NFD" ).charAt( 0 ) );
return i !== -1 ? String.fromCharCode( 0x101 + i * 2 ) : ch;
} ).join( "" );
}
// Up to PICKER_SIZE codes for what was typed, in the translation adder's order: the code itself, then
// names starting with it (shortest first), then codes starting with it (short-list languages first),
// then names containing it.
function match( term ) {
var all = names();
term = $.trim( term || "" ).toLowerCase();
var exact = all[ term ] ? [ term ] : [];
if ( term.length < 2 ) {
return exact;
}
var folded = fold( term );
var starts = [], contains = [], codes = [];
Object.keys( all ).forEach( function ( code ) {
if ( code === term ) {
return;
}
var i = fold( all[ code ] ).indexOf( folded );
if ( i === 0 ) {
starts.push( code );
} else if ( code.toLowerCase().indexOf( term ) === 0 ) {
codes.push( code );
} else if ( i !== -1 ) {
contains.push( code );
}
} );
function byName( a, b ) {
var ka = sortKey( all[ a ] ), kb = sortKey( all[ b ] );
return ka < kb ? -1 : ka > kb ? 1 : 0;
}
starts.sort( function ( a, b ) {
return all[ a ].length - all[ b ].length || byName( a, b );
} );
codes.sort( function ( a, b ) {
return ( !!core[ b ] - !!core[ a ] ) || a.length - b.length || ( a < b ? -1 : 1 );
} );
return exact.concat( starts, codes, contains.sort( byName ) ).slice( 0, PICKER_SIZE );
}
// Wraps a language-code input in the translation adder's dropdown: names and codes are searched, and
// picking one writes its code. onChange( code ) runs after a pick. The menu is fixed to the viewport
// outside the form, so a panel that clips its content does not cut it off.
// One menu for every picker on the page, since only the focused box shows it.
var $menu = null;
function sharedMenu() {
if ( !$menu ) {
$menu = $( "<ul class='wiktku-langmenu' role='listbox'></ul>" ).hide().appendTo( document.body );
// A scroll elsewhere would leave the menu floating away from its box.
window.addEventListener( "scroll", function ( e ) {
if ( e.target !== $menu[ 0 ] ) {
$menu.hide();
}
}, true );
}
return $menu;
}
function picker( input, onChange ) {
input = $( input )[ 0 ];
var $menu = sharedMenu();
var codes = [];
var active = -1;
input.setAttribute( "autocomplete", "off" );
function showName() {
var name = names()[ $.trim( input.value ) ];
input.title = name || "";
}
function close() {
$menu.hide();
active = -1;
}
function choose( code ) {
input.value = code;
close();
showName();
if ( onChange ) {
onChange( code );
}
}
function render() {
var all = names();
$menu.empty();
codes.forEach( function ( code, i ) {
$( "<li role='option'></li>" )
.toggleClass( "wiktku-langmenu-active", i === active )
.append( document.createTextNode( all[ code ] ),
$( "<span class='wiktku-langmenu-code'></span>" ).text( " " + code ) )
// Keeps the focus in the input, so its blur does not close the menu first.
.on( "mousedown", function ( e ) {
e.preventDefault();
choose( code );
} )
.appendTo( $menu );
} );
// Inside an OOUI dialog the menu has to sit in its window manager, or the modal covers it.
var container = $( input ).closest( ".oo-ui-windowManager" )[ 0 ] || document.body;
if ( $menu[ 0 ].parentNode !== container ) {
// Opening a modal makes the rest of the body inert, the menu with it if it was there.
$menu.appendTo( container ).removeAttr( "inert aria-hidden" );
}
var rect = input.getBoundingClientRect();
$menu.css( { left: rect.left, top: rect.bottom } ).toggle( codes.length > 0 );
var item = $menu.children()[ active ];
if ( item ) {
item.scrollIntoView( { block: "nearest" } );
}
}
function search() {
codes = match( input.value );
active = codes.length ? 0 : -1;
render();
}
$( input ).on( "input", function () {
search();
showName();
// Nothing found in the short list: fetch the rest and search again if the box still has focus.
if ( codes.length < PICKER_SIZE && !full && $.trim( input.value ).length > 1 ) {
ensureFull().then( function () {
if ( document.activeElement === input ) {
search();
}
} );
}
} ).on( "keydown", function ( e ) {
if ( !$menu.is( ":visible" ) || !codes.length ) {
return;
}
if ( e.key === "ArrowDown" || e.key === "ArrowUp" ) {
e.preventDefault();
active = ( active + ( e.key === "ArrowDown" ? 1 : codes.length - 1 ) ) % codes.length;
render();
} else if ( e.key === "Enter" && active >= 0 ) {
e.preventDefault();
choose( codes[ active ] );
} else if ( e.key === "Escape" ) {
e.stopPropagation();
close();
}
} ).on( "blur", close );
showName();
return input;
}
module.exports = {
names: names,
isFull: isFull,
ensureFull: ensureFull,
onFull: onFull,
match: match,
picker: picker
};
}() );
// </nowiki>
e1230is5gbt71w2ib1oxj210x734avd
6865540
6865534
2026-10-23T19:48:01Z
Wikihez
7344
Part-of-speech card: script codes (sc) in the same dropdown as the language fields
6865540
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/langs.js]]
*/
// ~300 common languages ship with the gadget; the remaining ~3400 are fetched from the translation
// editor's copy only when a search finds nothing in the short list.
( function () {
"use strict";
var core = require( "./ziman-core.json" );
var FULL_PAGE = "MediaWiki:Gadget-translation editor.js/ziman.json";
var full = null;
var pending = null;
var listeners = [];
// {code: name} -- the full list once it has arrived, the short one until then.
function names() {
return full || core;
}
function isFull() {
return !!full;
}
// Resolves with the full list, fetched once; falls back to the short list rather than rejecting.
function ensureFull() {
if ( full ) {
return $.Deferred().resolve( full ).promise();
}
if ( pending ) {
return pending;
}
pending = $.get( {
url: mw.util.getUrl( FULL_PAGE, { action: "raw" } ),
dataType: "text"
} ).then( function ( text ) {
try {
full = $.extend( {}, core, JSON.parse( text ) );
} catch ( e ) {
full = core;
}
pending = null;
listeners.forEach( function ( fn ) {
fn( full );
} );
return full;
}, function () {
pending = null;
return core;
} );
return pending;
}
// Called with the full list when it lands, so a picker built from the short one can rebuild itself.
function onFull( fn ) {
if ( full ) {
fn( full );
} else {
listeners.push( fn );
}
}
var PICKER_SIZE = 12;
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// A name without its diacritics, so "ital" finds "îtalî".
function fold( s ) {
return s.toLowerCase().normalize( "NFD" ).replace( /[̀-ͯ]/g, "" );
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey( s ) {
return Array.from( s.toLowerCase().replace( /['ʼ", -]/g, "" ) ).map( function ( ch ) {
var i = ALPHABET.indexOf( ch );
if ( i !== -1 ) {
return String.fromCharCode( 0x100 + i * 2 );
}
i = ALPHABET.indexOf( ch.normalize( "NFD" ).charAt( 0 ) );
return i !== -1 ? String.fromCharCode( 0x101 + i * 2 ) : ch;
} ).join( "" );
}
// Up to PICKER_SIZE codes for what was typed, in the translation adder's order: the code itself, then
// names starting with it (shortest first), then codes starting with it (short-list languages first),
// then names containing it.
function match( term ) {
var all = names();
term = $.trim( term || "" ).toLowerCase();
var exact = all[ term ] ? [ term ] : [];
if ( term.length < 2 ) {
return exact;
}
var folded = fold( term );
var starts = [], contains = [], codes = [];
Object.keys( all ).forEach( function ( code ) {
if ( code === term ) {
return;
}
var i = fold( all[ code ] ).indexOf( folded );
if ( i === 0 ) {
starts.push( code );
} else if ( code.toLowerCase().indexOf( term ) === 0 ) {
codes.push( code );
} else if ( i !== -1 ) {
contains.push( code );
}
} );
function byName( a, b ) {
var ka = sortKey( all[ a ] ), kb = sortKey( all[ b ] );
return ka < kb ? -1 : ka > kb ? 1 : 0;
}
starts.sort( function ( a, b ) {
return all[ a ].length - all[ b ].length || byName( a, b );
} );
codes.sort( function ( a, b ) {
return ( !!core[ b ] - !!core[ a ] ) || a.length - b.length || ( a < b ? -1 : 1 );
} );
return exact.concat( starts, codes, contains.sort( byName ) ).slice( 0, PICKER_SIZE );
}
// One menu for every box on the page, since only the focused box shows it.
var $menu = null;
function sharedMenu() {
if ( !$menu ) {
$menu = $( "<ul class='wiktku-langmenu' role='listbox'></ul>" ).hide().appendTo( document.body );
// A scroll elsewhere would leave the menu floating away from its box.
window.addEventListener( "scroll", function ( e ) {
if ( e.target !== $menu[ 0 ] ) {
$menu.hide();
}
}, true );
}
return $menu;
}
// The translation adder's dropdown on any text box. opts:
// items( term ) [ { value, label } ] to offer for what was typed; a label shows before the value
// onPick( value ) runs after a pick writes value into the box
// onFocus also open the menu when the box gets focus, as a short list like scripts wants
// Returns { search } to refresh the menu, e.g. once more items have arrived. The menu is fixed to the
// viewport outside the form, so a panel that clips its content does not cut it off.
function suggest( input, opts ) {
input = $( input )[ 0 ];
var $menu = sharedMenu();
var items = [];
var active = -1;
input.setAttribute( "autocomplete", "off" );
function close() {
$menu.hide();
active = -1;
}
function choose( value ) {
input.value = value;
close();
if ( opts.onPick ) {
opts.onPick( value );
}
}
function render() {
$menu.empty();
items.forEach( function ( item, i ) {
var $li = $( "<li role='option'></li>" ).toggleClass( "wiktku-langmenu-active", i === active );
if ( item.label ) {
$li.append( document.createTextNode( item.label ),
$( "<span class='wiktku-langmenu-code'></span>" ).text( " " + item.value ) );
} else {
$li.text( item.value );
}
// Keeps the focus in the input, so its blur does not close the menu first.
$li.on( "mousedown", function ( e ) {
e.preventDefault();
choose( item.value );
} ).appendTo( $menu );
} );
// Inside an OOUI dialog the menu has to sit in its window manager, or the modal covers it.
var container = $( input ).closest( ".oo-ui-windowManager" )[ 0 ] || document.body;
if ( $menu[ 0 ].parentNode !== container ) {
// Opening a modal makes the rest of the body inert, the menu with it if it was there.
$menu.appendTo( container ).removeAttr( "inert aria-hidden" );
}
var rect = input.getBoundingClientRect();
$menu.css( { left: rect.left, top: rect.bottom } ).toggle( items.length > 0 );
var current = $menu.children()[ active ];
if ( current ) {
current.scrollIntoView( { block: "nearest" } );
}
}
function search() {
if ( document.activeElement !== input ) {
return;
}
items = opts.items( input.value );
active = items.length ? 0 : -1;
render();
}
$( input ).on( "input", search ).on( "keydown", function ( e ) {
if ( !$menu.is( ":visible" ) || !items.length ) {
return;
}
if ( e.key === "ArrowDown" || e.key === "ArrowUp" ) {
e.preventDefault();
active = ( active + ( e.key === "ArrowDown" ? 1 : items.length - 1 ) ) % items.length;
render();
} else if ( e.key === "Enter" && active >= 0 ) {
e.preventDefault();
choose( items[ active ].value );
} else if ( e.key === "Escape" ) {
e.stopPropagation();
close();
}
} ).on( "blur", close );
if ( opts.onFocus ) {
$( input ).on( "focus", search );
}
return { search: search };
}
// suggest() for a language-code box: names and codes are searched, and picking one writes its code.
// onChange( code ) runs after a pick; the box's title shows the language's name.
function picker( input, onChange ) {
input = $( input )[ 0 ];
function showName() {
input.title = names()[ $.trim( input.value ) ] || "";
}
var menu = suggest( input, {
items: function ( term ) {
var all = names();
return match( term ).map( function ( code ) {
return { value: code, label: all[ code ] };
} );
},
onPick: function ( code ) {
showName();
if ( onChange ) {
onChange( code );
}
}
} );
$( input ).on( "input", function () {
showName();
// Short of a full menu from the short list: fetch the rest and search again.
if ( !full && $.trim( input.value ).length > 1 && match( input.value ).length < PICKER_SIZE ) {
ensureFull().then( menu.search );
}
} );
showName();
return input;
}
module.exports = {
names: names,
isFull: isFull,
ensureFull: ensureFull,
onFull: onFull,
match: match,
suggest: suggest,
picker: picker
};
}() );
// </nowiki>
k3godyx696tsbtugyovaxh5aau9tdo2
6865547
6865540
2026-10-23T20:07:18Z
Wikihez
7344
Shared dropdown: entries say what to show and what to write, so the translation adder can use it
6865547
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/langs.js]]
*/
// ~300 common languages ship with the gadget; the remaining ~3400 are fetched from the translation
// editor's copy only when a search finds nothing in the short list.
( function () {
"use strict";
var core = require( "./ziman-core.json" );
var FULL_PAGE = "MediaWiki:Gadget-translation editor.js/ziman.json";
var full = null;
var pending = null;
var listeners = [];
// {code: name} -- the full list once it has arrived, the short one until then.
function names() {
return full || core;
}
function isFull() {
return !!full;
}
// Resolves with the full list, fetched once; falls back to the short list rather than rejecting.
function ensureFull() {
if ( full ) {
return $.Deferred().resolve( full ).promise();
}
if ( pending ) {
return pending;
}
pending = $.get( {
url: mw.util.getUrl( FULL_PAGE, { action: "raw" } ),
dataType: "text"
} ).then( function ( text ) {
try {
full = $.extend( {}, core, JSON.parse( text ) );
} catch ( e ) {
full = core;
}
pending = null;
listeners.forEach( function ( fn ) {
fn( full );
} );
return full;
}, function () {
pending = null;
return core;
} );
return pending;
}
// Called with the full list when it lands, so a picker built from the short one can rebuild itself.
function onFull( fn ) {
if ( full ) {
fn( full );
} else {
listeners.push( fn );
}
}
var PICKER_SIZE = 12;
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// A name without its diacritics, so "ital" finds "îtalî".
function fold( s ) {
return s.toLowerCase().normalize( "NFD" ).replace( /[̀-ͯ]/g, "" );
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey( s ) {
return Array.from( s.toLowerCase().replace( /['ʼ", -]/g, "" ) ).map( function ( ch ) {
var i = ALPHABET.indexOf( ch );
if ( i !== -1 ) {
return String.fromCharCode( 0x100 + i * 2 );
}
i = ALPHABET.indexOf( ch.normalize( "NFD" ).charAt( 0 ) );
return i !== -1 ? String.fromCharCode( 0x101 + i * 2 ) : ch;
} ).join( "" );
}
// Up to PICKER_SIZE codes for what was typed, in the translation adder's order: the code itself, then
// names starting with it (shortest first), then codes starting with it (short-list languages first),
// then names containing it.
function match( term ) {
var all = names();
term = $.trim( term || "" ).toLowerCase();
var exact = all[ term ] ? [ term ] : [];
if ( term.length < 2 ) {
return exact;
}
var folded = fold( term );
var starts = [], contains = [], codes = [];
Object.keys( all ).forEach( function ( code ) {
if ( code === term ) {
return;
}
var i = fold( all[ code ] ).indexOf( folded );
if ( i === 0 ) {
starts.push( code );
} else if ( code.toLowerCase().indexOf( term ) === 0 ) {
codes.push( code );
} else if ( i !== -1 ) {
contains.push( code );
}
} );
function byName( a, b ) {
var ka = sortKey( all[ a ] ), kb = sortKey( all[ b ] );
return ka < kb ? -1 : ka > kb ? 1 : 0;
}
starts.sort( function ( a, b ) {
return all[ a ].length - all[ b ].length || byName( a, b );
} );
codes.sort( function ( a, b ) {
return ( !!core[ b ] - !!core[ a ] ) || a.length - b.length || ( a < b ? -1 : 1 );
} );
return exact.concat( starts, codes, contains.sort( byName ) ).slice( 0, PICKER_SIZE );
}
// One menu for every box on the page, since only the focused box shows it.
var $menu = null;
function sharedMenu() {
if ( !$menu ) {
$menu = $( "<ul class='wiktku-langmenu' role='listbox'></ul>" ).hide().appendTo( document.body );
// A scroll elsewhere would leave the menu floating away from its box.
window.addEventListener( "scroll", function ( e ) {
if ( e.target !== $menu[ 0 ] ) {
$menu.hide();
}
}, true );
}
return $menu;
}
// The translation adder's dropdown on any text box. opts:
// items( term ) [ { value, label, code } ] to offer for what was typed: value is written into the
// box, label (or value) is shown, with code after it in grey
// onPick( value ) runs after a pick writes value into the box
// onFocus also open the menu when the box gets focus, as a short list like scripts wants
// Returns { search } to refresh the menu, e.g. once more items have arrived. The menu is fixed to the
// viewport outside the form, so a panel that clips its content does not cut it off.
function suggest( input, opts ) {
input = $( input )[ 0 ];
var $menu = sharedMenu();
var items = [];
var active = -1;
input.setAttribute( "autocomplete", "off" );
function close() {
$menu.hide();
active = -1;
}
function choose( value ) {
input.value = value;
close();
if ( opts.onPick ) {
opts.onPick( value );
}
}
function render() {
$menu.empty();
items.forEach( function ( item, i ) {
var $li = $( "<li role='option'></li>" ).toggleClass( "wiktku-langmenu-active", i === active );
$li.text( item.label || item.value );
if ( item.code ) {
$li.append( $( "<span class='wiktku-langmenu-code'></span>" ).text( " " + item.code ) );
}
// Keeps the focus in the input, so its blur does not close the menu first.
$li.on( "mousedown", function ( e ) {
e.preventDefault();
choose( item.value );
} ).appendTo( $menu );
} );
// Inside an OOUI dialog the menu has to sit in its window manager, or the modal covers it.
var container = $( input ).closest( ".oo-ui-windowManager" )[ 0 ] || document.body;
if ( $menu[ 0 ].parentNode !== container ) {
// Opening a modal makes the rest of the body inert, the menu with it if it was there.
$menu.appendTo( container ).removeAttr( "inert aria-hidden" );
}
var rect = input.getBoundingClientRect();
$menu.css( { left: rect.left, top: rect.bottom } ).toggle( items.length > 0 );
var current = $menu.children()[ active ];
if ( current ) {
current.scrollIntoView( { block: "nearest" } );
}
}
function search() {
if ( document.activeElement !== input ) {
return;
}
items = opts.items( input.value );
active = items.length ? 0 : -1;
render();
}
$( input ).on( "input", search ).on( "keydown", function ( e ) {
if ( !$menu.is( ":visible" ) || !items.length ) {
return;
}
if ( e.key === "ArrowDown" || e.key === "ArrowUp" ) {
e.preventDefault();
active = ( active + ( e.key === "ArrowDown" ? 1 : items.length - 1 ) ) % items.length;
render();
} else if ( e.key === "Enter" && active >= 0 ) {
e.preventDefault();
choose( items[ active ].value );
} else if ( e.key === "Escape" ) {
e.stopPropagation();
close();
}
} ).on( "blur", close );
if ( opts.onFocus ) {
$( input ).on( "focus", search );
}
return { search: search };
}
// suggest() for a language-code box: names and codes are searched, and picking one writes its code.
// onChange( code ) runs after a pick; the box's title shows the language's name.
function picker( input, onChange ) {
input = $( input )[ 0 ];
function showName() {
input.title = names()[ $.trim( input.value ) ] || "";
}
var menu = suggest( input, {
items: function ( term ) {
var all = names();
return match( term ).map( function ( code ) {
return { value: code, label: all[ code ], code: code };
} );
},
onPick: function ( code ) {
showName();
if ( onChange ) {
onChange( code );
}
}
} );
$( input ).on( "input", function () {
showName();
// Short of a full menu from the short list: fetch the rest and search again.
if ( !full && $.trim( input.value ).length > 1 && match( input.value ).length < PICKER_SIZE ) {
ensureFull().then( menu.search );
}
} );
showName();
return input;
}
module.exports = {
names: names,
isFull: isFull,
ensureFull: ensureFull,
onFull: onFull,
match: match,
suggest: suggest,
picker: picker
};
}() );
// </nowiki>
ngq6615l7xon0mebzyvwuns75e4anfv
6865551
6865547
2026-10-23T20:12:00Z
Wikihez
7344
Shared dropdown: open upwards near the bottom of the window and fit the room available
6865551
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/langs.js]]
*/
// ~300 common languages ship with the gadget; the remaining ~3400 are fetched from the translation
// editor's copy only when a search finds nothing in the short list.
( function () {
"use strict";
var core = require( "./ziman-core.json" );
var FULL_PAGE = "MediaWiki:Gadget-translation editor.js/ziman.json";
var full = null;
var pending = null;
var listeners = [];
// {code: name} -- the full list once it has arrived, the short one until then.
function names() {
return full || core;
}
function isFull() {
return !!full;
}
// Resolves with the full list, fetched once; falls back to the short list rather than rejecting.
function ensureFull() {
if ( full ) {
return $.Deferred().resolve( full ).promise();
}
if ( pending ) {
return pending;
}
pending = $.get( {
url: mw.util.getUrl( FULL_PAGE, { action: "raw" } ),
dataType: "text"
} ).then( function ( text ) {
try {
full = $.extend( {}, core, JSON.parse( text ) );
} catch ( e ) {
full = core;
}
pending = null;
listeners.forEach( function ( fn ) {
fn( full );
} );
return full;
}, function () {
pending = null;
return core;
} );
return pending;
}
// Called with the full list when it lands, so a picker built from the short one can rebuild itself.
function onFull( fn ) {
if ( full ) {
fn( full );
} else {
listeners.push( fn );
}
}
var PICKER_SIZE = 12;
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// A name without its diacritics, so "ital" finds "îtalî".
function fold( s ) {
return s.toLowerCase().normalize( "NFD" ).replace( /[̀-ͯ]/g, "" );
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey( s ) {
return Array.from( s.toLowerCase().replace( /['ʼ", -]/g, "" ) ).map( function ( ch ) {
var i = ALPHABET.indexOf( ch );
if ( i !== -1 ) {
return String.fromCharCode( 0x100 + i * 2 );
}
i = ALPHABET.indexOf( ch.normalize( "NFD" ).charAt( 0 ) );
return i !== -1 ? String.fromCharCode( 0x101 + i * 2 ) : ch;
} ).join( "" );
}
// Up to PICKER_SIZE codes for what was typed, in the translation adder's order: the code itself, then
// names starting with it (shortest first), then codes starting with it (short-list languages first),
// then names containing it.
function match( term ) {
var all = names();
term = $.trim( term || "" ).toLowerCase();
var exact = all[ term ] ? [ term ] : [];
if ( term.length < 2 ) {
return exact;
}
var folded = fold( term );
var starts = [], contains = [], codes = [];
Object.keys( all ).forEach( function ( code ) {
if ( code === term ) {
return;
}
var i = fold( all[ code ] ).indexOf( folded );
if ( i === 0 ) {
starts.push( code );
} else if ( code.toLowerCase().indexOf( term ) === 0 ) {
codes.push( code );
} else if ( i !== -1 ) {
contains.push( code );
}
} );
function byName( a, b ) {
var ka = sortKey( all[ a ] ), kb = sortKey( all[ b ] );
return ka < kb ? -1 : ka > kb ? 1 : 0;
}
starts.sort( function ( a, b ) {
return all[ a ].length - all[ b ].length || byName( a, b );
} );
codes.sort( function ( a, b ) {
return ( !!core[ b ] - !!core[ a ] ) || a.length - b.length || ( a < b ? -1 : 1 );
} );
return exact.concat( starts, codes, contains.sort( byName ) ).slice( 0, PICKER_SIZE );
}
// One menu for every box on the page, since only the focused box shows it.
var $menu = null;
function sharedMenu() {
if ( !$menu ) {
$menu = $( "<ul class='wiktku-langmenu' role='listbox'></ul>" ).hide().appendTo( document.body );
// A scroll elsewhere would leave the menu floating away from its box.
window.addEventListener( "scroll", function ( e ) {
if ( e.target !== $menu[ 0 ] ) {
$menu.hide();
}
}, true );
}
return $menu;
}
// The translation adder's dropdown on any text box. opts:
// items( term ) [ { value, label, code } ] to offer for what was typed: value is written into the
// box, label (or value) is shown, with code after it in grey
// onPick( value ) runs after a pick writes value into the box
// onFocus also open the menu when the box gets focus, as a short list like scripts wants
// Returns { search } to refresh the menu, e.g. once more items have arrived. The menu is fixed to the
// viewport outside the form, so a panel that clips its content does not cut it off.
function suggest( input, opts ) {
input = $( input )[ 0 ];
var $menu = sharedMenu();
var items = [];
var active = -1;
input.setAttribute( "autocomplete", "off" );
function close() {
$menu.hide();
active = -1;
}
function choose( value ) {
input.value = value;
close();
if ( opts.onPick ) {
opts.onPick( value );
}
}
function render() {
$menu.empty();
items.forEach( function ( item, i ) {
var $li = $( "<li role='option'></li>" ).toggleClass( "wiktku-langmenu-active", i === active );
$li.text( item.label || item.value );
if ( item.code ) {
$li.append( $( "<span class='wiktku-langmenu-code'></span>" ).text( " " + item.code ) );
}
// Keeps the focus in the input, so its blur does not close the menu first.
$li.on( "mousedown", function ( e ) {
e.preventDefault();
choose( item.value );
} ).appendTo( $menu );
} );
// Inside an OOUI dialog the menu has to sit in its window manager, or the modal covers it.
var container = $( input ).closest( ".oo-ui-windowManager" )[ 0 ] || document.body;
if ( $menu[ 0 ].parentNode !== container ) {
// Opening a modal makes the rest of the body inert, the menu with it if it was there.
$menu.appendTo( container ).removeAttr( "inert aria-hidden" );
}
var rect = input.getBoundingClientRect();
var below = window.innerHeight - rect.bottom - 4;
var above = rect.top - 4;
$menu.css( { left: rect.left, top: rect.bottom, maxHeight: "" } ).toggle( items.length > 0 );
var height = $menu.outerHeight();
// Fixed to the window, the menu cannot be scrolled to: near the bottom it opens upwards
// if there is more room there, and either way it is cut to the room it has.
if ( height > below && above > below ) {
height = Math.min( height, above );
$menu.css( { top: rect.top - height, maxHeight: height } );
} else if ( height > below ) {
$menu.css( { maxHeight: below } );
}
var current = $menu.children()[ active ];
if ( current ) {
current.scrollIntoView( { block: "nearest" } );
}
}
function search() {
if ( document.activeElement !== input ) {
return;
}
items = opts.items( input.value );
active = items.length ? 0 : -1;
render();
}
$( input ).on( "input", search ).on( "keydown", function ( e ) {
if ( !$menu.is( ":visible" ) || !items.length ) {
return;
}
if ( e.key === "ArrowDown" || e.key === "ArrowUp" ) {
e.preventDefault();
active = ( active + ( e.key === "ArrowDown" ? 1 : items.length - 1 ) ) % items.length;
render();
} else if ( e.key === "Enter" && active >= 0 ) {
e.preventDefault();
choose( items[ active ].value );
} else if ( e.key === "Escape" ) {
e.stopPropagation();
close();
}
} ).on( "blur", close );
if ( opts.onFocus ) {
$( input ).on( "focus", search );
}
return { search: search };
}
// suggest() for a language-code box: names and codes are searched, and picking one writes its code.
// onChange( code ) runs after a pick; the box's title shows the language's name.
function picker( input, onChange ) {
input = $( input )[ 0 ];
function showName() {
input.title = names()[ $.trim( input.value ) ] || "";
}
var menu = suggest( input, {
items: function ( term ) {
var all = names();
return match( term ).map( function ( code ) {
return { value: code, label: all[ code ], code: code };
} );
},
onPick: function ( code ) {
showName();
if ( onChange ) {
onChange( code );
}
}
} );
$( input ).on( "input", function () {
showName();
// Short of a full menu from the short list: fetch the rest and search again.
if ( !full && $.trim( input.value ).length > 1 && match( input.value ).length < PICKER_SIZE ) {
ensureFull().then( menu.search );
}
} );
showName();
return input;
}
module.exports = {
names: names,
isFull: isFull,
ensureFull: ensureFull,
onFull: onFull,
match: match,
suggest: suggest,
picker: picker
};
}() );
// </nowiki>
bcfdeajj8g9ihry2bbik8zf9kyg1gtm
6865556
6865551
2026-10-23T20:17:00Z
Wikihez
7344
Use the dropdown from wikt.suggest
6865556
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/langs.js]]
*/
// ~300 common languages ship with the gadget; the remaining ~3400 are fetched from the translation
// editor's copy only when a search finds nothing in the short list.
( function () {
"use strict";
var core = require( "./ziman-core.json" );
var suggest = require( "ext.gadget.wikt.suggest" );
var FULL_PAGE = "MediaWiki:Gadget-translation editor.js/ziman.json";
var full = null;
var pending = null;
var listeners = [];
// {code: name} -- the full list once it has arrived, the short one until then.
function names() {
return full || core;
}
function isFull() {
return !!full;
}
// Resolves with the full list, fetched once; falls back to the short list rather than rejecting.
function ensureFull() {
if ( full ) {
return $.Deferred().resolve( full ).promise();
}
if ( pending ) {
return pending;
}
pending = $.get( {
url: mw.util.getUrl( FULL_PAGE, { action: "raw" } ),
dataType: "text"
} ).then( function ( text ) {
try {
full = $.extend( {}, core, JSON.parse( text ) );
} catch ( e ) {
full = core;
}
pending = null;
listeners.forEach( function ( fn ) {
fn( full );
} );
return full;
}, function () {
pending = null;
return core;
} );
return pending;
}
// Called with the full list when it lands, so a picker built from the short one can rebuild itself.
function onFull( fn ) {
if ( full ) {
fn( full );
} else {
listeners.push( fn );
}
}
var PICKER_SIZE = 12;
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// A name without its diacritics, so "ital" finds "îtalî".
function fold( s ) {
return s.toLowerCase().normalize( "NFD" ).replace( /[̀-ͯ]/g, "" );
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey( s ) {
return Array.from( s.toLowerCase().replace( /['ʼ", -]/g, "" ) ).map( function ( ch ) {
var i = ALPHABET.indexOf( ch );
if ( i !== -1 ) {
return String.fromCharCode( 0x100 + i * 2 );
}
i = ALPHABET.indexOf( ch.normalize( "NFD" ).charAt( 0 ) );
return i !== -1 ? String.fromCharCode( 0x101 + i * 2 ) : ch;
} ).join( "" );
}
// Up to PICKER_SIZE codes for what was typed, in the translation adder's order: the code itself, then
// names starting with it (shortest first), then codes starting with it (short-list languages first),
// then names containing it.
function match( term ) {
var all = names();
term = $.trim( term || "" ).toLowerCase();
var exact = all[ term ] ? [ term ] : [];
if ( term.length < 2 ) {
return exact;
}
var folded = fold( term );
var starts = [], contains = [], codes = [];
Object.keys( all ).forEach( function ( code ) {
if ( code === term ) {
return;
}
var i = fold( all[ code ] ).indexOf( folded );
if ( i === 0 ) {
starts.push( code );
} else if ( code.toLowerCase().indexOf( term ) === 0 ) {
codes.push( code );
} else if ( i !== -1 ) {
contains.push( code );
}
} );
function byName( a, b ) {
var ka = sortKey( all[ a ] ), kb = sortKey( all[ b ] );
return ka < kb ? -1 : ka > kb ? 1 : 0;
}
starts.sort( function ( a, b ) {
return all[ a ].length - all[ b ].length || byName( a, b );
} );
codes.sort( function ( a, b ) {
return ( !!core[ b ] - !!core[ a ] ) || a.length - b.length || ( a < b ? -1 : 1 );
} );
return exact.concat( starts, codes, contains.sort( byName ) ).slice( 0, PICKER_SIZE );
}
// suggest() for a language-code box: names and codes are searched, and picking one writes its code.
// onChange( code ) runs after a pick; the box's title shows the language's name.
function picker( input, onChange ) {
input = $( input )[ 0 ];
function showName() {
input.title = names()[ $.trim( input.value ) ] || "";
}
var menu = suggest( input, {
items: function ( term ) {
var all = names();
return match( term ).map( function ( code ) {
return { value: code, label: all[ code ], code: code };
} );
},
onPick: function ( code ) {
showName();
if ( onChange ) {
onChange( code );
}
}
} );
$( input ).on( "input", function () {
showName();
// Short of a full menu from the short list: fetch the rest and search again.
if ( !full && $.trim( input.value ).length > 1 && match( input.value ).length < PICKER_SIZE ) {
ensureFull().then( menu.search );
}
} );
showName();
return input;
}
module.exports = {
names: names,
isFull: isFull,
ensureFull: ensureFull,
onFull: onFull,
match: match,
suggest: suggest,
picker: picker
};
}() );
// </nowiki>
49tnbmu5qgcyl58ogtx71ktosxr1w3u
6865574
6865556
2026-10-23T20:36:13Z
Wikihez
7344
Language list from wikt.suggest (Modul:ziman/code to canonical name.json, same as the translation adder); darna behind Parametreyên din with labelled values
6865574
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/langs.js]]
*/
// ~300 common languages ship with the gadget; the rest come from wikt.suggest's list (Modul:ziman's export,
// the one the translation adder uses) only when a search finds too little in the short list.
( function () {
"use strict";
var core = require( "./ziman-core.json" );
var suggest = require( "ext.gadget.wikt.suggest" );
var full = null;
var pending = null;
var listeners = [];
// {code: name} -- the full list once it has arrived, the short one until then.
function names() {
return full || core;
}
function isFull() {
return !!full;
}
// Resolves with the full list, fetched once; falls back to the short list rather than rejecting.
function ensureFull() {
if ( full ) {
return $.Deferred().resolve( full ).promise();
}
if ( pending ) {
return pending;
}
pending = suggest.languages().then( function ( data ) {
full = $.extend( {}, core, data );
pending = null;
listeners.forEach( function ( fn ) {
fn( full );
} );
return full;
}, function () {
pending = null;
return core;
} );
return pending;
}
// Called with the full list when it lands, so a picker built from the short one can rebuild itself.
function onFull( fn ) {
if ( full ) {
fn( full );
} else {
listeners.push( fn );
}
}
var PICKER_SIZE = 12;
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// A name without its diacritics, so "ital" finds "îtalî".
function fold( s ) {
return s.toLowerCase().normalize( "NFD" ).replace( /[̀-ͯ]/g, "" );
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey( s ) {
return Array.from( s.toLowerCase().replace( /['ʼ", -]/g, "" ) ).map( function ( ch ) {
var i = ALPHABET.indexOf( ch );
if ( i !== -1 ) {
return String.fromCharCode( 0x100 + i * 2 );
}
i = ALPHABET.indexOf( ch.normalize( "NFD" ).charAt( 0 ) );
return i !== -1 ? String.fromCharCode( 0x101 + i * 2 ) : ch;
} ).join( "" );
}
// Up to PICKER_SIZE codes for what was typed, in the translation adder's order: the code itself, then
// names starting with it (shortest first), then codes starting with it (short-list languages first),
// then names containing it.
function match( term ) {
var all = names();
term = $.trim( term || "" ).toLowerCase();
var exact = all[ term ] ? [ term ] : [];
if ( term.length < 2 ) {
return exact;
}
var folded = fold( term );
var starts = [], contains = [], codes = [];
Object.keys( all ).forEach( function ( code ) {
if ( code === term ) {
return;
}
var i = fold( all[ code ] ).indexOf( folded );
if ( i === 0 ) {
starts.push( code );
} else if ( code.toLowerCase().indexOf( term ) === 0 ) {
codes.push( code );
} else if ( i !== -1 ) {
contains.push( code );
}
} );
function byName( a, b ) {
var ka = sortKey( all[ a ] ), kb = sortKey( all[ b ] );
return ka < kb ? -1 : ka > kb ? 1 : 0;
}
starts.sort( function ( a, b ) {
return all[ a ].length - all[ b ].length || byName( a, b );
} );
codes.sort( function ( a, b ) {
return ( !!core[ b ] - !!core[ a ] ) || a.length - b.length || ( a < b ? -1 : 1 );
} );
return exact.concat( starts, codes, contains.sort( byName ) ).slice( 0, PICKER_SIZE );
}
// suggest() for a language-code box: names and codes are searched, and picking one writes its code.
// onChange( code ) runs after a pick; the box's title shows the language's name.
function picker( input, onChange ) {
input = $( input )[ 0 ];
function showName() {
input.title = names()[ $.trim( input.value ) ] || "";
}
var menu = suggest( input, {
items: function ( term ) {
var all = names();
return match( term ).map( function ( code ) {
return { value: code, label: all[ code ], code: code };
} );
},
onPick: function ( code ) {
showName();
if ( onChange ) {
onChange( code );
}
}
} );
$( input ).on( "input", function () {
showName();
// Short of a full menu from the short list: fetch the rest and search again.
if ( !full && $.trim( input.value ).length > 1 && match( input.value ).length < PICKER_SIZE ) {
ensureFull().then( menu.search );
}
} );
showName();
return input;
}
module.exports = {
names: names,
isFull: isFull,
ensureFull: ensureFull,
onFull: onFull,
match: match,
suggest: suggest,
picker: picker
};
}() );
// </nowiki>
b1auaxur25kqfkksw1xhi60vrfd2vjb
MediaWiki:Gadget-wikt.edit/posparams.js
8
1359162
6865541
6865398
2026-10-23T19:48:06Z
Wikihez
7344
Part-of-speech card: script codes (sc) in the same dropdown as the language fields
6865541
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/posparams.js]]
*
* The part-of-speech parameter card: the live "{{navdêr|ku|…}}" line, the gender and transitivity
* questions, the transliteration boxes, and everything behind "Tercîhên pêşketî".
*
* Lived in the entry wizard, where it was one of its steps. DefSideBoxes needs the same card when it
* reclassifies a section, so it moved here rather than being written a second time -- two copies of a
* card with forty-odd fields and per-language gating would not have stayed in step.
*
* var card = posparams.build({
* lang: "ku", pos: "navdêr",
* onChange: function () { ... }, // a field changed
* onSync: function (c, j) { ... } // gender/number, for callers that mirror them
* });
* card.$element the card, to insert where you like
* card.setContext("ku", "lêker") re-gate every row for a new language / part of speech
* card.wikitext() / card.setWikitext(text) the template line the card describes
*
* Ids are per instance (see idPrefix): the checkboxes behind "Tercîhên pêşketî" pair a <label for> with
* an <input id>, and DefSideBoxes draws one card per section, so a fixed id would make a label toggle
* some other section's checkbox. Rows are found by data-pp-row for the same reason.
*/
(function () {
"use strict";
var data = require("./data-main.json");
var sections = require("ext.gadget.wikt.core").sections;
var wt = require("./wikitext.js");
var langs = require("./langs.js");
// Filled from data-main.json, exactly as the wizard filled them from the same file.
var metadata = data.metadata || {};
var NUMBER_LABELS = data.numberLabels || {};
var GENDER_BASE_LABELS = data.genderLabels || {};
var LEKTUR_CHOICES = data.lekturChoices || [];
var LEKTUR_FORM_CHOICES = data.lekturFormChoices || [];
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form
// and neqiyasbar both write to the shared |form=| param.
var OPT_PARAM_OVERRIDES = {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
};
// Modul:rêziman's alias_of names, read as the card's own.
var PARAM_ALIASES = {
sccat: "sckat",
katnacure: "nekatcure", noposcat: "nekatcure",
katnaçendterm: "nekatçendterm", nomultiwordcat: "nekatçendterm",
katnacins: "nekatcins", nogendercat: "nekatcins",
katnapalîndrom: "nekatpalîndrom", nopalindromecat: "nekatpalîndrom",
nolinkhead: "serlînkneke"
};
// Text boxes in the order the line writes them; "cuda" is the positional 2.
var TEXT_KEYS = ["tr", "ts", "sc", "m", "mtr", "n", "ntr", "p", "ptr", "y", "ytr", "sûp", "sûptr",
"komp", "komptr", "gir", "girtr", "hûr", "hûrtr", "hanja", "hanjatr", "niha", "nihatr",
"borî", "borîtr", "subj", "subjtr", "fniha", "fnihatr", "fborî", "fborîtr", "fdbê", "fdbêtr",
"fsubj", "fsubjtr", "id", "sort"];
// Values Module:parameters reads as false for a boolean.
var FALSE_VALUES = ["0", "no", "n", "false", "na", "ne", "nexêr"];
function canonicalKey(key) {
var k = $.trim(key);
return PARAM_ALIASES[k] || wt.canonicalParamName(k);
}
function hasCode(choices, code) {
return choices.some(function (c) { return c.code === code; });
}
// "gh" or "<form>-<base>"; null when the value is not one the two dropdowns can hold.
function splitLektur(value) {
var m = /^(?:([^-]+)-)?([^-]+)$/.exec(value || "");
if (!m || !hasCode(LEKTUR_CHOICES, m[2]) || (m[1] && !hasCode(LEKTUR_FORM_CHOICES, m[1]))) return null;
return { form: m[1] || "", base: m[2] };
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// numberLabels entries are objects ({display: "yekjimar"}), not strings -- "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// genderLabels entries are plain strings. A "<gender>-p" code is that gender in the plural.
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
var seq = 0;
function build(opts) {
opts = opts || {};
var context = { lang: opts.lang || "", pos: opts.pos || "" };
var idPrefix = "wiktku-pp-" + (++seq);
var mirror = { c: "", j: "" };
function fireChange() {
if (typeof opts.onChange === "function") opts.onChange();
}
// Gender/number are reported rather than written: the wizard mirrors them into hidden steps of
// its own, DefSideBoxes has nowhere to put them. Nulls mean "unchanged".
function syncMirror(c, j) {
if (c !== null && c !== undefined) mirror.c = c;
if (j !== null && j !== undefined) mirror.j = j;
if (typeof opts.onSync === "function") opts.onSync(mirror.c, mirror.j);
}
function mirrorValue(which) {
return mirror[which] || "";
}
var container = $("<div class='wiktku-posparams-step'></div>");
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
rebuildTa();
_rebuilding = false;
fireChange();
}
// onChange walks every sense block and nimûne item in the wizard, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("data-pp-row", key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' data-pp-row='c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Reported through syncMirror() rather than held here: the wizard mirrors gender into its own
// hidden step field, and DefSideBoxes ignores it.
cinsiyetSel.on("change", function () {
syncMirror($(this).val(), null);
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// reported through syncMirror() like gender, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' data-pp-row='jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
syncMirror(null, $(this).val());
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' data-pp-row='lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"));
$.each(LEKTUR_CHOICES, function (_, c) {
lekturSel.append($("<option></option>").val(c.code).text(c.label));
});
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' data-pp-row='lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"));
$.each(LEKTUR_FORM_CHOICES, function (_, c) {
lekturFormSel.append($("<option></option>").val(c.code).text(c.label));
});
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî are the conjugated forms; niha/borî/subj below are the roots the
// Lua module conjugates from. fniha/fborî show for every lêker language, fdbê only for ku,
// fsubj only outside ku — see syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' data-pp-heading='tewandin'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint, Kurmancî only: split ergativity makes a gerguhêz verb's past subject
// oblique ("min"), so fborî's hint follows the transitivity answer while fniha stays "ez".
// fdbê uses "ew ê". Hint and box share a flex line so the hint stays before the box.
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "fniha", "ez");
addPronounHint(fborîRow, "fborî", "");
addPronounHint(fdbêRow, "fdbê", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
// The script code, with the language's own scripts offered.
var scRow = makeTextRow("sc", "Koda skrîptê (sc)", "Latn, Arab…");
var scripts = [];
langs.suggest(scRow.find(".wiktku-posparams-field"), {
onFocus: true,
items: function (term) {
term = $.trim(term).toLowerCase();
return scripts.filter(function (code) {
return code.toLowerCase().indexOf(term) === 0;
}).map(function (code) {
return { value: code };
});
},
onPick: debouncedRebuild
});
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "xuyakirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(scRow)
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdek (mezin)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (biçûk)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjatr","Translit. hanja", "hanjatr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"))
.append(makeTextRow("id", "Id (id)", "id=…"))
.append(makeTextRow("sort", "Rêzkirin (sort)", "sort=…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — cureyê rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" },
{ key: "sckat", label: "sckat — kategoriya skrîptê lê zêde bike" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", idPrefix + "-opt-" + p.key).attr("data-pp-opt", p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", idPrefix + "-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("[data-pp-opt='form']");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", idPrefix + "-opt-neqiyasbar").attr("data-pp-opt", "neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", idPrefix + "-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
// Straight after "form" rather than at the foot of the list: the two share the |form=| param and
// exclude each other, so they read as one either/or pair when they sit together.
var $neqiyasbarRow = $("<div data-pp-row='neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl);
var $formRow = formCb.closest(".wiktku-posparams-opt-row");
if ($formRow.length) {
$formRow.after($neqiyasbarRow);
} else {
optPanel.append($neqiyasbarRow);
}
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Read with the shared parser, so a nested "c=" is never the call's own and "z=" answers for gender.
var callParam = wt.callParamValue;
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT fire onChange — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
readLine(text);
syncMirror(callParam(text, "c"), callParam(text, "j"));
});
// Name each parameter was written under on the page, keyed by the card's own name.
var spelling = {};
// Value of each ticked boolean as the page had it ("erê", "1", …).
var boolValue = {};
// Parameters the card has no field for, written back as they were: [{key, canon, value}].
var extras = [];
function selectHolds(sel, value) {
return sel.find("option").filter(function () { return this.value === value; }).length > 0;
}
// Fills every field from a "{{pos|lang|…}}" line; false when the text is not one closed call.
function readLine(text) {
var call = wt.parseTemplateCalls($.trim(text || "")).calls[0];
if (!call) return false;
var split = wt.splitCallParams(call.params);
spelling = {};
boolValue = {};
extras = [];
container.find(".wiktku-posparams-field").val("");
container.find("[data-pp-opt]").prop("checked", false);
cinsiyetSel.val("");
jimarSel.val("");
lekturSel.val("");
lekturFormSel.val("");
var isNoun = context.pos === "navdêr" || context.pos === "serenav";
split.used.forEach(function (key) {
var value = split.values[key];
var canon = key === "2" ? "cuda" : canonicalKey(key);
var taken = Object.prototype.hasOwnProperty.call(spelling, canon);
var placed = false;
if (key === "1") {
return;
}
// The "cuda" box is the positional 2; a named cuda= is a parameter of its own.
var $field = key === "cuda" ? $() : container.find(".wiktku-posparams-field[data-pp-key='" + canon + "']");
var $cb = container.find("[data-pp-opt='" + canon + "']");
if (taken) {
placed = false;
} else if ($field.length) {
$field.val(value);
placed = true;
} else if (canon === "form") {
if (value === "erê" || value === "-") {
container.find("[data-pp-opt='" + (value === "-" ? "neqiyasbar" : "form") + "']").prop("checked", true);
placed = true;
}
} else if ($cb.length && canon !== "neqiyasbar") {
if (value && FALSE_VALUES.indexOf(value.toLowerCase()) === -1) {
$cb.prop("checked", true);
boolValue[canon] = value;
placed = true;
}
} else if (canon === "c" && isNoun) {
if (selectHolds(cinsiyetSel, value)) {
cinsiyetSel.val(value);
placed = true;
}
} else if (canon === "c" && /^lêker/.test(context.pos)) {
var lektur = splitLektur(value);
if (lektur) {
lekturSel.val(lektur.base);
lekturFormSel.val(lektur.form);
placed = true;
}
} else if (canon === "j" && isNoun) {
if (selectHolds(jimarSel, value)) {
jimarSel.val(value);
placed = true;
}
}
if (placed) {
spelling[canon] = key;
} else {
extras.push({ key: key, canon: canon, value: value });
}
});
revealFilled();
return true;
}
// A value read from the page shows even where the row would not be offered, and opens the panel it sits in.
function revealFilled() {
container.find(".wiktku-posparams-field").each(function () {
if ($.trim(this.value)) $(this).closest("[data-pp-row]").show();
});
var filled = advPanel.find(".wiktku-posparams-field").filter(function () { return $.trim(this.value); }).length ||
advPanel.find("[data-pp-opt]:checked").length || jimarSel.val();
if (filled && !advPanel.is(":visible")) {
advPanel.show();
optToggle.attr("aria-expanded", "true").addClass("wiktku-adv-open");
}
}
container.append(wrap);
function rebuildTa() {
var pos = context.pos || "";
var lang = context.lang || "";
if (!pos || !lang) return;
var cinsiyet = container.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = container.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = container.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = container.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(container.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
var emitted = {};
function emit(key, value) {
out += "|" + (spelling[key] || key) + "=" + value;
emitted[key] = true;
}
function hasExtra(key) {
return extras.some(function (e) { return e.canon === key; });
}
// "|form=erê" marks an inflected form, which takes its gender from the lemma -- so the
// "|c=?" placeholder (a request to fill the gender in) has nothing to ask for there.
var isFormEntry = container.find("[data-pp-opt='form']").is(":checked");
// The alternative head is the positional 2, written right after the language.
if (v("cuda")) out += "|" + (/=/.test(v("cuda")) ? "2=" : "") + v("cuda");
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
emit("c", cinsiyet);
} else if (lang === "ku" && !isFormEntry && !hasExtra("c")) {
emit("c", "?");
}
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) emit("j", jimar);
} else if (/^lêker/.test(pos) && lektur) {
emit("c", lektur);
}
$.each(TEXT_KEYS, function (_, k) {
if (v(k)) emit(k, v(k));
});
container.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = OPT_PARAM_OVERRIDES[key];
if (override) {
emit(override.param, override.value);
} else {
emit(key, boolValue[key] || "1");
}
});
$.each(extras, function (_, e) {
if (!emitted[e.canon]) out += "|" + e.key + "=" + e.value;
});
out += "}}";
container.find(".wiktku-posparams-text").val(out);
syncMirror(cinsiyet, jimar);
}
function syncHelpers() {
var pos = context.pos || "";
var lang = context.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data-main.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = container.find("[data-pp-row=\'c\']");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || mirrorValue("c");
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = container.find("[data-pp-row=\'jimar\']");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || mirrorValue("j");
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
container.find("[data-pp-row=\'lektur\']").toggle(isLeker);
container.find("[data-pp-row=\'lekturform\']").toggle(isLeker);
container.find("[data-pp-row=\'tr\']").toggle(needsTr);
container.find("[data-pp-row=\'ts\']").toggle(needsTr);
container.find("[data-pp-row=\'sc\'], [data-pp-row=\'id\'], [data-pp-row=\'sort\']").show();
scripts = [].concat((meta && meta.sc) || []);
container.find("[data-pp-row=\'m\']").toggle(isNom);
container.find("[data-pp-row=\'mtr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'n\']").toggle(isNom);
container.find("[data-pp-row=\'ntr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'p\']").toggle(isNom);
container.find("[data-pp-row=\'ptr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'y\']").toggle(isNom);
container.find("[data-pp-row=\'ytr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'sûp\']").toggle(isAdj);
container.find("[data-pp-row=\'sûptr\']").toggle(isAdj && needsTr);
container.find("[data-pp-row=\'komp\']").toggle(isAdj);
container.find("[data-pp-row=\'komptr\']").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
container.find("[data-pp-row=\'neqiyasbar\']").toggle(isCompar);
if (isCompar) {
container.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
container.find("[data-pp-opt='neqiyasbar']").prop("checked", false);
}
// No "Formeke Xê" heading exists for these, so the box that makes one is not offered.
var formOk = !pos || sections.hasFormHeading(pos);
container.find("[data-pp-opt='form']").closest(".wiktku-posparams-opt-row").toggle(formOk);
if (!formOk) {
container.find("[data-pp-opt='form']").prop("checked", false);
}
container.find("[data-pp-row=\'gir\']").toggle(isTip);
container.find("[data-pp-row=\'girtr\']").toggle(isTip && needsTr);
container.find("[data-pp-row=\'hûr\']").toggle(isTip);
container.find("[data-pp-row=\'hûrtr\']").toggle(isTip && needsTr);
container.find("[data-pp-row=\'hanja\']").toggle(isJa);
container.find("[data-pp-row=\'hanjatr\']").toggle(isJa);
container.find("[data-pp-row=\'niha\']").toggle(isLeker);
container.find("[data-pp-row=\'nihatr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'borî\']").toggle(isLeker);
container.find("[data-pp-row=\'borîtr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'subj\']").toggle(isLekerSubj);
container.find("[data-pp-row=\'subjtr\']").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
container.find("[data-pp-heading='tewandin']").toggle(isLeker);
container.find("[data-pp-row=\'fniha\']").toggle(isLeker);
container.find("[data-pp-row=\'fnihatr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'fborî\']").toggle(isLeker);
container.find("[data-pp-row=\'fborîtr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'fdbê\']").toggle(isKuLeker);
container.find("[data-pp-row=\'fdbêtr\']").toggle(isKuLeker && needsTr);
container.find("[data-pp-row=\'fsubj\']").toggle(isLekerSubj);
container.find("[data-pp-row=\'fsubjtr\']").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
container.find("[data-pp-hint=\'fniha\']").toggle(isKuLeker);
var lekturVal = container.find(".wiktku-posparams-lektur-sel").val();
container.find("[data-pp-hint=\'fborî\']")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
container.find("[data-pp-hint=\'fdbê\']").toggle(isKuLeker);
revealFilled();
if (!$.trim(container.find(".wiktku-posparams-text").val())) {
rebuildTa();
}
}
syncHelpers();
return {
$element: container,
// isForm, when given, ticks "form" for a "Formeke Xê" heading; leaving it out keeps the checkbox.
setContext: function (lang, pos, isForm) {
var formCb = container.find("[data-pp-opt='form']");
var formMoved = isForm !== undefined && !!isForm !== formCb.is(":checked");
if (formMoved) {
formCb.prop("checked", !!isForm);
if (isForm) {
container.find("[data-pp-opt='neqiyasbar']").prop("checked", false);
}
}
var moved = formMoved || (lang || "") !== context.lang || (pos || "") !== context.pos;
context.lang = lang || "";
context.pos = pos || "";
syncHelpers();
// syncHelpers() only fills the line when the box is empty, so a hand-edited one survives
// every other change. A different part of speech or language is the exception: the
// template name and its language parameter both come from them, so whatever is in the
// box describes something that is no longer being edited.
if (moved) {
rebuildTa();
}
},
wikitext: function () {
return $.trim(container.find(".wiktku-posparams-text").val() || "");
},
setWikitext: function (text) {
container.find(".wiktku-posparams-text").val(text || "");
// Opened on a call the page already has: every field shows its value, read under any
// alias, so a save rewrites those values rather than adding a second one beside them.
readLine(text);
syncMirror(callParam(text, "c"), callParam(text, "j"));
},
rebuild: rebuildTa
};
}
module.exports = {
build: build,
needsTransliteration: needsTransliteration,
OPT_PARAM_OVERRIDES: OPT_PARAM_OVERRIDES
};
}());
// </nowiki>
74dc936cyhqwmbnqdkjij2pymweafal
6865625
6865541
2026-10-23T22:15:37Z
Wikihez
7344
Data tables from wikt.edit-data
6865625
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/posparams.js]]
*
* The part-of-speech parameter card: the live "{{navdêr|ku|…}}" line, the gender and transitivity
* questions, the transliteration boxes, and everything behind "Tercîhên pêşketî".
*
* Lived in the entry wizard, where it was one of its steps. DefSideBoxes needs the same card when it
* reclassifies a section, so it moved here rather than being written a second time -- two copies of a
* card with forty-odd fields and per-language gating would not have stayed in step.
*
* var card = posparams.build({
* lang: "ku", pos: "navdêr",
* onChange: function () { ... }, // a field changed
* onSync: function (c, j) { ... } // gender/number, for callers that mirror them
* });
* card.$element the card, to insert where you like
* card.setContext("ku", "lêker") re-gate every row for a new language / part of speech
* card.wikitext() / card.setWikitext(text) the template line the card describes
*
* Ids are per instance (see idPrefix): the checkboxes behind "Tercîhên pêşketî" pair a <label for> with
* an <input id>, and DefSideBoxes draws one card per section, so a fixed id would make a label toggle
* some other section's checkbox. Rows are found by data-pp-row for the same reason.
*/
(function () {
"use strict";
var data = require("ext.gadget.wikt.edit-data");
var sections = require("ext.gadget.wikt.core").sections;
var wt = require("./wikitext.js");
var langs = require("./langs.js");
// Filled from data-main.json, exactly as the wizard filled them from the same file.
var metadata = data.metadata || {};
var NUMBER_LABELS = data.numberLabels || {};
var GENDER_BASE_LABELS = data.genderLabels || {};
var LEKTUR_CHOICES = data.lekturChoices || [];
var LEKTUR_FORM_CHOICES = data.lekturFormChoices || [];
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form
// and neqiyasbar both write to the shared |form=| param.
var OPT_PARAM_OVERRIDES = {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
};
// Modul:rêziman's alias_of names, read as the card's own.
var PARAM_ALIASES = {
sccat: "sckat",
katnacure: "nekatcure", noposcat: "nekatcure",
katnaçendterm: "nekatçendterm", nomultiwordcat: "nekatçendterm",
katnacins: "nekatcins", nogendercat: "nekatcins",
katnapalîndrom: "nekatpalîndrom", nopalindromecat: "nekatpalîndrom",
nolinkhead: "serlînkneke"
};
// Text boxes in the order the line writes them; "cuda" is the positional 2.
var TEXT_KEYS = ["tr", "ts", "sc", "m", "mtr", "n", "ntr", "p", "ptr", "y", "ytr", "sûp", "sûptr",
"komp", "komptr", "gir", "girtr", "hûr", "hûrtr", "hanja", "hanjatr", "niha", "nihatr",
"borî", "borîtr", "subj", "subjtr", "fniha", "fnihatr", "fborî", "fborîtr", "fdbê", "fdbêtr",
"fsubj", "fsubjtr", "id", "sort"];
// Values Module:parameters reads as false for a boolean.
var FALSE_VALUES = ["0", "no", "n", "false", "na", "ne", "nexêr"];
function canonicalKey(key) {
var k = $.trim(key);
return PARAM_ALIASES[k] || wt.canonicalParamName(k);
}
function hasCode(choices, code) {
return choices.some(function (c) { return c.code === code; });
}
// "gh" or "<form>-<base>"; null when the value is not one the two dropdowns can hold.
function splitLektur(value) {
var m = /^(?:([^-]+)-)?([^-]+)$/.exec(value || "");
if (!m || !hasCode(LEKTUR_CHOICES, m[2]) || (m[1] && !hasCode(LEKTUR_FORM_CHOICES, m[1]))) return null;
return { form: m[1] || "", base: m[2] };
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// numberLabels entries are objects ({display: "yekjimar"}), not strings -- "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// genderLabels entries are plain strings. A "<gender>-p" code is that gender in the plural.
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
var seq = 0;
function build(opts) {
opts = opts || {};
var context = { lang: opts.lang || "", pos: opts.pos || "" };
var idPrefix = "wiktku-pp-" + (++seq);
var mirror = { c: "", j: "" };
function fireChange() {
if (typeof opts.onChange === "function") opts.onChange();
}
// Gender/number are reported rather than written: the wizard mirrors them into hidden steps of
// its own, DefSideBoxes has nowhere to put them. Nulls mean "unchanged".
function syncMirror(c, j) {
if (c !== null && c !== undefined) mirror.c = c;
if (j !== null && j !== undefined) mirror.j = j;
if (typeof opts.onSync === "function") opts.onSync(mirror.c, mirror.j);
}
function mirrorValue(which) {
return mirror[which] || "";
}
var container = $("<div class='wiktku-posparams-step'></div>");
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
rebuildTa();
_rebuilding = false;
fireChange();
}
// onChange walks every sense block and nimûne item in the wizard, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("data-pp-row", key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' data-pp-row='c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Reported through syncMirror() rather than held here: the wizard mirrors gender into its own
// hidden step field, and DefSideBoxes ignores it.
cinsiyetSel.on("change", function () {
syncMirror($(this).val(), null);
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// reported through syncMirror() like gender, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' data-pp-row='jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
syncMirror(null, $(this).val());
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' data-pp-row='lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"));
$.each(LEKTUR_CHOICES, function (_, c) {
lekturSel.append($("<option></option>").val(c.code).text(c.label));
});
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' data-pp-row='lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"));
$.each(LEKTUR_FORM_CHOICES, function (_, c) {
lekturFormSel.append($("<option></option>").val(c.code).text(c.label));
});
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî are the conjugated forms; niha/borî/subj below are the roots the
// Lua module conjugates from. fniha/fborî show for every lêker language, fdbê only for ku,
// fsubj only outside ku — see syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' data-pp-heading='tewandin'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint, Kurmancî only: split ergativity makes a gerguhêz verb's past subject
// oblique ("min"), so fborî's hint follows the transitivity answer while fniha stays "ez".
// fdbê uses "ew ê". Hint and box share a flex line so the hint stays before the box.
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "fniha", "ez");
addPronounHint(fborîRow, "fborî", "");
addPronounHint(fdbêRow, "fdbê", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
// The script code, with the language's own scripts offered.
var scRow = makeTextRow("sc", "Koda skrîptê (sc)", "Latn, Arab…");
var scripts = [];
langs.suggest(scRow.find(".wiktku-posparams-field"), {
onFocus: true,
items: function (term) {
term = $.trim(term).toLowerCase();
return scripts.filter(function (code) {
return code.toLowerCase().indexOf(term) === 0;
}).map(function (code) {
return { value: code };
});
},
onPick: debouncedRebuild
});
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "xuyakirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(scRow)
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdek (mezin)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (biçûk)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjatr","Translit. hanja", "hanjatr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"))
.append(makeTextRow("id", "Id (id)", "id=…"))
.append(makeTextRow("sort", "Rêzkirin (sort)", "sort=…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — cureyê rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" },
{ key: "sckat", label: "sckat — kategoriya skrîptê lê zêde bike" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", idPrefix + "-opt-" + p.key).attr("data-pp-opt", p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", idPrefix + "-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("[data-pp-opt='form']");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", idPrefix + "-opt-neqiyasbar").attr("data-pp-opt", "neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", idPrefix + "-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
// Straight after "form" rather than at the foot of the list: the two share the |form=| param and
// exclude each other, so they read as one either/or pair when they sit together.
var $neqiyasbarRow = $("<div data-pp-row='neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl);
var $formRow = formCb.closest(".wiktku-posparams-opt-row");
if ($formRow.length) {
$formRow.after($neqiyasbarRow);
} else {
optPanel.append($neqiyasbarRow);
}
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Read with the shared parser, so a nested "c=" is never the call's own and "z=" answers for gender.
var callParam = wt.callParamValue;
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT fire onChange — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
readLine(text);
syncMirror(callParam(text, "c"), callParam(text, "j"));
});
// Name each parameter was written under on the page, keyed by the card's own name.
var spelling = {};
// Value of each ticked boolean as the page had it ("erê", "1", …).
var boolValue = {};
// Parameters the card has no field for, written back as they were: [{key, canon, value}].
var extras = [];
function selectHolds(sel, value) {
return sel.find("option").filter(function () { return this.value === value; }).length > 0;
}
// Fills every field from a "{{pos|lang|…}}" line; false when the text is not one closed call.
function readLine(text) {
var call = wt.parseTemplateCalls($.trim(text || "")).calls[0];
if (!call) return false;
var split = wt.splitCallParams(call.params);
spelling = {};
boolValue = {};
extras = [];
container.find(".wiktku-posparams-field").val("");
container.find("[data-pp-opt]").prop("checked", false);
cinsiyetSel.val("");
jimarSel.val("");
lekturSel.val("");
lekturFormSel.val("");
var isNoun = context.pos === "navdêr" || context.pos === "serenav";
split.used.forEach(function (key) {
var value = split.values[key];
var canon = key === "2" ? "cuda" : canonicalKey(key);
var taken = Object.prototype.hasOwnProperty.call(spelling, canon);
var placed = false;
if (key === "1") {
return;
}
// The "cuda" box is the positional 2; a named cuda= is a parameter of its own.
var $field = key === "cuda" ? $() : container.find(".wiktku-posparams-field[data-pp-key='" + canon + "']");
var $cb = container.find("[data-pp-opt='" + canon + "']");
if (taken) {
placed = false;
} else if ($field.length) {
$field.val(value);
placed = true;
} else if (canon === "form") {
if (value === "erê" || value === "-") {
container.find("[data-pp-opt='" + (value === "-" ? "neqiyasbar" : "form") + "']").prop("checked", true);
placed = true;
}
} else if ($cb.length && canon !== "neqiyasbar") {
if (value && FALSE_VALUES.indexOf(value.toLowerCase()) === -1) {
$cb.prop("checked", true);
boolValue[canon] = value;
placed = true;
}
} else if (canon === "c" && isNoun) {
if (selectHolds(cinsiyetSel, value)) {
cinsiyetSel.val(value);
placed = true;
}
} else if (canon === "c" && /^lêker/.test(context.pos)) {
var lektur = splitLektur(value);
if (lektur) {
lekturSel.val(lektur.base);
lekturFormSel.val(lektur.form);
placed = true;
}
} else if (canon === "j" && isNoun) {
if (selectHolds(jimarSel, value)) {
jimarSel.val(value);
placed = true;
}
}
if (placed) {
spelling[canon] = key;
} else {
extras.push({ key: key, canon: canon, value: value });
}
});
revealFilled();
return true;
}
// A value read from the page shows even where the row would not be offered, and opens the panel it sits in.
function revealFilled() {
container.find(".wiktku-posparams-field").each(function () {
if ($.trim(this.value)) $(this).closest("[data-pp-row]").show();
});
var filled = advPanel.find(".wiktku-posparams-field").filter(function () { return $.trim(this.value); }).length ||
advPanel.find("[data-pp-opt]:checked").length || jimarSel.val();
if (filled && !advPanel.is(":visible")) {
advPanel.show();
optToggle.attr("aria-expanded", "true").addClass("wiktku-adv-open");
}
}
container.append(wrap);
function rebuildTa() {
var pos = context.pos || "";
var lang = context.lang || "";
if (!pos || !lang) return;
var cinsiyet = container.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = container.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = container.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = container.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(container.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
var emitted = {};
function emit(key, value) {
out += "|" + (spelling[key] || key) + "=" + value;
emitted[key] = true;
}
function hasExtra(key) {
return extras.some(function (e) { return e.canon === key; });
}
// "|form=erê" marks an inflected form, which takes its gender from the lemma -- so the
// "|c=?" placeholder (a request to fill the gender in) has nothing to ask for there.
var isFormEntry = container.find("[data-pp-opt='form']").is(":checked");
// The alternative head is the positional 2, written right after the language.
if (v("cuda")) out += "|" + (/=/.test(v("cuda")) ? "2=" : "") + v("cuda");
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
emit("c", cinsiyet);
} else if (lang === "ku" && !isFormEntry && !hasExtra("c")) {
emit("c", "?");
}
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) emit("j", jimar);
} else if (/^lêker/.test(pos) && lektur) {
emit("c", lektur);
}
$.each(TEXT_KEYS, function (_, k) {
if (v(k)) emit(k, v(k));
});
container.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = OPT_PARAM_OVERRIDES[key];
if (override) {
emit(override.param, override.value);
} else {
emit(key, boolValue[key] || "1");
}
});
$.each(extras, function (_, e) {
if (!emitted[e.canon]) out += "|" + e.key + "=" + e.value;
});
out += "}}";
container.find(".wiktku-posparams-text").val(out);
syncMirror(cinsiyet, jimar);
}
function syncHelpers() {
var pos = context.pos || "";
var lang = context.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data-main.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = container.find("[data-pp-row=\'c\']");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || mirrorValue("c");
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = container.find("[data-pp-row=\'jimar\']");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || mirrorValue("j");
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
container.find("[data-pp-row=\'lektur\']").toggle(isLeker);
container.find("[data-pp-row=\'lekturform\']").toggle(isLeker);
container.find("[data-pp-row=\'tr\']").toggle(needsTr);
container.find("[data-pp-row=\'ts\']").toggle(needsTr);
container.find("[data-pp-row=\'sc\'], [data-pp-row=\'id\'], [data-pp-row=\'sort\']").show();
scripts = [].concat((meta && meta.sc) || []);
container.find("[data-pp-row=\'m\']").toggle(isNom);
container.find("[data-pp-row=\'mtr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'n\']").toggle(isNom);
container.find("[data-pp-row=\'ntr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'p\']").toggle(isNom);
container.find("[data-pp-row=\'ptr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'y\']").toggle(isNom);
container.find("[data-pp-row=\'ytr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'sûp\']").toggle(isAdj);
container.find("[data-pp-row=\'sûptr\']").toggle(isAdj && needsTr);
container.find("[data-pp-row=\'komp\']").toggle(isAdj);
container.find("[data-pp-row=\'komptr\']").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
container.find("[data-pp-row=\'neqiyasbar\']").toggle(isCompar);
if (isCompar) {
container.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
container.find("[data-pp-opt='neqiyasbar']").prop("checked", false);
}
// No "Formeke Xê" heading exists for these, so the box that makes one is not offered.
var formOk = !pos || sections.hasFormHeading(pos);
container.find("[data-pp-opt='form']").closest(".wiktku-posparams-opt-row").toggle(formOk);
if (!formOk) {
container.find("[data-pp-opt='form']").prop("checked", false);
}
container.find("[data-pp-row=\'gir\']").toggle(isTip);
container.find("[data-pp-row=\'girtr\']").toggle(isTip && needsTr);
container.find("[data-pp-row=\'hûr\']").toggle(isTip);
container.find("[data-pp-row=\'hûrtr\']").toggle(isTip && needsTr);
container.find("[data-pp-row=\'hanja\']").toggle(isJa);
container.find("[data-pp-row=\'hanjatr\']").toggle(isJa);
container.find("[data-pp-row=\'niha\']").toggle(isLeker);
container.find("[data-pp-row=\'nihatr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'borî\']").toggle(isLeker);
container.find("[data-pp-row=\'borîtr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'subj\']").toggle(isLekerSubj);
container.find("[data-pp-row=\'subjtr\']").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
container.find("[data-pp-heading='tewandin']").toggle(isLeker);
container.find("[data-pp-row=\'fniha\']").toggle(isLeker);
container.find("[data-pp-row=\'fnihatr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'fborî\']").toggle(isLeker);
container.find("[data-pp-row=\'fborîtr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'fdbê\']").toggle(isKuLeker);
container.find("[data-pp-row=\'fdbêtr\']").toggle(isKuLeker && needsTr);
container.find("[data-pp-row=\'fsubj\']").toggle(isLekerSubj);
container.find("[data-pp-row=\'fsubjtr\']").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
container.find("[data-pp-hint=\'fniha\']").toggle(isKuLeker);
var lekturVal = container.find(".wiktku-posparams-lektur-sel").val();
container.find("[data-pp-hint=\'fborî\']")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
container.find("[data-pp-hint=\'fdbê\']").toggle(isKuLeker);
revealFilled();
if (!$.trim(container.find(".wiktku-posparams-text").val())) {
rebuildTa();
}
}
syncHelpers();
return {
$element: container,
// isForm, when given, ticks "form" for a "Formeke Xê" heading; leaving it out keeps the checkbox.
setContext: function (lang, pos, isForm) {
var formCb = container.find("[data-pp-opt='form']");
var formMoved = isForm !== undefined && !!isForm !== formCb.is(":checked");
if (formMoved) {
formCb.prop("checked", !!isForm);
if (isForm) {
container.find("[data-pp-opt='neqiyasbar']").prop("checked", false);
}
}
var moved = formMoved || (lang || "") !== context.lang || (pos || "") !== context.pos;
context.lang = lang || "";
context.pos = pos || "";
syncHelpers();
// syncHelpers() only fills the line when the box is empty, so a hand-edited one survives
// every other change. A different part of speech or language is the exception: the
// template name and its language parameter both come from them, so whatever is in the
// box describes something that is no longer being edited.
if (moved) {
rebuildTa();
}
},
wikitext: function () {
return $.trim(container.find(".wiktku-posparams-text").val() || "");
},
setWikitext: function (text) {
container.find(".wiktku-posparams-text").val(text || "");
// Opened on a call the page already has: every field shows its value, read under any
// alias, so a save rewrites those values rather than adding a second one beside them.
readLine(text);
syncMirror(callParam(text, "c"), callParam(text, "j"));
},
rebuild: rebuildTa
};
}
module.exports = {
build: build,
needsTransliteration: needsTransliteration,
OPT_PARAM_OVERRIDES: OPT_PARAM_OVERRIDES
};
}());
// </nowiki>
85409mbbg0t2jgff0pmn9blo0ciljd2
6865654
6865625
2026-10-24T10:03:05Z
Wikihez
7344
Comment: scripts and genders come from wikt.lang-metadata
6865654
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit/posparams.js]]
*
* The part-of-speech parameter card: the live "{{navdêr|ku|…}}" line, the gender and transitivity
* questions, the transliteration boxes, and everything behind "Tercîhên pêşketî".
*
* Lived in the entry wizard, where it was one of its steps. DefSideBoxes needs the same card when it
* reclassifies a section, so it moved here rather than being written a second time -- two copies of a
* card with forty-odd fields and per-language gating would not have stayed in step.
*
* var card = posparams.build({
* lang: "ku", pos: "navdêr",
* onChange: function () { ... }, // a field changed
* onSync: function (c, j) { ... } // gender/number, for callers that mirror them
* });
* card.$element the card, to insert where you like
* card.setContext("ku", "lêker") re-gate every row for a new language / part of speech
* card.wikitext() / card.setWikitext(text) the template line the card describes
*
* Ids are per instance (see idPrefix): the checkboxes behind "Tercîhên pêşketî" pair a <label for> with
* an <input id>, and DefSideBoxes draws one card per section, so a fixed id would make a label toggle
* some other section's checkbox. Rows are found by data-pp-row for the same reason.
*/
(function () {
"use strict";
var data = require("ext.gadget.wikt.edit-data");
var sections = require("ext.gadget.wikt.core").sections;
var wt = require("./wikitext.js");
var langs = require("./langs.js");
// From wikt.edit's data (data-main.json, with metadata from wikt.lang-metadata), as the wizard reads them.
var metadata = data.metadata || {};
var NUMBER_LABELS = data.numberLabels || {};
var GENDER_BASE_LABELS = data.genderLabels || {};
var LEKTUR_CHOICES = data.lekturChoices || [];
var LEKTUR_FORM_CHOICES = data.lekturFormChoices || [];
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form
// and neqiyasbar both write to the shared |form=| param.
var OPT_PARAM_OVERRIDES = {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
};
// Modul:rêziman's alias_of names, read as the card's own.
var PARAM_ALIASES = {
sccat: "sckat",
katnacure: "nekatcure", noposcat: "nekatcure",
katnaçendterm: "nekatçendterm", nomultiwordcat: "nekatçendterm",
katnacins: "nekatcins", nogendercat: "nekatcins",
katnapalîndrom: "nekatpalîndrom", nopalindromecat: "nekatpalîndrom",
nolinkhead: "serlînkneke"
};
// Text boxes in the order the line writes them; "cuda" is the positional 2.
var TEXT_KEYS = ["tr", "ts", "sc", "m", "mtr", "n", "ntr", "p", "ptr", "y", "ytr", "sûp", "sûptr",
"komp", "komptr", "gir", "girtr", "hûr", "hûrtr", "hanja", "hanjatr", "niha", "nihatr",
"borî", "borîtr", "subj", "subjtr", "fniha", "fnihatr", "fborî", "fborîtr", "fdbê", "fdbêtr",
"fsubj", "fsubjtr", "id", "sort"];
// Values Module:parameters reads as false for a boolean.
var FALSE_VALUES = ["0", "no", "n", "false", "na", "ne", "nexêr"];
function canonicalKey(key) {
var k = $.trim(key);
return PARAM_ALIASES[k] || wt.canonicalParamName(k);
}
function hasCode(choices, code) {
return choices.some(function (c) { return c.code === code; });
}
// "gh" or "<form>-<base>"; null when the value is not one the two dropdowns can hold.
function splitLektur(value) {
var m = /^(?:([^-]+)-)?([^-]+)$/.exec(value || "");
if (!m || !hasCode(LEKTUR_CHOICES, m[2]) || (m[1] && !hasCode(LEKTUR_FORM_CHOICES, m[1]))) return null;
return { form: m[1] || "", base: m[2] };
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// numberLabels entries are objects ({display: "yekjimar"}), not strings -- "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// genderLabels entries are plain strings. A "<gender>-p" code is that gender in the plural.
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
var seq = 0;
function build(opts) {
opts = opts || {};
var context = { lang: opts.lang || "", pos: opts.pos || "" };
var idPrefix = "wiktku-pp-" + (++seq);
var mirror = { c: "", j: "" };
function fireChange() {
if (typeof opts.onChange === "function") opts.onChange();
}
// Gender/number are reported rather than written: the wizard mirrors them into hidden steps of
// its own, DefSideBoxes has nowhere to put them. Nulls mean "unchanged".
function syncMirror(c, j) {
if (c !== null && c !== undefined) mirror.c = c;
if (j !== null && j !== undefined) mirror.j = j;
if (typeof opts.onSync === "function") opts.onSync(mirror.c, mirror.j);
}
function mirrorValue(which) {
return mirror[which] || "";
}
var container = $("<div class='wiktku-posparams-step'></div>");
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
rebuildTa();
_rebuilding = false;
fireChange();
}
// onChange walks every sense block and nimûne item in the wizard, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("data-pp-row", key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' data-pp-row='c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Reported through syncMirror() rather than held here: the wizard mirrors gender into its own
// hidden step field, and DefSideBoxes ignores it.
cinsiyetSel.on("change", function () {
syncMirror($(this).val(), null);
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// reported through syncMirror() like gender, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' data-pp-row='jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
syncMirror(null, $(this).val());
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' data-pp-row='lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"));
$.each(LEKTUR_CHOICES, function (_, c) {
lekturSel.append($("<option></option>").val(c.code).text(c.label));
});
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' data-pp-row='lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"));
$.each(LEKTUR_FORM_CHOICES, function (_, c) {
lekturFormSel.append($("<option></option>").val(c.code).text(c.label));
});
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî are the conjugated forms; niha/borî/subj below are the roots the
// Lua module conjugates from. fniha/fborî show for every lêker language, fdbê only for ku,
// fsubj only outside ku — see syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' data-pp-heading='tewandin'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint, Kurmancî only: split ergativity makes a gerguhêz verb's past subject
// oblique ("min"), so fborî's hint follows the transitivity answer while fniha stays "ez".
// fdbê uses "ew ê". Hint and box share a flex line so the hint stays before the box.
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "fniha", "ez");
addPronounHint(fborîRow, "fborî", "");
addPronounHint(fdbêRow, "fdbê", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
// The script code, with the language's own scripts offered.
var scRow = makeTextRow("sc", "Koda skrîptê (sc)", "Latn, Arab…");
var scripts = [];
langs.suggest(scRow.find(".wiktku-posparams-field"), {
onFocus: true,
items: function (term) {
term = $.trim(term).toLowerCase();
return scripts.filter(function (code) {
return code.toLowerCase().indexOf(term) === 0;
}).map(function (code) {
return { value: code };
});
},
onPick: debouncedRebuild
});
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "xuyakirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(scRow)
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdek (mezin)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (biçûk)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjatr","Translit. hanja", "hanjatr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"))
.append(makeTextRow("id", "Id (id)", "id=…"))
.append(makeTextRow("sort", "Rêzkirin (sort)", "sort=…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — cureyê rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" },
{ key: "sckat", label: "sckat — kategoriya skrîptê lê zêde bike" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", idPrefix + "-opt-" + p.key).attr("data-pp-opt", p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", idPrefix + "-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("[data-pp-opt='form']");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", idPrefix + "-opt-neqiyasbar").attr("data-pp-opt", "neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", idPrefix + "-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
// Straight after "form" rather than at the foot of the list: the two share the |form=| param and
// exclude each other, so they read as one either/or pair when they sit together.
var $neqiyasbarRow = $("<div data-pp-row='neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl);
var $formRow = formCb.closest(".wiktku-posparams-opt-row");
if ($formRow.length) {
$formRow.after($neqiyasbarRow);
} else {
optPanel.append($neqiyasbarRow);
}
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Read with the shared parser, so a nested "c=" is never the call's own and "z=" answers for gender.
var callParam = wt.callParamValue;
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT fire onChange — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
readLine(text);
syncMirror(callParam(text, "c"), callParam(text, "j"));
});
// Name each parameter was written under on the page, keyed by the card's own name.
var spelling = {};
// Value of each ticked boolean as the page had it ("erê", "1", …).
var boolValue = {};
// Parameters the card has no field for, written back as they were: [{key, canon, value}].
var extras = [];
function selectHolds(sel, value) {
return sel.find("option").filter(function () { return this.value === value; }).length > 0;
}
// Fills every field from a "{{pos|lang|…}}" line; false when the text is not one closed call.
function readLine(text) {
var call = wt.parseTemplateCalls($.trim(text || "")).calls[0];
if (!call) return false;
var split = wt.splitCallParams(call.params);
spelling = {};
boolValue = {};
extras = [];
container.find(".wiktku-posparams-field").val("");
container.find("[data-pp-opt]").prop("checked", false);
cinsiyetSel.val("");
jimarSel.val("");
lekturSel.val("");
lekturFormSel.val("");
var isNoun = context.pos === "navdêr" || context.pos === "serenav";
split.used.forEach(function (key) {
var value = split.values[key];
var canon = key === "2" ? "cuda" : canonicalKey(key);
var taken = Object.prototype.hasOwnProperty.call(spelling, canon);
var placed = false;
if (key === "1") {
return;
}
// The "cuda" box is the positional 2; a named cuda= is a parameter of its own.
var $field = key === "cuda" ? $() : container.find(".wiktku-posparams-field[data-pp-key='" + canon + "']");
var $cb = container.find("[data-pp-opt='" + canon + "']");
if (taken) {
placed = false;
} else if ($field.length) {
$field.val(value);
placed = true;
} else if (canon === "form") {
if (value === "erê" || value === "-") {
container.find("[data-pp-opt='" + (value === "-" ? "neqiyasbar" : "form") + "']").prop("checked", true);
placed = true;
}
} else if ($cb.length && canon !== "neqiyasbar") {
if (value && FALSE_VALUES.indexOf(value.toLowerCase()) === -1) {
$cb.prop("checked", true);
boolValue[canon] = value;
placed = true;
}
} else if (canon === "c" && isNoun) {
if (selectHolds(cinsiyetSel, value)) {
cinsiyetSel.val(value);
placed = true;
}
} else if (canon === "c" && /^lêker/.test(context.pos)) {
var lektur = splitLektur(value);
if (lektur) {
lekturSel.val(lektur.base);
lekturFormSel.val(lektur.form);
placed = true;
}
} else if (canon === "j" && isNoun) {
if (selectHolds(jimarSel, value)) {
jimarSel.val(value);
placed = true;
}
}
if (placed) {
spelling[canon] = key;
} else {
extras.push({ key: key, canon: canon, value: value });
}
});
revealFilled();
return true;
}
// A value read from the page shows even where the row would not be offered, and opens the panel it sits in.
function revealFilled() {
container.find(".wiktku-posparams-field").each(function () {
if ($.trim(this.value)) $(this).closest("[data-pp-row]").show();
});
var filled = advPanel.find(".wiktku-posparams-field").filter(function () { return $.trim(this.value); }).length ||
advPanel.find("[data-pp-opt]:checked").length || jimarSel.val();
if (filled && !advPanel.is(":visible")) {
advPanel.show();
optToggle.attr("aria-expanded", "true").addClass("wiktku-adv-open");
}
}
container.append(wrap);
function rebuildTa() {
var pos = context.pos || "";
var lang = context.lang || "";
if (!pos || !lang) return;
var cinsiyet = container.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = container.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = container.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = container.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(container.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
var emitted = {};
function emit(key, value) {
out += "|" + (spelling[key] || key) + "=" + value;
emitted[key] = true;
}
function hasExtra(key) {
return extras.some(function (e) { return e.canon === key; });
}
// "|form=erê" marks an inflected form, which takes its gender from the lemma -- so the
// "|c=?" placeholder (a request to fill the gender in) has nothing to ask for there.
var isFormEntry = container.find("[data-pp-opt='form']").is(":checked");
// The alternative head is the positional 2, written right after the language.
if (v("cuda")) out += "|" + (/=/.test(v("cuda")) ? "2=" : "") + v("cuda");
if (pos === "navdêr" || pos === "serenav") {
if (cinsiyet) {
emit("c", cinsiyet);
} else if (lang === "ku" && !isFormEntry && !hasExtra("c")) {
emit("c", "?");
}
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) emit("j", jimar);
} else if (/^lêker/.test(pos) && lektur) {
emit("c", lektur);
}
$.each(TEXT_KEYS, function (_, k) {
if (v(k)) emit(k, v(k));
});
container.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = OPT_PARAM_OVERRIDES[key];
if (override) {
emit(override.param, override.value);
} else {
emit(key, boolValue[key] || "1");
}
});
$.each(extras, function (_, e) {
if (!emitted[e.canon]) out += "|" + e.key + "=" + e.value;
});
out += "}}";
container.find(".wiktku-posparams-text").val(out);
syncMirror(cinsiyet, jimar);
}
function syncHelpers() {
var pos = context.pos || "";
var lang = context.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data-main.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = container.find("[data-pp-row=\'c\']");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || mirrorValue("c");
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = container.find("[data-pp-row=\'jimar\']");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || mirrorValue("j");
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
container.find("[data-pp-row=\'lektur\']").toggle(isLeker);
container.find("[data-pp-row=\'lekturform\']").toggle(isLeker);
container.find("[data-pp-row=\'tr\']").toggle(needsTr);
container.find("[data-pp-row=\'ts\']").toggle(needsTr);
container.find("[data-pp-row=\'sc\'], [data-pp-row=\'id\'], [data-pp-row=\'sort\']").show();
scripts = [].concat((meta && meta.sc) || []);
container.find("[data-pp-row=\'m\']").toggle(isNom);
container.find("[data-pp-row=\'mtr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'n\']").toggle(isNom);
container.find("[data-pp-row=\'ntr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'p\']").toggle(isNom);
container.find("[data-pp-row=\'ptr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'y\']").toggle(isNom);
container.find("[data-pp-row=\'ytr\']").toggle(isNom && needsTr);
container.find("[data-pp-row=\'sûp\']").toggle(isAdj);
container.find("[data-pp-row=\'sûptr\']").toggle(isAdj && needsTr);
container.find("[data-pp-row=\'komp\']").toggle(isAdj);
container.find("[data-pp-row=\'komptr\']").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
container.find("[data-pp-row=\'neqiyasbar\']").toggle(isCompar);
if (isCompar) {
container.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
container.find("[data-pp-opt='neqiyasbar']").prop("checked", false);
}
// No "Formeke Xê" heading exists for these, so the box that makes one is not offered.
var formOk = !pos || sections.hasFormHeading(pos);
container.find("[data-pp-opt='form']").closest(".wiktku-posparams-opt-row").toggle(formOk);
if (!formOk) {
container.find("[data-pp-opt='form']").prop("checked", false);
}
container.find("[data-pp-row=\'gir\']").toggle(isTip);
container.find("[data-pp-row=\'girtr\']").toggle(isTip && needsTr);
container.find("[data-pp-row=\'hûr\']").toggle(isTip);
container.find("[data-pp-row=\'hûrtr\']").toggle(isTip && needsTr);
container.find("[data-pp-row=\'hanja\']").toggle(isJa);
container.find("[data-pp-row=\'hanjatr\']").toggle(isJa);
container.find("[data-pp-row=\'niha\']").toggle(isLeker);
container.find("[data-pp-row=\'nihatr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'borî\']").toggle(isLeker);
container.find("[data-pp-row=\'borîtr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'subj\']").toggle(isLekerSubj);
container.find("[data-pp-row=\'subjtr\']").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
container.find("[data-pp-heading='tewandin']").toggle(isLeker);
container.find("[data-pp-row=\'fniha\']").toggle(isLeker);
container.find("[data-pp-row=\'fnihatr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'fborî\']").toggle(isLeker);
container.find("[data-pp-row=\'fborîtr\']").toggle(isLeker && needsTr);
container.find("[data-pp-row=\'fdbê\']").toggle(isKuLeker);
container.find("[data-pp-row=\'fdbêtr\']").toggle(isKuLeker && needsTr);
container.find("[data-pp-row=\'fsubj\']").toggle(isLekerSubj);
container.find("[data-pp-row=\'fsubjtr\']").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
container.find("[data-pp-hint=\'fniha\']").toggle(isKuLeker);
var lekturVal = container.find(".wiktku-posparams-lektur-sel").val();
container.find("[data-pp-hint=\'fborî\']")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
container.find("[data-pp-hint=\'fdbê\']").toggle(isKuLeker);
revealFilled();
if (!$.trim(container.find(".wiktku-posparams-text").val())) {
rebuildTa();
}
}
syncHelpers();
return {
$element: container,
// isForm, when given, ticks "form" for a "Formeke Xê" heading; leaving it out keeps the checkbox.
setContext: function (lang, pos, isForm) {
var formCb = container.find("[data-pp-opt='form']");
var formMoved = isForm !== undefined && !!isForm !== formCb.is(":checked");
if (formMoved) {
formCb.prop("checked", !!isForm);
if (isForm) {
container.find("[data-pp-opt='neqiyasbar']").prop("checked", false);
}
}
var moved = formMoved || (lang || "") !== context.lang || (pos || "") !== context.pos;
context.lang = lang || "";
context.pos = pos || "";
syncHelpers();
// syncHelpers() only fills the line when the box is empty, so a hand-edited one survives
// every other change. A different part of speech or language is the exception: the
// template name and its language parameter both come from them, so whatever is in the
// box describes something that is no longer being edited.
if (moved) {
rebuildTa();
}
},
wikitext: function () {
return $.trim(container.find(".wiktku-posparams-text").val() || "");
},
setWikitext: function (text) {
container.find(".wiktku-posparams-text").val(text || "");
// Opened on a call the page already has: every field shows its value, read under any
// alias, so a save rewrites those values rather than adding a second one beside them.
readLine(text);
syncMirror(callParam(text, "c"), callParam(text, "j"));
},
rebuild: rebuildTa
};
}
module.exports = {
build: build,
needsTransliteration: needsTransliteration,
OPT_PARAM_OVERRIDES: OPT_PARAM_OVERRIDES
};
}());
// </nowiki>
r0jbaw9qx6o30pibj2tp1z8cwe46hcx
MediaWiki:Gadget-wikt.edit/data-main.json
8
1359163
6865576
6865399
2026-10-23T20:36:18Z
Wikihez
7344
Language list from wikt.suggest (Modul:ziman/code to canonical name.json, same as the translation adder); darna behind Parametreyên din with labelled values
6865576
json
application/json
{
"langChoices": {
"ku": "kurmancî",
"zza": "zazakî",
"ckb": "soranî",
"sdh": "kurdiya başûrî",
"hac": "hewramî",
"de": "almanî",
"ar": "erebî",
"fa": "farisî",
"fr": "fransî",
"fi": "fînî",
"en": "îngilîzî",
"ja": "japonî",
"sv": "swêdî",
"tr": "tirkî"
},
"posExamples": {
"navdêr": "Kelîmeyên ji bo tiştan (sêv, heywan, qedeh) yan terman (dilşadî, bextiyarî hwd.) di rêzimanê de.",
"lêker": "Kelîmeyên ji bo kirin an bûyînê wekî 'nivîsîn' yan 'xeber dan'.",
"rengdêr": "Kelîmeyên ku wesfa navdêran nîşan didin wekî 'sêva sor' yan 'xwarina şîrîn'.",
"hoker": "Kelîmeyên ku dem, cih yan awayê kirinê diyar dikin wekî 'bi vî awayî' yan 'demkî'.",
"daçek": "Kelîmeyên ku cih, dem yan hereketê diyar dikin wekî 'di.. de' yan 'ji'.",
"cînav": "Kelîmeyên ku li şûna navdêran tên bikaranîn daku navdêr neyên dubarekirin wekî 'ew' yan 'em'.",
"baneşan": "Kelîmeyên ku hest, deng hwd. îfade dikin wekî 'noşî!' yan 'aferîn'.",
"serenav": "Navdêrên ku şexsiyetên ferdî nîşan didin, wek navên kes, cih an organîzasyonan nimûne 'London' yan 'Diyarbekir'.",
"gotineke pêşiyan": "Gotinên ku di nav xelkê de bi berbelavî tên bikaranîn bo nimûne 'Serê neêşe ne hewceyî destmalê ye'."
},
"genderLabels": {
"m": "mê",
"n": "nêr",
"nt": "nêtar",
"mn": "mê yan nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"mndudil": "mê yan nêr bikaranîn dudil e",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": {
"rastnivîs": {
"langs": [
"ku"
],
"noLang": true
},
"ku-Latn": {
"langs": [
"ku"
],
"noLang": true
},
"ku-tewîna-nav": {
"langs": [
"ku"
],
"noLang": true
},
"sdh-Arab": {
"langs": [
"sdh"
],
"noLang": true
},
"hac-Arab": {
"langs": [
"hac"
],
"noLang": true
},
"ja-romanîzekirin": {
"langs": [
"ja"
],
"noLang": true
},
"ckb-Arab": {
"langs": [
"ckb"
],
"noLang": true
},
"zza-awayê-Jacobsonê": {
"langs": [
"zza"
]
},
"awayekî din": {
"langs": null
},
"guharto": {
"langs": null
},
"hevmaneya": {
"langs": null
},
"kevnbûyî": {
"langs": null
},
"kêmpeyda": {
"langs": null
},
"kurtkirin": {
"langs": null
},
"paşnav": {
"langs": null
},
"şaşnivîs": {
"langs": null
}
},
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 3
},
{
"key": "terciman",
"label": "Terciman",
"span": 3
},
{
"key": "amadekar",
"label": "Amadekar",
"span": 3
},
{
"key": "weşanxane",
"label": "Weşanxane",
"span": 3
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "beş",
"label": "Beş",
"span": 3,
"width": "medium"
},
{
"key": "cih",
"label": "Cih",
"span": 2,
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"span": 1,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
},
{
"key": "sala-weşandinê",
"label": "Sala weşandinê",
"span": 2,
"width": "narrow"
},
{
"key": "çap",
"label": "Çap",
"span": 1,
"width": "narrow"
},
{
"key": "cild",
"label": "Cild",
"span": 2,
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"span": 1,
"width": "narrow"
},
{
"key": "isbn",
"label": "ISBN",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 3
},
{
"key": "terciman",
"label": "Terciman",
"span": 3
},
{
"key": "amadekar",
"label": "Amadekar",
"span": 3
},
{
"key": "weşanxane",
"label": "Weşanxane",
"span": 3
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "kovar",
"label": "Kovar",
"span": 4
},
{
"key": "cild",
"label": "Cild",
"span": 2,
"width": "narrow"
},
{
"key": "hejmar",
"label": "Hejmar",
"span": 2,
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"span": 2,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
},
{
"key": "rûpel",
"label": "Rûpel",
"span": 2,
"width": "narrow"
},
{
"key": "issn",
"label": "ISSN",
"span": 2,
"width": "narrow"
},
{
"key": "doi",
"label": "DOI",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 4
},
{
"key": "terciman",
"label": "Terciman",
"span": 4
},
{
"key": "websîte",
"label": "Websîte",
"span": 4
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "url",
"label": "URL",
"span": 5
},
{
"key": "urlya-arşîvê",
"label": "URLya Arşîvê",
"span": 5
},
{
"key": "roja-arşîvê",
"label": "Roja arşîvê",
"span": 3,
"width": "medium"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
},
{
"key": "sal",
"label": "Sal",
"span": 1,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
}
]
},
"jêder-vîdeo": {
"fields": [
{
"key": "hunermend",
"label": "Hunermend",
"span": 6
},
{
"key": "platform",
"label": "Platform",
"span": 6
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "sal",
"label": "Sal",
"span": 2,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3
},
{
"key": "dem",
"label": "Dem",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
],
"label": "Vîdeo"
}
},
"pronTpls": {
"ku-IPA": {
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
"ku-kîte": {
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
"zza-IPA": {
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
"zza-kîte": {
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
"IPA": {
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
"deng": {
"desc": "Nimûneya telafûzê bi dosyeya dengî",
"snippet": "* {{deng|CODE|navê_dosyayê.ogg|dever=}}"
},
"bilêv": {
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
"kîte": {
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
"hevdeng": {
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
},
"etymTpls": {
"etîmon": {
"desc": "Etîmolojiya birêkûpêk: zincîr, dar û kategorî — {{etîmon|ku|:deyn|fa:پنجه\u003Ct:dest\u003E}}",
"snippet": "{{etîmon|CODE}}"
},
"ji": {
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
"deyn": {
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
"mîr": {
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
"qertaf": {
"desc": "Ji bo pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
"anlz": {
"desc": "Wekî qertaf lê dibêje analîza şiklî — {{anlz|kod|pêşgir-|reh}}",
"snippet": "{{anlz|CODE|…|…}}"
},
"q": {
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
"hv": {
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
"nh": {
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
"cotreh": {
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
"yekser": {
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
"wergirtin": {
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
"paşvesazî": {
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
"qusandin": {
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
"têkilkirin": {
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
"nyz": {
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
"onom": {
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
"navmiletî": {
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
"neo": {
"desc": "neolojîzm — {{neolojîzm|ku|hûr=1}}",
"snippet": "{{neolojîzm|CODE}}"
}
},
"etimon": {
"keywords": [
{
"key": "mîrasmayî",
"desc": "Kelîmeya ku ji zimanê bav û kalan maye",
"aliases": [
"mîr",
"inh"
]
},
{
"key": "deyn",
"desc": "Kelîmeya ku rasterast ji zimanekî din hatiye deynkirin",
"aliases": [
"bor",
"borrowed"
]
},
{
"key": "deynh",
"desc": "Deynkirina hînbûyî: ne bi riya têkiliya rojane, bi qesd hatiye girtin",
"short": "deynkirina hînbûyî",
"aliases": [
"lbor"
]
},
{
"key": "deynhq",
"desc": "Deynkirina hînbûyî ya ku hinekî hatiye guhertin",
"short": "deynkirina hînbûyî ya qismî",
"aliases": [
"slbor"
]
},
{
"key": "deynort",
"desc": "Deynkirina ortografîk",
"short": "deynkirina ortografîk",
"aliases": [
"obor"
]
},
{
"key": "deynne",
"desc": "Deynkirina neadaptekirî",
"short": "deynkirina neadaptekirî",
"aliases": [
"ubor"
]
},
{
"key": "dariştî",
"desc": "Kelîmeya ku ji zimanekî din hatiye dariştin, wextê ne mîrasmayî û ne jî deynkirineke diyar be",
"short": "dariştin ji zimanekî din",
"aliases": [
"drş",
"der"
]
},
{
"key": "ji",
"desc": "Dariştineke di nav eynî zimanî de ku cureyê wê nehatiye diyarkirin; tenê ji bo kelîmeyên eynî zimanî derbasdar e",
"short": "dariştin di eynî zimanî de",
"aliases": [
"from"
]
},
{
"key": "drşnd",
"desc": "Dariştin heye lê cureyê wê ne diyar e",
"short": "dariştin, cure ne diyar",
"aliases": [
"uder"
]
},
{
"key": "qertaf",
"desc": "Kelîmeya ji qertafan an ji du kelîmeyan pêk hatî; rêza parçeyan girîng e",
"short": "ji qertafan an du kelîmeyan",
"aliases": [
"qf",
"af"
]
},
{
"key": "reh",
"desc": "Kelîmeyên pey xwe wek reh dide nasîn; tenê ji bo kategoriyan e (\"Peyvên … yên aîdî rehê …\") û di metn û darê de xuya nabe",
"short": "reh, tenê kategorî",
"aliases": [
"root"
],
"invisible": true
},
{
"key": "yekser",
"desc": "Wergerandina yekser; zincîr li vir disekine",
"short": "wergerandina yekser",
"aliases": [
"cal",
"clq"
]
},
{
"key": "yekserqismî",
"desc": "Wergerandina yekser a qismî; zincîr li vir disekine",
"short": "wergerandina yekser a qismî",
"aliases": [
"yekserq",
"pcal"
]
},
{
"key": "wergirtin",
"desc": "Wergirtina maneyê; mana wergirtî bi \u003Cmaneid:…\u003E tê nîşankirin",
"short": "wergirtina maneyê",
"aliases": [
"sl"
]
},
{
"key": "lihevanîn",
"desc": "Lihevanîna dengî-maneyî",
"aliases": [
"psm"
]
},
{
"key": "tesîr",
"desc": "Kelîme di bin tesîra kelîmeyeke din de guheriye; zincîr li vir disekine",
"short": "di bin tesîra kelîmeyekê",
"aliases": [
"influence"
]
},
{
"key": "têkilkirin",
"desc": "Têkilkirina du kelîmeyan",
"aliases": [
"blend"
],
"min": 2
},
{
"key": "ûnîverbasyon",
"desc": "Ûnîverbasyon",
"aliases": [
"ûnîv",
"univ"
]
},
{
"key": "qusandin",
"desc": "Qusandin",
"aliases": [
"clip"
]
},
{
"key": "paşvesazî",
"desc": "Paşvesazî",
"aliases": [
"bf"
]
},
{
"key": "elîpsîz",
"desc": "Elîpsîz",
"aliases": [
"elîp",
"ellip"
]
},
{
"key": "nomînalîzasyon",
"desc": "Nomînalîzasyon",
"aliases": [
"nomîn",
"nom"
]
},
{
"key": "translîterasyon",
"desc": "Translîterasyon",
"aliases": [
"translît",
"translit"
]
},
{
"key": "redûblîkasyon",
"desc": "Redûblîkasyon",
"aliases": [
"redûb",
"redup"
]
},
{
"key": "kurtkirin",
"desc": "Kurtkirina kelîmeyekê",
"aliases": [
"abbr"
]
},
{
"key": "sylabbr",
"desc": "Kurtkirin li gorî kîteyan",
"short": "kurtkirin bi kîteyan",
"aliases": [
"syllabic abbreviation",
"sylabbrev"
]
},
{
"key": "akronîm",
"desc": "Akronîm",
"aliases": [
"acro"
]
},
{
"key": "înîtîalîzm",
"desc": "Înîtîalîzm",
"aliases": [
"înît",
"init"
]
},
{
"key": "metatez",
"desc": "Metatez",
"aliases": [
"meta"
]
},
{
"key": "afeq",
"desc": "Wek :qf, lê ne di metnê ne jî di darê de xuya dibe; tenê kategoriyan çêdike",
"short": "qertaf, tenê kategorî",
"invisible": true
},
{
"key": "apheresis",
"desc": "Şiklê ku dengê destpêkê winda kiriye",
"short": "dengê destpêkê winda",
"aliases": [
"apheretic",
"aphetic"
]
},
{
"key": "denominal",
"desc": "Fiîla ji îsmekê çêbûyî",
"short": "fiîl ji îsmekê",
"aliases": [
"denom"
]
},
{
"key": "deverbal",
"desc": "Kelîmeya ji fiîlekê çêbûyî",
"short": "kelîme ji fiîlekê"
},
{
"key": "vrd",
"desc": "Dariştina vṛddhî (sanskrîtî); bi qertaf: :vrd-af",
"short": "dariştina vṛddhî"
},
{
"key": "sa-af",
"desc": "Pêkhateya sanskrîtî",
"short": "pêkhateya sanskrîtî",
"min": 2
}
],
"wordMods": [
{
"key": "t",
"desc": "Tercimeya kelîmeyê bi kurmancî",
"aliases": [
"w"
]
},
{
"key": "tr",
"desc": "Translîterasyon"
},
{
"key": "ts",
"desc": "Transkrîpsiyon"
},
{
"key": "cuda",
"desc": "Şiklê ku tê nîşandan, eger ne wek navê rûpelê be",
"aliases": [
"alt"
]
},
{
"key": "id",
"desc": "Nasnameya etîmolojiya wê kelîmeyê li ser rûpela wê; eger mane tune be, wek mane tê nîşandan"
},
{
"key": "crz",
"desc": "Cureyê rêzimanî yê kelîmeyê",
"aliases": [
"pos"
],
"values": [
"reh",
"kelîme",
"pêşgir",
"paşgir",
"navgir"
]
},
{
"key": "c",
"desc": "Cinsiyet: n (nêr), m (mê), nt (nêtar)",
"values": [
"n",
"m",
"nt"
],
"aliases": [
"z",
"g"
]
},
{
"key": "q",
"desc": "Wesf li pêşiyê kelîmeyê"
},
{
"key": "qq",
"desc": "Wesf li pey kelîmeyê"
},
{
"key": "hrf",
"desc": "Mana herfiyen",
"aliases": [
"lit"
]
},
{
"key": "ref",
"desc": "Çavkanî; binêre #Çavkanî"
},
{
"key": "nediyar",
"desc": "Ev gav ne teqez e",
"flag": true,
"aliases": [
"unc"
]
},
{
"key": "crztip",
"desc": "Kelîme ji bo kategoriyan wek reh yan kelîme tê hesibandin",
"aliases": [
"postype"
],
"values": [
"reh",
"kelîme"
]
},
{
"key": "etimon",
"desc": "Etîmolojiya di cih de; binêre jor",
"aliases": [
"ety"
]
},
{
"key": "aftype",
"desc": "Di nav :qf de: cureyê qertafê; binêre #Cureyên qertafan",
"values": [
"pêşgir",
"paşgir",
"navgir",
"circumfix",
"infix",
"non-affix",
"root"
],
"only": [
"qertaf"
]
},
{
"key": "deyn",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "deynh",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "deynhq",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
}
],
"keywordMods": [
{
"key": "nediyar",
"desc": "Ev gav ne teqez e: \"Mihtemelen…\"",
"flag": true,
"aliases": [
"unc"
]
},
{
"key": "grdk",
"desc": "Girêdeka di navbera kelîmeyên jêder de, li şûna \"yan jî\"",
"aliases": [
"conj"
]
},
{
"key": "anlz",
"desc": "Analîza şiklî",
"flag": true,
"aliases": [
"surf"
]
},
{
"key": "neo",
"desc": "Di nav :qf de: neolojîzm, \"Neolojîzm ji…\", di darê de \"neo.\"",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "hrf",
"desc": "Di nav :qf de: mana herfiyen",
"aliases": [
"lit"
],
"only": [
"qertaf"
]
},
{
"key": "maneid",
"desc": "Di nav :wergirtin de: nasnameya maneya wergirtî",
"aliases": [
"senseid"
],
"only": [
"wergirtin"
]
},
{
"key": "metn",
"desc": "Di nav :ji û :drş de: metnekî din li şûna \"Ji\"",
"aliases": [
"text"
],
"only": [
"ji",
"dariştî"
]
},
{
"key": "ref",
"desc": "Çavkaniya vê gavê"
}
],
"metnChoices": [
{
"value": "++",
"desc": "Tevahiya zincîrê"
},
{
"value": "+",
"desc": "Tenê gava pêşîn"
},
{
"value": "*",
"desc": "Heta kelîmeya pêşîn a ku rûpela wê heye"
},
{
"value": "1",
"desc": "Ewqas gav (hejmarek)",
"free": true
},
{
"value": ":",
"desc": "Heta zimanê bi wê kodê (:kod)",
"free": true
},
{
"value": ":*",
"desc": "Heta wî zimanî, eger rûpela wî tune be heta ya pêşîn a heyî (:kod*)",
"free": true
}
],
"afTypes": [
{
"key": "pêşgir",
"desc": "Pêşgir"
},
{
"key": "paşgir",
"desc": "Paşgir"
},
{
"key": "navgir",
"desc": "Navgir"
},
{
"key": "circumfix",
"desc": "Dorgir"
},
{
"key": "infix",
"desc": "Nawgir (infix)"
},
{
"key": "non-affix",
"desc": "Ne qertaf"
},
{
"key": "root",
"desc": "Reh"
}
],
"paramAliases": {
"text": "metn",
"notext": "metnna",
"pos": "crz",
"doublet": "cotreh",
"etydate": "tarîxa-etîm",
"nodot": "niqtena",
"dot": "niqte",
"nocat": "katna",
"nekat": "katna"
},
"params": [
{
"key": "darna",
"desc": "",
"type": "select",
"values": [
{
"value": "1",
"label": "1 - dara etîmolojiyê li vê rûpelê nayê nîşandan"
},
{
"value": "0",
"label": "0 - ev etîmolojî di dara rûpelên din de dernakeve"
}
]
},
{
"key": "id",
"desc": "Nasnameya vê etîmolojiyê. Eger mane nehatibe dayîn, wek mane jî tê nîşandan.",
"type": "text",
"primary": true
},
{
"key": "title",
"desc": "Kelîmeya ku etîmolojî jê re tê nivîsîn, eger ne navê rûpelê be.",
"type": "text"
},
{
"key": "crz",
"desc": "Cureyê rêzimanî yê kelîmeyê ji bo kategoriyan: reh, kelîme, pêşgir, paşgir, navgir.",
"type": "select",
"values": [
"reh",
"kelîme",
"pêşgir",
"paşgir",
"navgir"
]
},
{
"key": "cotreh",
"desc": "Cotrehên vê kelîmeyê, bi bêhnokê ji hev cuda.",
"type": "text"
},
{
"key": "tarîxa-etîm",
"desc": "Tarîxa peyitandina pêşîn, wek 1901, 1500s, 14c, c,1900, before,1850, r,1501,1558.",
"type": "text"
},
{
"key": "niqtena",
"desc": "Bi 1 niqteya dawiyê nayê zêdekirin.",
"type": "flag"
},
{
"key": "niqte",
"desc": "Li şûna niqteya dawiyê nîşaneke din.",
"type": "text"
},
{
"key": "katna",
"desc": "Bi 1 kategorî nayên zêdekirin. Herwiha {{para|nekat}}.",
"type": "flag"
},
{
"key": "exnihilo",
"desc": "Kelîmeya ku ji tu kelîmeyeke din nehatiye çêkirin.",
"type": "flag"
}
]
},
"metadata": {
"aa": {
"haswikt": 1,
"sc": [
"Latn",
"Ethi"
]
},
"ab": {
"haswikt": 1,
"sc": [
"Cyrl",
"Latn",
"Geor"
]
},
"aer": {
"sc": "Latn"
},
"af": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ak": {
"haswikt": 1
},
"akk": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Xsux"
},
"als": {
"haswikt": 1
},
"am": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1,
"sc": "Ethi"
},
"an": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ang": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"ar": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"alt": 1,
"haswikt": 1,
"sc": "Arab"
},
"arc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": [
"Hebr",
"Syrc"
]
},
"are": {},
"arz": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Arab"
},
"as": {
"haswikt": 1,
"sc": "Beng"
},
"ast": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"av": {
"haswikt": 1
},
"ay": {
"haswikt": 1
},
"az": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl",
"Arab"
]
},
"ba": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"bar": {},
"bat-smg": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"bbl": {
"sc": "Geor"
},
"be": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"be-x-old": {
"sc": "Cyrl"
},
"bg": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"bh": {
"haswikt": 1,
"sc": "Deva"
},
"bhb": {
"sc": "Deva"
},
"bi": {
"haswikt": 1
},
"blt": {
"sc": "Tavt"
},
"bm": {
"haswikt": 1,
"sc": [
"Latn",
"Nkoo",
"Arab"
]
},
"bn": {
"haswikt": 1,
"sc": "Beng"
},
"bo": {
"haswikt": 1,
"sc": "Tibt"
},
"br": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ca": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"cdo": {
"sc": "Hani"
},
"ce": {
"sc": "Cyrl"
},
"ch": {
"haswikt": 1
},
"chr": {
"haswikt": 1,
"sc": "Cher"
},
"cjy": {
"sc": "Hani"
},
"ckb": {
"haswikt": 1,
"sc": "Arab",
"wsc": "ku-Arab"
},
"cmn": {
"sc": "Hani",
"wiktprefix": "zh"
},
"co": {
"haswikt": 1
},
"cpx": {
"sc": "Hani"
},
"cr": {
"haswikt": 1,
"sc": "Cans"
},
"crh": {},
"cs": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"csb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"cu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": [
"Cyrs",
"Glag"
]
},
"cv": {
"sc": "Cyrl"
},
"cy": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"czh": {
"sc": "Hani"
},
"czo": {
"sc": "Hani"
},
"da": {
"g": [
"g",
"nt",
"g-p",
"nt-p",
"p"
],
"haswikt": 1
},
"dax": {},
"de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"dhg": {},
"djb": {},
"dji": {},
"djr": {},
"dng": {
"sc": "Cyrl"
},
"dsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"dsx": {},
"duj": {},
"dv": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Thaa"
},
"dz": {
"haswikt": 1,
"sc": "Tibt"
},
"el": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"en": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"eo": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"es": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"et": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ett": {
"g": [
"y",
"p"
],
"sc": "Ital"
},
"eu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fa": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-cls": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-ira": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fi": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fil": {},
"fj": {
"haswikt": 1
},
"fo": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1
},
"fr": {
"g": [
"n",
"m",
"mnmane",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"frm": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fro": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fur": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fy": {
"g": [
"g",
"nt",
"p"
],
"haswikt": 1
},
"ga": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"gan": {
"sc": "Hani"
},
"gd": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gez": {
"sc": "Ethi"
},
"gl": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gmw-ecg": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gmy": {
"sc": "Linb"
},
"gn": {
"haswikt": 1
},
"gnn": {
"sc": "Latn"
},
"got": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"sc": "Goth"
},
"grc": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
]
},
"gsw": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Gujr"
},
"guf": {
"sc": "Latn"
},
"gv": {
"haswikt": 1
},
"ha": {
"haswikt": 1
},
"hak": {
"sc": "Hani"
},
"har": {
"sc": "Ethi"
},
"he": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"hi": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Deva"
},
"hif": {
"sc": [
"Latn",
"Deva"
]
},
"hit": {
"sc": "Xsux"
},
"hrx": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"hsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"hsn": {
"sc": "Hani"
},
"hu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"hy": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Armn"
},
"ia": {
"haswikt": 1
},
"id": {
"haswikt": 1
},
"ie": {
"haswikt": 1
},
"ik": {
"haswikt": 1
},
"ike": {
"sc": "Cans"
},
"ikt": {
"sc": "Cans"
},
"io": {
"haswikt": 1
},
"is": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"it": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"iu": {
"haswikt": 1,
"sc": "Cans"
},
"ja": {
"haswikt": 1,
"sc": "Jpan"
},
"jay": {
"sc": "Latn"
},
"jbo": {
"haswikt": 1
},
"jv": {
"haswikt": 1
},
"ka": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"khb": {
"sc": [
"Talu",
"Lana",
"Thai"
]
},
"kjh": {
"sc": "Cyrl"
},
"kk": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"kkh": {
"sc": [
"Lana",
"Thai"
]
},
"kl": {
"haswikt": 1
},
"km": {
"haswikt": 1,
"sc": "Khmr"
},
"kn": {
"haswikt": 1,
"sc": "Knda"
},
"ku": {
"g": [
"n",
"m",
"mnmane",
"mndudil",
"n-p",
"m-p",
"p"
],
"wiktprefix": "ku"
},
"ko": {
"haswikt": 1,
"sc": "Kore"
},
"krc": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"ks": {
"haswikt": 1,
"sc": [
"Arab",
"Deva"
],
"wsc": "ks-Arab"
},
"kw": {
"haswikt": 1
},
"ky": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"la": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"lb": {
"g": [
"n",
"m",
"nt",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"lez": {
"sc": "Cyrl"
},
"li": {
"haswikt": 1
},
"lki": {
"wiktprefix": "ku"
},
"lld": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lmo": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ln": {
"haswikt": 1
},
"lo": {
"haswikt": 1,
"sc": "Laoo"
},
"lt": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lv": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lzz": {
"sc": "Geor"
},
"mg": {
"haswikt": 1
},
"mh": {
"haswikt": 1
},
"mi": {
"g": 0,
"haswikt": 1
},
"mk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ml": {
"haswikt": 1,
"sc": "Mlym"
},
"mn": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Cyrl",
"Mong"
]
},
"mnp": {
"sc": "Hani"
},
"mo": {
"haswikt": 1,
"sc": "Cyrl"
},
"mol": {
"sc": "Cyrl"
},
"mr": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1,
"sc": "Deva"
},
"ms": {
"haswikt": 1,
"sc": [
"Latn",
"Arab"
]
},
"mt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"mwp": {
"sc": "Latn"
},
"my": {
"haswikt": 1,
"sc": "Mymr"
},
"na": {
"haswikt": 1
},
"nah": {
"haswikt": 1
},
"nan": {
"wiktprefix": "zh-min-nan",
"sc": "Hani"
},
"nb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"wiktprefix": "no"
},
"nds-de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"nds-nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"ne": {
"haswikt": 1,
"sc": "Deva"
},
"nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nn": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"no": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nod": {
"sc": "Lana"
},
"non": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"oc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"om": {
"haswikt": 1
},
"or": {
"haswikt": 1,
"sc": "Orya"
},
"orv": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrs"
},
"osc": {
"sc": "Ital"
},
"ota": {
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "ota-Arab"
},
"pa": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": [
"Guru",
"Arab"
]
},
"pdt": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"peo": {
"sc": "Xpeo"
},
"phn": {
"sc": "Phnx"
},
"pi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"pjt": {
"sc": "Latn"
},
"pl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"pms": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"pox": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"împf",
"pf"
]
},
"prs": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"ps": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ps-Arab"
},
"pt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"qu": {
"haswikt": 1
},
"rgn": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"rit": {
"sc": "Latn"
},
"rm": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"rn": {
"haswikt": 1
},
"ro": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"roa-rup": {
"haswikt": 1
},
"ru": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"rue": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrl"
},
"ruo": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rup": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "roa-rup"
},
"ruq": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rw": {
"haswikt": 1
},
"sa": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Deva"
},
"sah": {
"sc": "Cyrl"
},
"sc": {
"haswikt": 1
},
"scn": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"sco": {
"sc": "Latn"
},
"sd": {
"haswikt": 1,
"sc": "Arab",
"wsc": "sd-Arab"
},
"sdh": {
"sc": "Arab",
"wiktprefix": "ku"
},
"sg": {
"haswikt": 1
},
"sh": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"si": {
"haswikt": 1,
"sc": "Sinh"
},
"simple": {
"haswikt": 1
},
"sk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sl": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sm": {
"haswikt": 1
},
"sn": {
"haswikt": 1
},
"so": {
"haswikt": 1
},
"spx": {
"sc": "Ital"
},
"sq": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ss": {
"haswikt": 1
},
"st": {
"haswikt": 1
},
"su": {
"haswikt": 1
},
"sux": {
"sc": "Xsux"
},
"sv": {
"g": [
"g",
"nt",
"g-p",
"nt-p"
],
"haswikt": 1
},
"sva": {
"sc": "Geor"
},
"sw": {
"nclass": 1,
"haswikt": 1
},
"syc": {
"sc": "Syrc"
},
"syr": {
"sc": "Syrc"
},
"szl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"ta": {
"haswikt": 1,
"sc": "Taml"
},
"tdd": {
"sc": "Tale"
},
"te": {
"haswikt": 1,
"sc": "Telu"
},
"tg": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Cyrl"
},
"th": {
"haswikt": 1,
"sc": "Thai"
},
"ti": {
"haswikt": 1,
"sc": "Ethi"
},
"tig": {
"sc": "Ethi"
},
"tiw": {
"sc": "Latn"
},
"tk": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tl": {
"haswikt": 1,
"sc": [
"Latn",
"Tglg"
]
},
"tmr": {
"sc": "Hebr"
},
"tn": {
"haswikt": 1
},
"to": {
"haswikt": 1
},
"tpi": {
"haswikt": 1
},
"tr": {
"g": [
"y",
"p"
],
"alt": 1,
"haswikt": 1
},
"ts": {
"haswikt": 1
},
"tt": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tts": {
"sc": "Thai"
},
"tw": {
"haswikt": 1
},
"udi": {
"sc": [
"Cyrl",
"Latn",
"Armn",
"Geor"
]
},
"ug": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ug-Arab"
},
"uga": {
"sc": "Ugar"
},
"uk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ulk": {
"sc": "Latn"
},
"ur": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ur-Arab"
},
"uz": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"vec": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"vi": {
"haswikt": 1
},
"vls": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"vo": {
"haswikt": 1
},
"wa": {
"haswikt": 1
},
"wbp": {
"sc": "Latn"
},
"wo": {
"haswikt": 1
},
"wuu": {
"sc": "Hani"
},
"xae": {
"sc": "Ital"
},
"xcr": {
"sc": "Cari"
},
"xfa": {
"sc": "Ital"
},
"xh": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"xlc": {
"sc": "Lyci"
},
"xld": {
"sc": "Lydi"
},
"xlu": {
"sc": "Xsux"
},
"xmf": {
"sc": "Geor"
},
"xno": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"xrr": {
"sc": "Ital"
},
"xst": {
"sc": "Ethi"
},
"xum": {
"sc": "Ital"
},
"xve": {
"sc": "Ital"
},
"xvo": {
"sc": "Ital"
},
"yi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"yo": {
"haswikt": 1
},
"yua": {
"g": [
"y",
"p"
],
"alt": 1
},
"yue": {
"sc": "Hani"
},
"za": {
"haswikt": 1,
"sc": "Latn"
},
"zh-classical": {
"sc": "Hani"
},
"zh-min-nan": {
"haswikt": 1
},
"zh-yue": {
"sc": "Hani"
},
"zu": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"zza": {
"g": [
"n",
"m",
"mn",
"n-p",
"m-p",
"p"
],
"wiktprefix": "diq"
}
},
"numberLabels": {
"y": {
"display": "yekjimar"
},
"d": {
"display": "hejmar dual"
},
"p": {
"display": "pirjimar"
},
"p+": {
"display": "axlebî pirjimar"
},
"tp": {
"display": "tenê pirjimar"
},
"j": {
"display": "jimarbar"
},
"j+": {
"display": "axlebî jimarbar"
},
"j/nj": {
"display": "jimarbar û nejimarbar"
},
"nj": {
"display": "nejimarbar"
},
"nj+": {
"display": "axlebî nejimarbar"
},
"hj!": {
"display": "hejmar nepeyitandî ye"
},
"hj?": {
"display": "hejmar hewce ye"
}
},
"leadingTplOrder": [
"maneid",
"K",
"e"
],
"lekturChoices": [
{
"code": "gh",
"label": "gerguhêz (îsmî mefûl digire)"
},
{
"code": "ngh",
"label": "negerguhêz (îsmî mefûl nagire)"
}
],
"lekturFormChoices": [
{
"code": "x",
"label": "xwerû"
},
{
"code": "p",
"label": "pêkhatî"
},
{
"code": "h",
"label": "hevedudanî"
}
],
"deverQuickPicks": [
"kurmanciya rojavayî",
"kurmanciya merkezî",
"kurmanciya rojhilatî",
"Badînî",
"Botî",
"Serhedî",
"Bakur",
"Amed",
"Riha"
],
"maneTags": [
"cure?",
"rêziman?",
"kat-rêziman",
"kategoriya rêzimanê?",
"bêcure"
],
"senseGroups": [
{
"key": "alt-r",
"label": "Formên alternatîv",
"field": "alternativ",
"order": 3,
"hint": "Varyantên vê kelîmeyê"
},
{
"key": "hevmane",
"label": "Hevmane",
"field": "hevmane",
"order": 1,
"hint": "Spehî hevmaneya Bedew e."
},
{
"key": "dijmane",
"label": "Dijmane",
"field": "dijmane",
"order": 2,
"hint": "Zengîn dijmaneya feqîr e."
},
{
"key": "koordîne",
"label": "Termên koordîne",
"field": "koordine",
"order": 4,
"hint": "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in"
},
{
"key": "jornav",
"label": "Jornav",
"field": "jornav",
"order": 5,
"hint": "Heywan jornavê guhandar e"
},
{
"key": "jêrnav",
"label": "Jêrnav",
"field": "jernav",
"order": 6,
"hint": "Guhandar jêrnavê heywan e"
}
],
"subsectionOrder": [
"Bilêvkirin",
"Ji wêjeya klasîk",
"Ji wêjeyê",
"Tewandin",
"Notên bikaranînê",
"Bi alfabeyên din",
"Formên alternatîv",
"Kurtkirin",
"Sembol",
"Navê zanistî",
"Hevmane",
"Nêzîk",
"Dijmane",
"Jornav",
"Jêrnav",
"Jê",
"Têkildar",
"Etîmolojî",
"Dûnde",
"Bide ber",
"Herwiha binêre",
"Bêhtir bixwîne",
"Bi zaravayên din",
"Werger",
"Qafiye",
"Anagram",
"Çavkanî"
],
"templateAliases": {
"K": [
"K",
"kategorîzebike",
"mijar"
],
"alt-r": [
"alt-r",
"forma alt di rêzê de",
"herwiha-rêz",
"herwiha di rêza maneyê de"
],
"dijmane": [
"dijmane",
"dijwate",
"dijmaneyên peyvê"
],
"e": [
"e",
"etîket",
"f",
"lb"
],
"g": [
"g",
"girêdan",
"l",
"link"
],
"hevmane": [
"hevmane",
"hevwate",
"hevmaneya peyvê",
"hevmaneyên peyvê",
"synonyms"
],
"jornav": [
"jornav",
"hîpernîm"
],
"jêrnav": [
"jêrnav",
"hîponîm"
],
"koordîne": [
"koordîne",
"termên koordîne",
"peyvên koordîneyî"
],
"mane?": [
"mane?",
"wate?",
"bêmane",
"bêwate"
],
"maneid": [
"maneid",
"senseid"
],
"h-bnr": [
"h-bnr",
"bnr-h",
"bnr h",
"bnr-hrw",
"bnr herwiha",
"herwiha binêre",
"binêre herwiha"
]
},
"wordlistTemplates": [
"kol3",
"kol2",
"kol",
"col3",
"col2",
"col"
],
"templateParams": {
"deyn": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"sc",
"id"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya kelîmeyê",
"tr": "Transliterasyon",
"ts": "Transkîpsiyon",
"sc": "Koda skrîptê",
"id": "IDya maneyê"
}
},
"hv": {
"order": [
"1",
"2",
"3",
"4",
"tr",
"ts",
"sc"
],
"labels": {
"1": "Koda zimanî",
"2": "Kelîme",
"3": "Xuyakirina alternatîv",
"4": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"sc": "Koda skrîptê"
}
},
"ji": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"id",
"sc"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"id": "IDya maneyê",
"sc": "Koda skrîptê"
}
},
"jêder-kitêb": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"t",
"nivîskar",
"nivîskar2",
"nivîskar3",
"amadekar",
"terciman",
"sernav",
"url",
"weşanxane",
"cih",
"cild",
"sal",
"sala-weşandinê",
"çap",
"rûpel",
"isbn",
"nekat",
"nivîskar4",
"nivîskar5",
"issn",
"oclc",
"serî",
"doi"
],
"labels": {
"1": "koda zimanî",
"sc": "koda skrîptê eger hewce be",
"tr": "translîterasyon",
"t": "tercime",
"rûpel": "rûpel"
}
},
"jêder-kovar": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"werger",
"kovar",
"sernav",
"wergêr",
"nivîskar",
"nivîskar2",
"url",
"weşanxane",
"cîld",
"hejmar",
"sal",
"tarîx",
"rûpel"
],
"labels": {
"1": "Koda zimanî",
"sc": "Koda skrîptê",
"tr": "Translîterasyon",
"werger": "Tercimeya kurmancî ya jêgirtinê"
}
},
"jêder-vîdeo": {
"order": [
"1",
"title",
"year",
"date",
"author",
"authorlink",
"actor",
"role",
"time",
"at",
"medium",
"network",
"text",
"translation",
"season",
"number",
"episode",
"publisher",
"url",
"director",
"brackets"
],
"labels": {
"1": "Language",
"title": "Title",
"year": "Year of release",
"date": "Date of release",
"author": "Writer (screenplay)",
"authorlink": "Wikipedia author page",
"actor": "Actor (speaker)",
"role": "Role",
"time": "Timecode",
"at": "Context",
"medium": "Recording medium",
"network": "Network",
"text": "Quoted line",
"translation": "Translation",
"season": "Season number (series)",
"number": "Episode number (series)",
"episode": "Episode title (series)",
"publisher": "Publisher",
"url": "URL",
"director": "Director",
"brackets": "Brackets"
}
},
"jêder-websîte": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"t",
"nivîskar",
"terciman",
"sernav",
"url",
"urlya-arşîvê",
"websîte",
"tarîx",
"sal"
],
"labels": {
"1": "Koda zimanî",
"sc": "Koda skrîptê ya jêgirtinê",
"tr": "Translîterasyona jêgirtinê",
"t": "Tercimeya kurmancî ya jêgirtinê"
}
},
"mîr": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"id",
"sc"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"id": "IDya maneyê",
"sc": "Koda skrîptê"
}
},
"nh": {
"order": [
"1",
"2",
"3",
"4",
"tr",
"ts",
"sc"
],
"labels": {
"1": "Koda zimanî",
"2": "Kelîme",
"3": "Xuyakirina alternatîv",
"4": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"sc": "Koda skrîptê"
}
},
"qertaf": {
"order": [
"1",
"2",
"3",
"sc",
"cuda1",
"cuda2",
"t1",
"t2",
"tr1",
"tr2"
],
"labels": {
"1": "language code",
"2": "first part",
"3": "second part",
"sc": "script code",
"cuda1": "first part alternate text",
"cuda2": "second part alternate text",
"w1": "first gloss",
"w2": "second gloss",
"tr1": "first part transliteration",
"tr2": "second part transliteration"
}
},
"yekser": {
"order": [
"1",
"2",
"3",
"hûr",
"nekat"
]
}
}
}
jt5upg01dmfm15qyosu9buor4phdety
6865600
6865576
2026-10-23T21:01:01Z
Wikihez
7344
Etimon keywords: qertaf, dariştî, drşnd, ji, mîrasmayî, deyn first
6865600
json
application/json
{
"langChoices": {
"ku": "kurmancî",
"zza": "zazakî",
"ckb": "soranî",
"sdh": "kurdiya başûrî",
"hac": "hewramî",
"de": "almanî",
"ar": "erebî",
"fa": "farisî",
"fr": "fransî",
"fi": "fînî",
"en": "îngilîzî",
"ja": "japonî",
"sv": "swêdî",
"tr": "tirkî"
},
"posExamples": {
"navdêr": "Kelîmeyên ji bo tiştan (sêv, heywan, qedeh) yan terman (dilşadî, bextiyarî hwd.) di rêzimanê de.",
"lêker": "Kelîmeyên ji bo kirin an bûyînê wekî 'nivîsîn' yan 'xeber dan'.",
"rengdêr": "Kelîmeyên ku wesfa navdêran nîşan didin wekî 'sêva sor' yan 'xwarina şîrîn'.",
"hoker": "Kelîmeyên ku dem, cih yan awayê kirinê diyar dikin wekî 'bi vî awayî' yan 'demkî'.",
"daçek": "Kelîmeyên ku cih, dem yan hereketê diyar dikin wekî 'di.. de' yan 'ji'.",
"cînav": "Kelîmeyên ku li şûna navdêran tên bikaranîn daku navdêr neyên dubarekirin wekî 'ew' yan 'em'.",
"baneşan": "Kelîmeyên ku hest, deng hwd. îfade dikin wekî 'noşî!' yan 'aferîn'.",
"serenav": "Navdêrên ku şexsiyetên ferdî nîşan didin, wek navên kes, cih an organîzasyonan nimûne 'London' yan 'Diyarbekir'.",
"gotineke pêşiyan": "Gotinên ku di nav xelkê de bi berbelavî tên bikaranîn bo nimûne 'Serê neêşe ne hewceyî destmalê ye'."
},
"genderLabels": {
"m": "mê",
"n": "nêr",
"nt": "nêtar",
"mn": "mê yan nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"mndudil": "mê yan nêr bikaranîn dudil e",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": {
"rastnivîs": {
"langs": [
"ku"
],
"noLang": true
},
"ku-Latn": {
"langs": [
"ku"
],
"noLang": true
},
"ku-tewîna-nav": {
"langs": [
"ku"
],
"noLang": true
},
"sdh-Arab": {
"langs": [
"sdh"
],
"noLang": true
},
"hac-Arab": {
"langs": [
"hac"
],
"noLang": true
},
"ja-romanîzekirin": {
"langs": [
"ja"
],
"noLang": true
},
"ckb-Arab": {
"langs": [
"ckb"
],
"noLang": true
},
"zza-awayê-Jacobsonê": {
"langs": [
"zza"
]
},
"awayekî din": {
"langs": null
},
"guharto": {
"langs": null
},
"hevmaneya": {
"langs": null
},
"kevnbûyî": {
"langs": null
},
"kêmpeyda": {
"langs": null
},
"kurtkirin": {
"langs": null
},
"paşnav": {
"langs": null
},
"şaşnivîs": {
"langs": null
}
},
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 3
},
{
"key": "terciman",
"label": "Terciman",
"span": 3
},
{
"key": "amadekar",
"label": "Amadekar",
"span": 3
},
{
"key": "weşanxane",
"label": "Weşanxane",
"span": 3
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "beş",
"label": "Beş",
"span": 3,
"width": "medium"
},
{
"key": "cih",
"label": "Cih",
"span": 2,
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"span": 1,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
},
{
"key": "sala-weşandinê",
"label": "Sala weşandinê",
"span": 2,
"width": "narrow"
},
{
"key": "çap",
"label": "Çap",
"span": 1,
"width": "narrow"
},
{
"key": "cild",
"label": "Cild",
"span": 2,
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"span": 1,
"width": "narrow"
},
{
"key": "isbn",
"label": "ISBN",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 3
},
{
"key": "terciman",
"label": "Terciman",
"span": 3
},
{
"key": "amadekar",
"label": "Amadekar",
"span": 3
},
{
"key": "weşanxane",
"label": "Weşanxane",
"span": 3
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "kovar",
"label": "Kovar",
"span": 4
},
{
"key": "cild",
"label": "Cild",
"span": 2,
"width": "narrow"
},
{
"key": "hejmar",
"label": "Hejmar",
"span": 2,
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"span": 2,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
},
{
"key": "rûpel",
"label": "Rûpel",
"span": 2,
"width": "narrow"
},
{
"key": "issn",
"label": "ISSN",
"span": 2,
"width": "narrow"
},
{
"key": "doi",
"label": "DOI",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 4
},
{
"key": "terciman",
"label": "Terciman",
"span": 4
},
{
"key": "websîte",
"label": "Websîte",
"span": 4
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "url",
"label": "URL",
"span": 5
},
{
"key": "urlya-arşîvê",
"label": "URLya Arşîvê",
"span": 5
},
{
"key": "roja-arşîvê",
"label": "Roja arşîvê",
"span": 3,
"width": "medium"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
},
{
"key": "sal",
"label": "Sal",
"span": 1,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
}
]
},
"jêder-vîdeo": {
"fields": [
{
"key": "hunermend",
"label": "Hunermend",
"span": 6
},
{
"key": "platform",
"label": "Platform",
"span": 6
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "sal",
"label": "Sal",
"span": 2,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3
},
{
"key": "dem",
"label": "Dem",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
],
"label": "Vîdeo"
}
},
"pronTpls": {
"ku-IPA": {
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
"ku-kîte": {
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
"zza-IPA": {
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
"zza-kîte": {
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
"IPA": {
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
"deng": {
"desc": "Nimûneya telafûzê bi dosyeya dengî",
"snippet": "* {{deng|CODE|navê_dosyayê.ogg|dever=}}"
},
"bilêv": {
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
"kîte": {
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
"hevdeng": {
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
},
"etymTpls": {
"etîmon": {
"desc": "Etîmolojiya birêkûpêk: zincîr, dar û kategorî — {{etîmon|ku|:deyn|fa:پنجه\u003Ct:dest\u003E}}",
"snippet": "{{etîmon|CODE}}"
},
"ji": {
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
"deyn": {
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
"mîr": {
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
"qertaf": {
"desc": "Ji bo pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
"anlz": {
"desc": "Wekî qertaf lê dibêje analîza şiklî — {{anlz|kod|pêşgir-|reh}}",
"snippet": "{{anlz|CODE|…|…}}"
},
"q": {
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
"hv": {
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
"nh": {
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
"cotreh": {
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
"yekser": {
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
"wergirtin": {
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
"paşvesazî": {
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
"qusandin": {
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
"têkilkirin": {
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
"nyz": {
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
"onom": {
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
"navmiletî": {
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
"neo": {
"desc": "neolojîzm — {{neolojîzm|ku|hûr=1}}",
"snippet": "{{neolojîzm|CODE}}"
}
},
"etimon": {
"keywords": [
{
"key": "qertaf",
"desc": "Kelîmeya ji qertafan an ji du kelîmeyan pêk hatî; rêza parçeyan girîng e",
"short": "ji qertafan an du kelîmeyan",
"aliases": [
"qf",
"af"
]
},
{
"key": "dariştî",
"desc": "Kelîmeya ku ji zimanekî din hatiye dariştin, wextê ne mîrasmayî û ne jî deynkirineke diyar be",
"short": "dariştin ji zimanekî din",
"aliases": [
"drş",
"der"
]
},
{
"key": "drşnd",
"desc": "Dariştin heye lê cureyê wê ne diyar e",
"short": "dariştin, cure ne diyar",
"aliases": [
"uder"
]
},
{
"key": "ji",
"desc": "Dariştineke di nav eynî zimanî de ku cureyê wê nehatiye diyarkirin; tenê ji bo kelîmeyên eynî zimanî derbasdar e",
"short": "dariştin di eynî zimanî de",
"aliases": [
"from"
]
},
{
"key": "mîrasmayî",
"desc": "Kelîmeya ku ji zimanê bav û kalan maye",
"aliases": [
"mîr",
"inh"
]
},
{
"key": "deyn",
"desc": "Kelîmeya ku rasterast ji zimanekî din hatiye deynkirin",
"aliases": [
"bor",
"borrowed"
]
},
{
"key": "deynh",
"desc": "Deynkirina hînbûyî: ne bi riya têkiliya rojane, bi qesd hatiye girtin",
"short": "deynkirina hînbûyî",
"aliases": [
"lbor"
]
},
{
"key": "deynhq",
"desc": "Deynkirina hînbûyî ya ku hinekî hatiye guhertin",
"short": "deynkirina hînbûyî ya qismî",
"aliases": [
"slbor"
]
},
{
"key": "deynort",
"desc": "Deynkirina ortografîk",
"short": "deynkirina ortografîk",
"aliases": [
"obor"
]
},
{
"key": "deynne",
"desc": "Deynkirina neadaptekirî",
"short": "deynkirina neadaptekirî",
"aliases": [
"ubor"
]
},
{
"key": "reh",
"desc": "Kelîmeyên pey xwe wek reh dide nasîn; tenê ji bo kategoriyan e (\"Peyvên … yên aîdî rehê …\") û di metn û darê de xuya nabe",
"short": "reh, tenê kategorî",
"aliases": [
"root"
],
"invisible": true
},
{
"key": "yekser",
"desc": "Wergerandina yekser; zincîr li vir disekine",
"short": "wergerandina yekser",
"aliases": [
"cal",
"clq"
]
},
{
"key": "yekserqismî",
"desc": "Wergerandina yekser a qismî; zincîr li vir disekine",
"short": "wergerandina yekser a qismî",
"aliases": [
"yekserq",
"pcal"
]
},
{
"key": "wergirtin",
"desc": "Wergirtina maneyê; mana wergirtî bi \u003Cmaneid:…\u003E tê nîşankirin",
"short": "wergirtina maneyê",
"aliases": [
"sl"
]
},
{
"key": "lihevanîn",
"desc": "Lihevanîna dengî-maneyî",
"aliases": [
"psm"
]
},
{
"key": "tesîr",
"desc": "Kelîme di bin tesîra kelîmeyeke din de guheriye; zincîr li vir disekine",
"short": "di bin tesîra kelîmeyekê",
"aliases": [
"influence"
]
},
{
"key": "têkilkirin",
"desc": "Têkilkirina du kelîmeyan",
"aliases": [
"blend"
],
"min": 2
},
{
"key": "ûnîverbasyon",
"desc": "Ûnîverbasyon",
"aliases": [
"ûnîv",
"univ"
]
},
{
"key": "qusandin",
"desc": "Qusandin",
"aliases": [
"clip"
]
},
{
"key": "paşvesazî",
"desc": "Paşvesazî",
"aliases": [
"bf"
]
},
{
"key": "elîpsîz",
"desc": "Elîpsîz",
"aliases": [
"elîp",
"ellip"
]
},
{
"key": "nomînalîzasyon",
"desc": "Nomînalîzasyon",
"aliases": [
"nomîn",
"nom"
]
},
{
"key": "translîterasyon",
"desc": "Translîterasyon",
"aliases": [
"translît",
"translit"
]
},
{
"key": "redûblîkasyon",
"desc": "Redûblîkasyon",
"aliases": [
"redûb",
"redup"
]
},
{
"key": "kurtkirin",
"desc": "Kurtkirina kelîmeyekê",
"aliases": [
"abbr"
]
},
{
"key": "sylabbr",
"desc": "Kurtkirin li gorî kîteyan",
"short": "kurtkirin bi kîteyan",
"aliases": [
"syllabic abbreviation",
"sylabbrev"
]
},
{
"key": "akronîm",
"desc": "Akronîm",
"aliases": [
"acro"
]
},
{
"key": "înîtîalîzm",
"desc": "Înîtîalîzm",
"aliases": [
"înît",
"init"
]
},
{
"key": "metatez",
"desc": "Metatez",
"aliases": [
"meta"
]
},
{
"key": "afeq",
"desc": "Wek :qf, lê ne di metnê ne jî di darê de xuya dibe; tenê kategoriyan çêdike",
"short": "qertaf, tenê kategorî",
"invisible": true
},
{
"key": "apheresis",
"desc": "Şiklê ku dengê destpêkê winda kiriye",
"short": "dengê destpêkê winda",
"aliases": [
"apheretic",
"aphetic"
]
},
{
"key": "denominal",
"desc": "Fiîla ji îsmekê çêbûyî",
"short": "fiîl ji îsmekê",
"aliases": [
"denom"
]
},
{
"key": "deverbal",
"desc": "Kelîmeya ji fiîlekê çêbûyî",
"short": "kelîme ji fiîlekê"
},
{
"key": "vrd",
"desc": "Dariştina vṛddhî (sanskrîtî); bi qertaf: :vrd-af",
"short": "dariştina vṛddhî"
},
{
"key": "sa-af",
"desc": "Pêkhateya sanskrîtî",
"short": "pêkhateya sanskrîtî",
"min": 2
}
],
"wordMods": [
{
"key": "t",
"desc": "Tercimeya kelîmeyê bi kurmancî",
"aliases": [
"w"
]
},
{
"key": "tr",
"desc": "Translîterasyon"
},
{
"key": "ts",
"desc": "Transkrîpsiyon"
},
{
"key": "cuda",
"desc": "Şiklê ku tê nîşandan, eger ne wek navê rûpelê be",
"aliases": [
"alt"
]
},
{
"key": "id",
"desc": "Nasnameya etîmolojiya wê kelîmeyê li ser rûpela wê; eger mane tune be, wek mane tê nîşandan"
},
{
"key": "crz",
"desc": "Cureyê rêzimanî yê kelîmeyê",
"aliases": [
"pos"
],
"values": [
"reh",
"kelîme",
"pêşgir",
"paşgir",
"navgir"
]
},
{
"key": "c",
"desc": "Cinsiyet: n (nêr), m (mê), nt (nêtar)",
"values": [
"n",
"m",
"nt"
],
"aliases": [
"z",
"g"
]
},
{
"key": "q",
"desc": "Wesf li pêşiyê kelîmeyê"
},
{
"key": "qq",
"desc": "Wesf li pey kelîmeyê"
},
{
"key": "hrf",
"desc": "Mana herfiyen",
"aliases": [
"lit"
]
},
{
"key": "ref",
"desc": "Çavkanî; binêre #Çavkanî"
},
{
"key": "nediyar",
"desc": "Ev gav ne teqez e",
"flag": true,
"aliases": [
"unc"
]
},
{
"key": "crztip",
"desc": "Kelîme ji bo kategoriyan wek reh yan kelîme tê hesibandin",
"aliases": [
"postype"
],
"values": [
"reh",
"kelîme"
]
},
{
"key": "etimon",
"desc": "Etîmolojiya di cih de; binêre jor",
"aliases": [
"ety"
]
},
{
"key": "aftype",
"desc": "Di nav :qf de: cureyê qertafê; binêre #Cureyên qertafan",
"values": [
"pêşgir",
"paşgir",
"navgir",
"circumfix",
"infix",
"non-affix",
"root"
],
"only": [
"qertaf"
]
},
{
"key": "deyn",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "deynh",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "deynhq",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
}
],
"keywordMods": [
{
"key": "nediyar",
"desc": "Ev gav ne teqez e: \"Mihtemelen…\"",
"flag": true,
"aliases": [
"unc"
]
},
{
"key": "grdk",
"desc": "Girêdeka di navbera kelîmeyên jêder de, li şûna \"yan jî\"",
"aliases": [
"conj"
]
},
{
"key": "anlz",
"desc": "Analîza şiklî",
"flag": true,
"aliases": [
"surf"
]
},
{
"key": "neo",
"desc": "Di nav :qf de: neolojîzm, \"Neolojîzm ji…\", di darê de \"neo.\"",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "hrf",
"desc": "Di nav :qf de: mana herfiyen",
"aliases": [
"lit"
],
"only": [
"qertaf"
]
},
{
"key": "maneid",
"desc": "Di nav :wergirtin de: nasnameya maneya wergirtî",
"aliases": [
"senseid"
],
"only": [
"wergirtin"
]
},
{
"key": "metn",
"desc": "Di nav :ji û :drş de: metnekî din li şûna \"Ji\"",
"aliases": [
"text"
],
"only": [
"ji",
"dariştî"
]
},
{
"key": "ref",
"desc": "Çavkaniya vê gavê"
}
],
"metnChoices": [
{
"value": "++",
"desc": "Tevahiya zincîrê"
},
{
"value": "+",
"desc": "Tenê gava pêşîn"
},
{
"value": "*",
"desc": "Heta kelîmeya pêşîn a ku rûpela wê heye"
},
{
"value": "1",
"desc": "Ewqas gav (hejmarek)",
"free": true
},
{
"value": ":",
"desc": "Heta zimanê bi wê kodê (:kod)",
"free": true
},
{
"value": ":*",
"desc": "Heta wî zimanî, eger rûpela wî tune be heta ya pêşîn a heyî (:kod*)",
"free": true
}
],
"afTypes": [
{
"key": "pêşgir",
"desc": "Pêşgir"
},
{
"key": "paşgir",
"desc": "Paşgir"
},
{
"key": "navgir",
"desc": "Navgir"
},
{
"key": "circumfix",
"desc": "Dorgir"
},
{
"key": "infix",
"desc": "Nawgir (infix)"
},
{
"key": "non-affix",
"desc": "Ne qertaf"
},
{
"key": "root",
"desc": "Reh"
}
],
"paramAliases": {
"text": "metn",
"notext": "metnna",
"pos": "crz",
"doublet": "cotreh",
"etydate": "tarîxa-etîm",
"nodot": "niqtena",
"dot": "niqte",
"nocat": "katna",
"nekat": "katna"
},
"params": [
{
"key": "darna",
"desc": "",
"type": "select",
"values": [
{
"value": "1",
"label": "1 - dara etîmolojiyê li vê rûpelê nayê nîşandan"
},
{
"value": "0",
"label": "0 - ev etîmolojî di dara rûpelên din de dernakeve"
}
]
},
{
"key": "id",
"desc": "Nasnameya vê etîmolojiyê. Eger mane nehatibe dayîn, wek mane jî tê nîşandan.",
"type": "text",
"primary": true
},
{
"key": "title",
"desc": "Kelîmeya ku etîmolojî jê re tê nivîsîn, eger ne navê rûpelê be.",
"type": "text"
},
{
"key": "crz",
"desc": "Cureyê rêzimanî yê kelîmeyê ji bo kategoriyan: reh, kelîme, pêşgir, paşgir, navgir.",
"type": "select",
"values": [
"reh",
"kelîme",
"pêşgir",
"paşgir",
"navgir"
]
},
{
"key": "cotreh",
"desc": "Cotrehên vê kelîmeyê, bi bêhnokê ji hev cuda.",
"type": "text"
},
{
"key": "tarîxa-etîm",
"desc": "Tarîxa peyitandina pêşîn, wek 1901, 1500s, 14c, c,1900, before,1850, r,1501,1558.",
"type": "text"
},
{
"key": "niqtena",
"desc": "Bi 1 niqteya dawiyê nayê zêdekirin.",
"type": "flag"
},
{
"key": "niqte",
"desc": "Li şûna niqteya dawiyê nîşaneke din.",
"type": "text"
},
{
"key": "katna",
"desc": "Bi 1 kategorî nayên zêdekirin. Herwiha {{para|nekat}}.",
"type": "flag"
},
{
"key": "exnihilo",
"desc": "Kelîmeya ku ji tu kelîmeyeke din nehatiye çêkirin.",
"type": "flag"
}
]
},
"metadata": {
"aa": {
"haswikt": 1,
"sc": [
"Latn",
"Ethi"
]
},
"ab": {
"haswikt": 1,
"sc": [
"Cyrl",
"Latn",
"Geor"
]
},
"aer": {
"sc": "Latn"
},
"af": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ak": {
"haswikt": 1
},
"akk": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Xsux"
},
"als": {
"haswikt": 1
},
"am": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1,
"sc": "Ethi"
},
"an": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ang": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"ar": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"alt": 1,
"haswikt": 1,
"sc": "Arab"
},
"arc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": [
"Hebr",
"Syrc"
]
},
"are": {},
"arz": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Arab"
},
"as": {
"haswikt": 1,
"sc": "Beng"
},
"ast": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"av": {
"haswikt": 1
},
"ay": {
"haswikt": 1
},
"az": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl",
"Arab"
]
},
"ba": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"bar": {},
"bat-smg": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"bbl": {
"sc": "Geor"
},
"be": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"be-x-old": {
"sc": "Cyrl"
},
"bg": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"bh": {
"haswikt": 1,
"sc": "Deva"
},
"bhb": {
"sc": "Deva"
},
"bi": {
"haswikt": 1
},
"blt": {
"sc": "Tavt"
},
"bm": {
"haswikt": 1,
"sc": [
"Latn",
"Nkoo",
"Arab"
]
},
"bn": {
"haswikt": 1,
"sc": "Beng"
},
"bo": {
"haswikt": 1,
"sc": "Tibt"
},
"br": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ca": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"cdo": {
"sc": "Hani"
},
"ce": {
"sc": "Cyrl"
},
"ch": {
"haswikt": 1
},
"chr": {
"haswikt": 1,
"sc": "Cher"
},
"cjy": {
"sc": "Hani"
},
"ckb": {
"haswikt": 1,
"sc": "Arab",
"wsc": "ku-Arab"
},
"cmn": {
"sc": "Hani",
"wiktprefix": "zh"
},
"co": {
"haswikt": 1
},
"cpx": {
"sc": "Hani"
},
"cr": {
"haswikt": 1,
"sc": "Cans"
},
"crh": {},
"cs": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"csb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"cu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": [
"Cyrs",
"Glag"
]
},
"cv": {
"sc": "Cyrl"
},
"cy": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"czh": {
"sc": "Hani"
},
"czo": {
"sc": "Hani"
},
"da": {
"g": [
"g",
"nt",
"g-p",
"nt-p",
"p"
],
"haswikt": 1
},
"dax": {},
"de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"dhg": {},
"djb": {},
"dji": {},
"djr": {},
"dng": {
"sc": "Cyrl"
},
"dsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"dsx": {},
"duj": {},
"dv": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Thaa"
},
"dz": {
"haswikt": 1,
"sc": "Tibt"
},
"el": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"en": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"eo": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"es": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"et": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ett": {
"g": [
"y",
"p"
],
"sc": "Ital"
},
"eu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fa": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-cls": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-ira": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fi": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fil": {},
"fj": {
"haswikt": 1
},
"fo": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1
},
"fr": {
"g": [
"n",
"m",
"mnmane",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"frm": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fro": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fur": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fy": {
"g": [
"g",
"nt",
"p"
],
"haswikt": 1
},
"ga": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"gan": {
"sc": "Hani"
},
"gd": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gez": {
"sc": "Ethi"
},
"gl": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gmw-ecg": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gmy": {
"sc": "Linb"
},
"gn": {
"haswikt": 1
},
"gnn": {
"sc": "Latn"
},
"got": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"sc": "Goth"
},
"grc": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
]
},
"gsw": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Gujr"
},
"guf": {
"sc": "Latn"
},
"gv": {
"haswikt": 1
},
"ha": {
"haswikt": 1
},
"hak": {
"sc": "Hani"
},
"har": {
"sc": "Ethi"
},
"he": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"hi": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Deva"
},
"hif": {
"sc": [
"Latn",
"Deva"
]
},
"hit": {
"sc": "Xsux"
},
"hrx": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"hsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"hsn": {
"sc": "Hani"
},
"hu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"hy": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Armn"
},
"ia": {
"haswikt": 1
},
"id": {
"haswikt": 1
},
"ie": {
"haswikt": 1
},
"ik": {
"haswikt": 1
},
"ike": {
"sc": "Cans"
},
"ikt": {
"sc": "Cans"
},
"io": {
"haswikt": 1
},
"is": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"it": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"iu": {
"haswikt": 1,
"sc": "Cans"
},
"ja": {
"haswikt": 1,
"sc": "Jpan"
},
"jay": {
"sc": "Latn"
},
"jbo": {
"haswikt": 1
},
"jv": {
"haswikt": 1
},
"ka": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"khb": {
"sc": [
"Talu",
"Lana",
"Thai"
]
},
"kjh": {
"sc": "Cyrl"
},
"kk": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"kkh": {
"sc": [
"Lana",
"Thai"
]
},
"kl": {
"haswikt": 1
},
"km": {
"haswikt": 1,
"sc": "Khmr"
},
"kn": {
"haswikt": 1,
"sc": "Knda"
},
"ku": {
"g": [
"n",
"m",
"mnmane",
"mndudil",
"n-p",
"m-p",
"p"
],
"wiktprefix": "ku"
},
"ko": {
"haswikt": 1,
"sc": "Kore"
},
"krc": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"ks": {
"haswikt": 1,
"sc": [
"Arab",
"Deva"
],
"wsc": "ks-Arab"
},
"kw": {
"haswikt": 1
},
"ky": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"la": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"lb": {
"g": [
"n",
"m",
"nt",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"lez": {
"sc": "Cyrl"
},
"li": {
"haswikt": 1
},
"lki": {
"wiktprefix": "ku"
},
"lld": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lmo": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ln": {
"haswikt": 1
},
"lo": {
"haswikt": 1,
"sc": "Laoo"
},
"lt": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lv": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lzz": {
"sc": "Geor"
},
"mg": {
"haswikt": 1
},
"mh": {
"haswikt": 1
},
"mi": {
"g": 0,
"haswikt": 1
},
"mk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ml": {
"haswikt": 1,
"sc": "Mlym"
},
"mn": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Cyrl",
"Mong"
]
},
"mnp": {
"sc": "Hani"
},
"mo": {
"haswikt": 1,
"sc": "Cyrl"
},
"mol": {
"sc": "Cyrl"
},
"mr": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1,
"sc": "Deva"
},
"ms": {
"haswikt": 1,
"sc": [
"Latn",
"Arab"
]
},
"mt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"mwp": {
"sc": "Latn"
},
"my": {
"haswikt": 1,
"sc": "Mymr"
},
"na": {
"haswikt": 1
},
"nah": {
"haswikt": 1
},
"nan": {
"wiktprefix": "zh-min-nan",
"sc": "Hani"
},
"nb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"wiktprefix": "no"
},
"nds-de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"nds-nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"ne": {
"haswikt": 1,
"sc": "Deva"
},
"nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nn": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"no": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nod": {
"sc": "Lana"
},
"non": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"oc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"om": {
"haswikt": 1
},
"or": {
"haswikt": 1,
"sc": "Orya"
},
"orv": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrs"
},
"osc": {
"sc": "Ital"
},
"ota": {
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "ota-Arab"
},
"pa": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": [
"Guru",
"Arab"
]
},
"pdt": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"peo": {
"sc": "Xpeo"
},
"phn": {
"sc": "Phnx"
},
"pi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"pjt": {
"sc": "Latn"
},
"pl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"pms": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"pox": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"împf",
"pf"
]
},
"prs": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"ps": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ps-Arab"
},
"pt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"qu": {
"haswikt": 1
},
"rgn": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"rit": {
"sc": "Latn"
},
"rm": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"rn": {
"haswikt": 1
},
"ro": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"roa-rup": {
"haswikt": 1
},
"ru": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"rue": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrl"
},
"ruo": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rup": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "roa-rup"
},
"ruq": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rw": {
"haswikt": 1
},
"sa": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Deva"
},
"sah": {
"sc": "Cyrl"
},
"sc": {
"haswikt": 1
},
"scn": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"sco": {
"sc": "Latn"
},
"sd": {
"haswikt": 1,
"sc": "Arab",
"wsc": "sd-Arab"
},
"sdh": {
"sc": "Arab",
"wiktprefix": "ku"
},
"sg": {
"haswikt": 1
},
"sh": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"si": {
"haswikt": 1,
"sc": "Sinh"
},
"simple": {
"haswikt": 1
},
"sk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sl": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sm": {
"haswikt": 1
},
"sn": {
"haswikt": 1
},
"so": {
"haswikt": 1
},
"spx": {
"sc": "Ital"
},
"sq": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ss": {
"haswikt": 1
},
"st": {
"haswikt": 1
},
"su": {
"haswikt": 1
},
"sux": {
"sc": "Xsux"
},
"sv": {
"g": [
"g",
"nt",
"g-p",
"nt-p"
],
"haswikt": 1
},
"sva": {
"sc": "Geor"
},
"sw": {
"nclass": 1,
"haswikt": 1
},
"syc": {
"sc": "Syrc"
},
"syr": {
"sc": "Syrc"
},
"szl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"ta": {
"haswikt": 1,
"sc": "Taml"
},
"tdd": {
"sc": "Tale"
},
"te": {
"haswikt": 1,
"sc": "Telu"
},
"tg": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Cyrl"
},
"th": {
"haswikt": 1,
"sc": "Thai"
},
"ti": {
"haswikt": 1,
"sc": "Ethi"
},
"tig": {
"sc": "Ethi"
},
"tiw": {
"sc": "Latn"
},
"tk": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tl": {
"haswikt": 1,
"sc": [
"Latn",
"Tglg"
]
},
"tmr": {
"sc": "Hebr"
},
"tn": {
"haswikt": 1
},
"to": {
"haswikt": 1
},
"tpi": {
"haswikt": 1
},
"tr": {
"g": [
"y",
"p"
],
"alt": 1,
"haswikt": 1
},
"ts": {
"haswikt": 1
},
"tt": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tts": {
"sc": "Thai"
},
"tw": {
"haswikt": 1
},
"udi": {
"sc": [
"Cyrl",
"Latn",
"Armn",
"Geor"
]
},
"ug": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ug-Arab"
},
"uga": {
"sc": "Ugar"
},
"uk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ulk": {
"sc": "Latn"
},
"ur": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ur-Arab"
},
"uz": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"vec": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"vi": {
"haswikt": 1
},
"vls": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"vo": {
"haswikt": 1
},
"wa": {
"haswikt": 1
},
"wbp": {
"sc": "Latn"
},
"wo": {
"haswikt": 1
},
"wuu": {
"sc": "Hani"
},
"xae": {
"sc": "Ital"
},
"xcr": {
"sc": "Cari"
},
"xfa": {
"sc": "Ital"
},
"xh": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"xlc": {
"sc": "Lyci"
},
"xld": {
"sc": "Lydi"
},
"xlu": {
"sc": "Xsux"
},
"xmf": {
"sc": "Geor"
},
"xno": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"xrr": {
"sc": "Ital"
},
"xst": {
"sc": "Ethi"
},
"xum": {
"sc": "Ital"
},
"xve": {
"sc": "Ital"
},
"xvo": {
"sc": "Ital"
},
"yi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"yo": {
"haswikt": 1
},
"yua": {
"g": [
"y",
"p"
],
"alt": 1
},
"yue": {
"sc": "Hani"
},
"za": {
"haswikt": 1,
"sc": "Latn"
},
"zh-classical": {
"sc": "Hani"
},
"zh-min-nan": {
"haswikt": 1
},
"zh-yue": {
"sc": "Hani"
},
"zu": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"zza": {
"g": [
"n",
"m",
"mn",
"n-p",
"m-p",
"p"
],
"wiktprefix": "diq"
}
},
"numberLabels": {
"y": {
"display": "yekjimar"
},
"d": {
"display": "hejmar dual"
},
"p": {
"display": "pirjimar"
},
"p+": {
"display": "axlebî pirjimar"
},
"tp": {
"display": "tenê pirjimar"
},
"j": {
"display": "jimarbar"
},
"j+": {
"display": "axlebî jimarbar"
},
"j/nj": {
"display": "jimarbar û nejimarbar"
},
"nj": {
"display": "nejimarbar"
},
"nj+": {
"display": "axlebî nejimarbar"
},
"hj!": {
"display": "hejmar nepeyitandî ye"
},
"hj?": {
"display": "hejmar hewce ye"
}
},
"leadingTplOrder": [
"maneid",
"K",
"e"
],
"lekturChoices": [
{
"code": "gh",
"label": "gerguhêz (îsmî mefûl digire)"
},
{
"code": "ngh",
"label": "negerguhêz (îsmî mefûl nagire)"
}
],
"lekturFormChoices": [
{
"code": "x",
"label": "xwerû"
},
{
"code": "p",
"label": "pêkhatî"
},
{
"code": "h",
"label": "hevedudanî"
}
],
"deverQuickPicks": [
"kurmanciya rojavayî",
"kurmanciya merkezî",
"kurmanciya rojhilatî",
"Badînî",
"Botî",
"Serhedî",
"Bakur",
"Amed",
"Riha"
],
"maneTags": [
"cure?",
"rêziman?",
"kat-rêziman",
"kategoriya rêzimanê?",
"bêcure"
],
"senseGroups": [
{
"key": "alt-r",
"label": "Formên alternatîv",
"field": "alternativ",
"order": 3,
"hint": "Varyantên vê kelîmeyê"
},
{
"key": "hevmane",
"label": "Hevmane",
"field": "hevmane",
"order": 1,
"hint": "Spehî hevmaneya Bedew e."
},
{
"key": "dijmane",
"label": "Dijmane",
"field": "dijmane",
"order": 2,
"hint": "Zengîn dijmaneya feqîr e."
},
{
"key": "koordîne",
"label": "Termên koordîne",
"field": "koordine",
"order": 4,
"hint": "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in"
},
{
"key": "jornav",
"label": "Jornav",
"field": "jornav",
"order": 5,
"hint": "Heywan jornavê guhandar e"
},
{
"key": "jêrnav",
"label": "Jêrnav",
"field": "jernav",
"order": 6,
"hint": "Guhandar jêrnavê heywan e"
}
],
"subsectionOrder": [
"Bilêvkirin",
"Ji wêjeya klasîk",
"Ji wêjeyê",
"Tewandin",
"Notên bikaranînê",
"Bi alfabeyên din",
"Formên alternatîv",
"Kurtkirin",
"Sembol",
"Navê zanistî",
"Hevmane",
"Nêzîk",
"Dijmane",
"Jornav",
"Jêrnav",
"Jê",
"Têkildar",
"Etîmolojî",
"Dûnde",
"Bide ber",
"Herwiha binêre",
"Bêhtir bixwîne",
"Bi zaravayên din",
"Werger",
"Qafiye",
"Anagram",
"Çavkanî"
],
"templateAliases": {
"K": [
"K",
"kategorîzebike",
"mijar"
],
"alt-r": [
"alt-r",
"forma alt di rêzê de",
"herwiha-rêz",
"herwiha di rêza maneyê de"
],
"dijmane": [
"dijmane",
"dijwate",
"dijmaneyên peyvê"
],
"e": [
"e",
"etîket",
"f",
"lb"
],
"g": [
"g",
"girêdan",
"l",
"link"
],
"hevmane": [
"hevmane",
"hevwate",
"hevmaneya peyvê",
"hevmaneyên peyvê",
"synonyms"
],
"jornav": [
"jornav",
"hîpernîm"
],
"jêrnav": [
"jêrnav",
"hîponîm"
],
"koordîne": [
"koordîne",
"termên koordîne",
"peyvên koordîneyî"
],
"mane?": [
"mane?",
"wate?",
"bêmane",
"bêwate"
],
"maneid": [
"maneid",
"senseid"
],
"h-bnr": [
"h-bnr",
"bnr-h",
"bnr h",
"bnr-hrw",
"bnr herwiha",
"herwiha binêre",
"binêre herwiha"
]
},
"wordlistTemplates": [
"kol3",
"kol2",
"kol",
"col3",
"col2",
"col"
],
"templateParams": {
"deyn": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"sc",
"id"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya kelîmeyê",
"tr": "Transliterasyon",
"ts": "Transkîpsiyon",
"sc": "Koda skrîptê",
"id": "IDya maneyê"
}
},
"hv": {
"order": [
"1",
"2",
"3",
"4",
"tr",
"ts",
"sc"
],
"labels": {
"1": "Koda zimanî",
"2": "Kelîme",
"3": "Xuyakirina alternatîv",
"4": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"sc": "Koda skrîptê"
}
},
"ji": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"id",
"sc"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"id": "IDya maneyê",
"sc": "Koda skrîptê"
}
},
"jêder-kitêb": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"t",
"nivîskar",
"nivîskar2",
"nivîskar3",
"amadekar",
"terciman",
"sernav",
"url",
"weşanxane",
"cih",
"cild",
"sal",
"sala-weşandinê",
"çap",
"rûpel",
"isbn",
"nekat",
"nivîskar4",
"nivîskar5",
"issn",
"oclc",
"serî",
"doi"
],
"labels": {
"1": "koda zimanî",
"sc": "koda skrîptê eger hewce be",
"tr": "translîterasyon",
"t": "tercime",
"rûpel": "rûpel"
}
},
"jêder-kovar": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"werger",
"kovar",
"sernav",
"wergêr",
"nivîskar",
"nivîskar2",
"url",
"weşanxane",
"cîld",
"hejmar",
"sal",
"tarîx",
"rûpel"
],
"labels": {
"1": "Koda zimanî",
"sc": "Koda skrîptê",
"tr": "Translîterasyon",
"werger": "Tercimeya kurmancî ya jêgirtinê"
}
},
"jêder-vîdeo": {
"order": [
"1",
"title",
"year",
"date",
"author",
"authorlink",
"actor",
"role",
"time",
"at",
"medium",
"network",
"text",
"translation",
"season",
"number",
"episode",
"publisher",
"url",
"director",
"brackets"
],
"labels": {
"1": "Language",
"title": "Title",
"year": "Year of release",
"date": "Date of release",
"author": "Writer (screenplay)",
"authorlink": "Wikipedia author page",
"actor": "Actor (speaker)",
"role": "Role",
"time": "Timecode",
"at": "Context",
"medium": "Recording medium",
"network": "Network",
"text": "Quoted line",
"translation": "Translation",
"season": "Season number (series)",
"number": "Episode number (series)",
"episode": "Episode title (series)",
"publisher": "Publisher",
"url": "URL",
"director": "Director",
"brackets": "Brackets"
}
},
"jêder-websîte": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"t",
"nivîskar",
"terciman",
"sernav",
"url",
"urlya-arşîvê",
"websîte",
"tarîx",
"sal"
],
"labels": {
"1": "Koda zimanî",
"sc": "Koda skrîptê ya jêgirtinê",
"tr": "Translîterasyona jêgirtinê",
"t": "Tercimeya kurmancî ya jêgirtinê"
}
},
"mîr": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"id",
"sc"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"id": "IDya maneyê",
"sc": "Koda skrîptê"
}
},
"nh": {
"order": [
"1",
"2",
"3",
"4",
"tr",
"ts",
"sc"
],
"labels": {
"1": "Koda zimanî",
"2": "Kelîme",
"3": "Xuyakirina alternatîv",
"4": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"sc": "Koda skrîptê"
}
},
"qertaf": {
"order": [
"1",
"2",
"3",
"sc",
"cuda1",
"cuda2",
"t1",
"t2",
"tr1",
"tr2"
],
"labels": {
"1": "language code",
"2": "first part",
"3": "second part",
"sc": "script code",
"cuda1": "first part alternate text",
"cuda2": "second part alternate text",
"w1": "first gloss",
"w2": "second gloss",
"tr1": "first part transliteration",
"tr2": "second part transliteration"
}
},
"yekser": {
"order": [
"1",
"2",
"3",
"hûr",
"nekat"
]
}
}
}
26838a6aiasabky037to3d4916elvr5
6865649
6865600
2026-10-24T08:46:13Z
Wikihez
7344
metadata moved to wikt.lang-metadata
6865649
json
application/json
{
"langChoices": {
"ku": "kurmancî",
"zza": "zazakî",
"ckb": "soranî",
"sdh": "kurdiya başûrî",
"hac": "hewramî",
"de": "almanî",
"ar": "erebî",
"fa": "farisî",
"fr": "fransî",
"fi": "fînî",
"en": "îngilîzî",
"ja": "japonî",
"sv": "swêdî",
"tr": "tirkî"
},
"posExamples": {
"navdêr": "Kelîmeyên ji bo tiştan (sêv, heywan, qedeh) yan terman (dilşadî, bextiyarî hwd.) di rêzimanê de.",
"lêker": "Kelîmeyên ji bo kirin an bûyînê wekî 'nivîsîn' yan 'xeber dan'.",
"rengdêr": "Kelîmeyên ku wesfa navdêran nîşan didin wekî 'sêva sor' yan 'xwarina şîrîn'.",
"hoker": "Kelîmeyên ku dem, cih yan awayê kirinê diyar dikin wekî 'bi vî awayî' yan 'demkî'.",
"daçek": "Kelîmeyên ku cih, dem yan hereketê diyar dikin wekî 'di.. de' yan 'ji'.",
"cînav": "Kelîmeyên ku li şûna navdêran tên bikaranîn daku navdêr neyên dubarekirin wekî 'ew' yan 'em'.",
"baneşan": "Kelîmeyên ku hest, deng hwd. îfade dikin wekî 'noşî!' yan 'aferîn'.",
"serenav": "Navdêrên ku şexsiyetên ferdî nîşan didin, wek navên kes, cih an organîzasyonan nimûne 'London' yan 'Diyarbekir'.",
"gotineke pêşiyan": "Gotinên ku di nav xelkê de bi berbelavî tên bikaranîn bo nimûne 'Serê neêşe ne hewceyî destmalê ye'."
},
"genderLabels": {
"m": "mê",
"n": "nêr",
"nt": "nêtar",
"mn": "mê yan nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"mndudil": "mê yan nêr bikaranîn dudil e",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": {
"rastnivîs": {
"langs": [
"ku"
],
"noLang": true
},
"ku-Latn": {
"langs": [
"ku"
],
"noLang": true
},
"ku-tewîna-nav": {
"langs": [
"ku"
],
"noLang": true
},
"sdh-Arab": {
"langs": [
"sdh"
],
"noLang": true
},
"hac-Arab": {
"langs": [
"hac"
],
"noLang": true
},
"ja-romanîzekirin": {
"langs": [
"ja"
],
"noLang": true
},
"ckb-Arab": {
"langs": [
"ckb"
],
"noLang": true
},
"zza-awayê-Jacobsonê": {
"langs": [
"zza"
]
},
"awayekî din": {
"langs": null
},
"guharto": {
"langs": null
},
"hevmaneya": {
"langs": null
},
"kevnbûyî": {
"langs": null
},
"kêmpeyda": {
"langs": null
},
"kurtkirin": {
"langs": null
},
"paşnav": {
"langs": null
},
"şaşnivîs": {
"langs": null
}
},
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 3
},
{
"key": "terciman",
"label": "Terciman",
"span": 3
},
{
"key": "amadekar",
"label": "Amadekar",
"span": 3
},
{
"key": "weşanxane",
"label": "Weşanxane",
"span": 3
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "beş",
"label": "Beş",
"span": 3,
"width": "medium"
},
{
"key": "cih",
"label": "Cih",
"span": 2,
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"span": 1,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
},
{
"key": "sala-weşandinê",
"label": "Sala weşandinê",
"span": 2,
"width": "narrow"
},
{
"key": "çap",
"label": "Çap",
"span": 1,
"width": "narrow"
},
{
"key": "cild",
"label": "Cild",
"span": 2,
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"span": 1,
"width": "narrow"
},
{
"key": "isbn",
"label": "ISBN",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 3
},
{
"key": "terciman",
"label": "Terciman",
"span": 3
},
{
"key": "amadekar",
"label": "Amadekar",
"span": 3
},
{
"key": "weşanxane",
"label": "Weşanxane",
"span": 3
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "kovar",
"label": "Kovar",
"span": 4
},
{
"key": "cild",
"label": "Cild",
"span": 2,
"width": "narrow"
},
{
"key": "hejmar",
"label": "Hejmar",
"span": 2,
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"span": 2,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
},
{
"key": "rûpel",
"label": "Rûpel",
"span": 2,
"width": "narrow"
},
{
"key": "issn",
"label": "ISSN",
"span": 2,
"width": "narrow"
},
{
"key": "doi",
"label": "DOI",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar",
"span": 4
},
{
"key": "terciman",
"label": "Terciman",
"span": 4
},
{
"key": "websîte",
"label": "Websîte",
"span": 4
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "url",
"label": "URL",
"span": 5
},
{
"key": "urlya-arşîvê",
"label": "URLya Arşîvê",
"span": 5
},
{
"key": "roja-arşîvê",
"label": "Roja arşîvê",
"span": 3,
"width": "medium"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
},
{
"key": "sal",
"label": "Sal",
"span": 1,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3,
"width": "medium"
}
]
},
"jêder-vîdeo": {
"fields": [
{
"key": "hunermend",
"label": "Hunermend",
"span": 6
},
{
"key": "platform",
"label": "Platform",
"span": 6
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"span": 12,
"width": "wide"
},
{
"key": "sal",
"label": "Sal",
"span": 2,
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"span": 3
},
{
"key": "dem",
"label": "Dem",
"span": 2,
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"span": 12,
"width": "wide"
},
{
"key": "tarîxa-gihiştinê",
"label": "Tarîxa gihiştinê",
"span": 3,
"width": "medium"
}
],
"label": "Vîdeo"
}
},
"pronTpls": {
"ku-IPA": {
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
"ku-kîte": {
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
"zza-IPA": {
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
"zza-kîte": {
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
"IPA": {
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
"deng": {
"desc": "Nimûneya telafûzê bi dosyeya dengî",
"snippet": "* {{deng|CODE|navê_dosyayê.ogg|dever=}}"
},
"bilêv": {
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
"kîte": {
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
"hevdeng": {
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
},
"etymTpls": {
"etîmon": {
"desc": "Etîmolojiya birêkûpêk: zincîr, dar û kategorî — {{etîmon|ku|:deyn|fa:پنجه\u003Ct:dest\u003E}}",
"snippet": "{{etîmon|CODE}}"
},
"ji": {
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
"deyn": {
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
"mîr": {
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
"qertaf": {
"desc": "Ji bo pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
"anlz": {
"desc": "Wekî qertaf lê dibêje analîza şiklî — {{anlz|kod|pêşgir-|reh}}",
"snippet": "{{anlz|CODE|…|…}}"
},
"q": {
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
"hv": {
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
"nh": {
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
"cotreh": {
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
"yekser": {
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
"wergirtin": {
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
"paşvesazî": {
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
"qusandin": {
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
"têkilkirin": {
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
"nyz": {
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
"onom": {
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
"navmiletî": {
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
"neo": {
"desc": "neolojîzm — {{neolojîzm|ku|hûr=1}}",
"snippet": "{{neolojîzm|CODE}}"
}
},
"etimon": {
"keywords": [
{
"key": "qertaf",
"desc": "Kelîmeya ji qertafan an ji du kelîmeyan pêk hatî; rêza parçeyan girîng e",
"short": "ji qertafan an du kelîmeyan",
"aliases": [
"qf",
"af"
]
},
{
"key": "dariştî",
"desc": "Kelîmeya ku ji zimanekî din hatiye dariştin, wextê ne mîrasmayî û ne jî deynkirineke diyar be",
"short": "dariştin ji zimanekî din",
"aliases": [
"drş",
"der"
]
},
{
"key": "drşnd",
"desc": "Dariştin heye lê cureyê wê ne diyar e",
"short": "dariştin, cure ne diyar",
"aliases": [
"uder"
]
},
{
"key": "ji",
"desc": "Dariştineke di nav eynî zimanî de ku cureyê wê nehatiye diyarkirin; tenê ji bo kelîmeyên eynî zimanî derbasdar e",
"short": "dariştin di eynî zimanî de",
"aliases": [
"from"
]
},
{
"key": "mîrasmayî",
"desc": "Kelîmeya ku ji zimanê bav û kalan maye",
"aliases": [
"mîr",
"inh"
]
},
{
"key": "deyn",
"desc": "Kelîmeya ku rasterast ji zimanekî din hatiye deynkirin",
"aliases": [
"bor",
"borrowed"
]
},
{
"key": "deynh",
"desc": "Deynkirina hînbûyî: ne bi riya têkiliya rojane, bi qesd hatiye girtin",
"short": "deynkirina hînbûyî",
"aliases": [
"lbor"
]
},
{
"key": "deynhq",
"desc": "Deynkirina hînbûyî ya ku hinekî hatiye guhertin",
"short": "deynkirina hînbûyî ya qismî",
"aliases": [
"slbor"
]
},
{
"key": "deynort",
"desc": "Deynkirina ortografîk",
"short": "deynkirina ortografîk",
"aliases": [
"obor"
]
},
{
"key": "deynne",
"desc": "Deynkirina neadaptekirî",
"short": "deynkirina neadaptekirî",
"aliases": [
"ubor"
]
},
{
"key": "reh",
"desc": "Kelîmeyên pey xwe wek reh dide nasîn; tenê ji bo kategoriyan e (\"Peyvên … yên aîdî rehê …\") û di metn û darê de xuya nabe",
"short": "reh, tenê kategorî",
"aliases": [
"root"
],
"invisible": true
},
{
"key": "yekser",
"desc": "Wergerandina yekser; zincîr li vir disekine",
"short": "wergerandina yekser",
"aliases": [
"cal",
"clq"
]
},
{
"key": "yekserqismî",
"desc": "Wergerandina yekser a qismî; zincîr li vir disekine",
"short": "wergerandina yekser a qismî",
"aliases": [
"yekserq",
"pcal"
]
},
{
"key": "wergirtin",
"desc": "Wergirtina maneyê; mana wergirtî bi \u003Cmaneid:…\u003E tê nîşankirin",
"short": "wergirtina maneyê",
"aliases": [
"sl"
]
},
{
"key": "lihevanîn",
"desc": "Lihevanîna dengî-maneyî",
"aliases": [
"psm"
]
},
{
"key": "tesîr",
"desc": "Kelîme di bin tesîra kelîmeyeke din de guheriye; zincîr li vir disekine",
"short": "di bin tesîra kelîmeyekê",
"aliases": [
"influence"
]
},
{
"key": "têkilkirin",
"desc": "Têkilkirina du kelîmeyan",
"aliases": [
"blend"
],
"min": 2
},
{
"key": "ûnîverbasyon",
"desc": "Ûnîverbasyon",
"aliases": [
"ûnîv",
"univ"
]
},
{
"key": "qusandin",
"desc": "Qusandin",
"aliases": [
"clip"
]
},
{
"key": "paşvesazî",
"desc": "Paşvesazî",
"aliases": [
"bf"
]
},
{
"key": "elîpsîz",
"desc": "Elîpsîz",
"aliases": [
"elîp",
"ellip"
]
},
{
"key": "nomînalîzasyon",
"desc": "Nomînalîzasyon",
"aliases": [
"nomîn",
"nom"
]
},
{
"key": "translîterasyon",
"desc": "Translîterasyon",
"aliases": [
"translît",
"translit"
]
},
{
"key": "redûblîkasyon",
"desc": "Redûblîkasyon",
"aliases": [
"redûb",
"redup"
]
},
{
"key": "kurtkirin",
"desc": "Kurtkirina kelîmeyekê",
"aliases": [
"abbr"
]
},
{
"key": "sylabbr",
"desc": "Kurtkirin li gorî kîteyan",
"short": "kurtkirin bi kîteyan",
"aliases": [
"syllabic abbreviation",
"sylabbrev"
]
},
{
"key": "akronîm",
"desc": "Akronîm",
"aliases": [
"acro"
]
},
{
"key": "înîtîalîzm",
"desc": "Înîtîalîzm",
"aliases": [
"înît",
"init"
]
},
{
"key": "metatez",
"desc": "Metatez",
"aliases": [
"meta"
]
},
{
"key": "afeq",
"desc": "Wek :qf, lê ne di metnê ne jî di darê de xuya dibe; tenê kategoriyan çêdike",
"short": "qertaf, tenê kategorî",
"invisible": true
},
{
"key": "apheresis",
"desc": "Şiklê ku dengê destpêkê winda kiriye",
"short": "dengê destpêkê winda",
"aliases": [
"apheretic",
"aphetic"
]
},
{
"key": "denominal",
"desc": "Fiîla ji îsmekê çêbûyî",
"short": "fiîl ji îsmekê",
"aliases": [
"denom"
]
},
{
"key": "deverbal",
"desc": "Kelîmeya ji fiîlekê çêbûyî",
"short": "kelîme ji fiîlekê"
},
{
"key": "vrd",
"desc": "Dariştina vṛddhî (sanskrîtî); bi qertaf: :vrd-af",
"short": "dariştina vṛddhî"
},
{
"key": "sa-af",
"desc": "Pêkhateya sanskrîtî",
"short": "pêkhateya sanskrîtî",
"min": 2
}
],
"wordMods": [
{
"key": "t",
"desc": "Tercimeya kelîmeyê bi kurmancî",
"aliases": [
"w"
]
},
{
"key": "tr",
"desc": "Translîterasyon"
},
{
"key": "ts",
"desc": "Transkrîpsiyon"
},
{
"key": "cuda",
"desc": "Şiklê ku tê nîşandan, eger ne wek navê rûpelê be",
"aliases": [
"alt"
]
},
{
"key": "id",
"desc": "Nasnameya etîmolojiya wê kelîmeyê li ser rûpela wê; eger mane tune be, wek mane tê nîşandan"
},
{
"key": "crz",
"desc": "Cureyê rêzimanî yê kelîmeyê",
"aliases": [
"pos"
],
"values": [
"reh",
"kelîme",
"pêşgir",
"paşgir",
"navgir"
]
},
{
"key": "c",
"desc": "Cinsiyet: n (nêr), m (mê), nt (nêtar)",
"values": [
"n",
"m",
"nt"
],
"aliases": [
"z",
"g"
]
},
{
"key": "q",
"desc": "Wesf li pêşiyê kelîmeyê"
},
{
"key": "qq",
"desc": "Wesf li pey kelîmeyê"
},
{
"key": "hrf",
"desc": "Mana herfiyen",
"aliases": [
"lit"
]
},
{
"key": "ref",
"desc": "Çavkanî; binêre #Çavkanî"
},
{
"key": "nediyar",
"desc": "Ev gav ne teqez e",
"flag": true,
"aliases": [
"unc"
]
},
{
"key": "crztip",
"desc": "Kelîme ji bo kategoriyan wek reh yan kelîme tê hesibandin",
"aliases": [
"postype"
],
"values": [
"reh",
"kelîme"
]
},
{
"key": "etimon",
"desc": "Etîmolojiya di cih de; binêre jor",
"aliases": [
"ety"
]
},
{
"key": "aftype",
"desc": "Di nav :qf de: cureyê qertafê; binêre #Cureyên qertafan",
"values": [
"pêşgir",
"paşgir",
"navgir",
"circumfix",
"infix",
"non-affix",
"root"
],
"only": [
"qertaf"
]
},
{
"key": "deyn",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "deynh",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "deynhq",
"desc": "Di nav :qf de: ev parçe deynkirî ye",
"flag": true,
"only": [
"qertaf"
]
}
],
"keywordMods": [
{
"key": "nediyar",
"desc": "Ev gav ne teqez e: \"Mihtemelen…\"",
"flag": true,
"aliases": [
"unc"
]
},
{
"key": "grdk",
"desc": "Girêdeka di navbera kelîmeyên jêder de, li şûna \"yan jî\"",
"aliases": [
"conj"
]
},
{
"key": "anlz",
"desc": "Analîza şiklî",
"flag": true,
"aliases": [
"surf"
]
},
{
"key": "neo",
"desc": "Di nav :qf de: neolojîzm, \"Neolojîzm ji…\", di darê de \"neo.\"",
"flag": true,
"only": [
"qertaf"
]
},
{
"key": "hrf",
"desc": "Di nav :qf de: mana herfiyen",
"aliases": [
"lit"
],
"only": [
"qertaf"
]
},
{
"key": "maneid",
"desc": "Di nav :wergirtin de: nasnameya maneya wergirtî",
"aliases": [
"senseid"
],
"only": [
"wergirtin"
]
},
{
"key": "metn",
"desc": "Di nav :ji û :drş de: metnekî din li şûna \"Ji\"",
"aliases": [
"text"
],
"only": [
"ji",
"dariştî"
]
},
{
"key": "ref",
"desc": "Çavkaniya vê gavê"
}
],
"metnChoices": [
{
"value": "++",
"desc": "Tevahiya zincîrê"
},
{
"value": "+",
"desc": "Tenê gava pêşîn"
},
{
"value": "*",
"desc": "Heta kelîmeya pêşîn a ku rûpela wê heye"
},
{
"value": "1",
"desc": "Ewqas gav (hejmarek)",
"free": true
},
{
"value": ":",
"desc": "Heta zimanê bi wê kodê (:kod)",
"free": true
},
{
"value": ":*",
"desc": "Heta wî zimanî, eger rûpela wî tune be heta ya pêşîn a heyî (:kod*)",
"free": true
}
],
"afTypes": [
{
"key": "pêşgir",
"desc": "Pêşgir"
},
{
"key": "paşgir",
"desc": "Paşgir"
},
{
"key": "navgir",
"desc": "Navgir"
},
{
"key": "circumfix",
"desc": "Dorgir"
},
{
"key": "infix",
"desc": "Nawgir (infix)"
},
{
"key": "non-affix",
"desc": "Ne qertaf"
},
{
"key": "root",
"desc": "Reh"
}
],
"paramAliases": {
"text": "metn",
"notext": "metnna",
"pos": "crz",
"doublet": "cotreh",
"etydate": "tarîxa-etîm",
"nodot": "niqtena",
"dot": "niqte",
"nocat": "katna",
"nekat": "katna"
},
"params": [
{
"key": "darna",
"desc": "",
"type": "select",
"values": [
{
"value": "1",
"label": "1 - dara etîmolojiyê li vê rûpelê nayê nîşandan"
},
{
"value": "0",
"label": "0 - ev etîmolojî di dara rûpelên din de dernakeve"
}
]
},
{
"key": "id",
"desc": "Nasnameya vê etîmolojiyê. Eger mane nehatibe dayîn, wek mane jî tê nîşandan.",
"type": "text",
"primary": true
},
{
"key": "title",
"desc": "Kelîmeya ku etîmolojî jê re tê nivîsîn, eger ne navê rûpelê be.",
"type": "text"
},
{
"key": "crz",
"desc": "Cureyê rêzimanî yê kelîmeyê ji bo kategoriyan: reh, kelîme, pêşgir, paşgir, navgir.",
"type": "select",
"values": [
"reh",
"kelîme",
"pêşgir",
"paşgir",
"navgir"
]
},
{
"key": "cotreh",
"desc": "Cotrehên vê kelîmeyê, bi bêhnokê ji hev cuda.",
"type": "text"
},
{
"key": "tarîxa-etîm",
"desc": "Tarîxa peyitandina pêşîn, wek 1901, 1500s, 14c, c,1900, before,1850, r,1501,1558.",
"type": "text"
},
{
"key": "niqtena",
"desc": "Bi 1 niqteya dawiyê nayê zêdekirin.",
"type": "flag"
},
{
"key": "niqte",
"desc": "Li şûna niqteya dawiyê nîşaneke din.",
"type": "text"
},
{
"key": "katna",
"desc": "Bi 1 kategorî nayên zêdekirin. Herwiha {{para|nekat}}.",
"type": "flag"
},
{
"key": "exnihilo",
"desc": "Kelîmeya ku ji tu kelîmeyeke din nehatiye çêkirin.",
"type": "flag"
}
]
},
"numberLabels": {
"y": {
"display": "yekjimar"
},
"d": {
"display": "hejmar dual"
},
"p": {
"display": "pirjimar"
},
"p+": {
"display": "axlebî pirjimar"
},
"tp": {
"display": "tenê pirjimar"
},
"j": {
"display": "jimarbar"
},
"j+": {
"display": "axlebî jimarbar"
},
"j/nj": {
"display": "jimarbar û nejimarbar"
},
"nj": {
"display": "nejimarbar"
},
"nj+": {
"display": "axlebî nejimarbar"
},
"hj!": {
"display": "hejmar nepeyitandî ye"
},
"hj?": {
"display": "hejmar hewce ye"
}
},
"leadingTplOrder": [
"maneid",
"K",
"e"
],
"lekturChoices": [
{
"code": "gh",
"label": "gerguhêz (îsmî mefûl digire)"
},
{
"code": "ngh",
"label": "negerguhêz (îsmî mefûl nagire)"
}
],
"lekturFormChoices": [
{
"code": "x",
"label": "xwerû"
},
{
"code": "p",
"label": "pêkhatî"
},
{
"code": "h",
"label": "hevedudanî"
}
],
"deverQuickPicks": [
"kurmanciya rojavayî",
"kurmanciya merkezî",
"kurmanciya rojhilatî",
"Badînî",
"Botî",
"Serhedî",
"Bakur",
"Amed",
"Riha"
],
"maneTags": [
"cure?",
"rêziman?",
"kat-rêziman",
"kategoriya rêzimanê?",
"bêcure"
],
"senseGroups": [
{
"key": "alt-r",
"label": "Formên alternatîv",
"field": "alternativ",
"order": 3,
"hint": "Varyantên vê kelîmeyê"
},
{
"key": "hevmane",
"label": "Hevmane",
"field": "hevmane",
"order": 1,
"hint": "Spehî hevmaneya Bedew e."
},
{
"key": "dijmane",
"label": "Dijmane",
"field": "dijmane",
"order": 2,
"hint": "Zengîn dijmaneya feqîr e."
},
{
"key": "koordîne",
"label": "Termên koordîne",
"field": "koordine",
"order": 4,
"hint": "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in"
},
{
"key": "jornav",
"label": "Jornav",
"field": "jornav",
"order": 5,
"hint": "Heywan jornavê guhandar e"
},
{
"key": "jêrnav",
"label": "Jêrnav",
"field": "jernav",
"order": 6,
"hint": "Guhandar jêrnavê heywan e"
}
],
"subsectionOrder": [
"Bilêvkirin",
"Ji wêjeya klasîk",
"Ji wêjeyê",
"Tewandin",
"Notên bikaranînê",
"Bi alfabeyên din",
"Formên alternatîv",
"Kurtkirin",
"Sembol",
"Navê zanistî",
"Hevmane",
"Nêzîk",
"Dijmane",
"Jornav",
"Jêrnav",
"Jê",
"Têkildar",
"Etîmolojî",
"Dûnde",
"Bide ber",
"Herwiha binêre",
"Bêhtir bixwîne",
"Bi zaravayên din",
"Werger",
"Qafiye",
"Anagram",
"Çavkanî"
],
"templateAliases": {
"K": [
"K",
"kategorîzebike",
"mijar"
],
"alt-r": [
"alt-r",
"forma alt di rêzê de",
"herwiha-rêz",
"herwiha di rêza maneyê de"
],
"dijmane": [
"dijmane",
"dijwate",
"dijmaneyên peyvê"
],
"e": [
"e",
"etîket",
"f",
"lb"
],
"g": [
"g",
"girêdan",
"l",
"link"
],
"hevmane": [
"hevmane",
"hevwate",
"hevmaneya peyvê",
"hevmaneyên peyvê",
"synonyms"
],
"jornav": [
"jornav",
"hîpernîm"
],
"jêrnav": [
"jêrnav",
"hîponîm"
],
"koordîne": [
"koordîne",
"termên koordîne",
"peyvên koordîneyî"
],
"mane?": [
"mane?",
"wate?",
"bêmane",
"bêwate"
],
"maneid": [
"maneid",
"senseid"
],
"h-bnr": [
"h-bnr",
"bnr-h",
"bnr h",
"bnr-hrw",
"bnr herwiha",
"herwiha binêre",
"binêre herwiha"
]
},
"wordlistTemplates": [
"kol3",
"kol2",
"kol",
"col3",
"col2",
"col"
],
"templateParams": {
"deyn": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"sc",
"id"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya kelîmeyê",
"tr": "Transliterasyon",
"ts": "Transkîpsiyon",
"sc": "Koda skrîptê",
"id": "IDya maneyê"
}
},
"hv": {
"order": [
"1",
"2",
"3",
"4",
"tr",
"ts",
"sc"
],
"labels": {
"1": "Koda zimanî",
"2": "Kelîme",
"3": "Xuyakirina alternatîv",
"4": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"sc": "Koda skrîptê"
}
},
"ji": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"id",
"sc"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"id": "IDya maneyê",
"sc": "Koda skrîptê"
}
},
"jêder-kitêb": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"t",
"nivîskar",
"nivîskar2",
"nivîskar3",
"amadekar",
"terciman",
"sernav",
"url",
"weşanxane",
"cih",
"cild",
"sal",
"sala-weşandinê",
"çap",
"rûpel",
"isbn",
"nekat",
"nivîskar4",
"nivîskar5",
"issn",
"oclc",
"serî",
"doi"
],
"labels": {
"1": "koda zimanî",
"sc": "koda skrîptê eger hewce be",
"tr": "translîterasyon",
"t": "tercime",
"rûpel": "rûpel"
}
},
"jêder-kovar": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"werger",
"kovar",
"sernav",
"wergêr",
"nivîskar",
"nivîskar2",
"url",
"weşanxane",
"cîld",
"hejmar",
"sal",
"tarîx",
"rûpel"
],
"labels": {
"1": "Koda zimanî",
"sc": "Koda skrîptê",
"tr": "Translîterasyon",
"werger": "Tercimeya kurmancî ya jêgirtinê"
}
},
"jêder-vîdeo": {
"order": [
"1",
"title",
"year",
"date",
"author",
"authorlink",
"actor",
"role",
"time",
"at",
"medium",
"network",
"text",
"translation",
"season",
"number",
"episode",
"publisher",
"url",
"director",
"brackets"
],
"labels": {
"1": "Language",
"title": "Title",
"year": "Year of release",
"date": "Date of release",
"author": "Writer (screenplay)",
"authorlink": "Wikipedia author page",
"actor": "Actor (speaker)",
"role": "Role",
"time": "Timecode",
"at": "Context",
"medium": "Recording medium",
"network": "Network",
"text": "Quoted line",
"translation": "Translation",
"season": "Season number (series)",
"number": "Episode number (series)",
"episode": "Episode title (series)",
"publisher": "Publisher",
"url": "URL",
"director": "Director",
"brackets": "Brackets"
}
},
"jêder-websîte": {
"order": [
"1",
"jêgirtin",
"sc",
"tr",
"t",
"nivîskar",
"terciman",
"sernav",
"url",
"urlya-arşîvê",
"websîte",
"tarîx",
"sal"
],
"labels": {
"1": "Koda zimanî",
"sc": "Koda skrîptê ya jêgirtinê",
"tr": "Translîterasyona jêgirtinê",
"t": "Tercimeya kurmancî ya jêgirtinê"
}
},
"mîr": {
"order": [
"1",
"2",
"3",
"4",
"5",
"tr",
"ts",
"id",
"sc"
],
"labels": {
"1": "Zimanê jêder",
"2": "Zimanê wergir",
"3": "Kelîme",
"4": "Xuyakirina alternatîv",
"5": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"id": "IDya maneyê",
"sc": "Koda skrîptê"
}
},
"nh": {
"order": [
"1",
"2",
"3",
"4",
"tr",
"ts",
"sc"
],
"labels": {
"1": "Koda zimanî",
"2": "Kelîme",
"3": "Xuyakirina alternatîv",
"4": "Maneya peyvê",
"tr": "Transliterasyon",
"ts": "Transkrîpsiyon",
"sc": "Koda skrîptê"
}
},
"qertaf": {
"order": [
"1",
"2",
"3",
"sc",
"cuda1",
"cuda2",
"t1",
"t2",
"tr1",
"tr2"
],
"labels": {
"1": "language code",
"2": "first part",
"3": "second part",
"sc": "script code",
"cuda1": "first part alternate text",
"cuda2": "second part alternate text",
"w1": "first gloss",
"w2": "second gloss",
"tr1": "first part transliteration",
"tr2": "second part transliteration"
}
},
"yekser": {
"order": [
"1",
"2",
"3",
"hûr",
"nekat"
]
}
}
}
34e3adcvf77585haxleoul3jwknfwdg
MediaWiki:Gadget-wikt.edit.css
8
1359165
6865535
6414271
2026-10-23T19:43:06Z
Wikihez
7344
Language fields: the translation adder's dropdown (names and codes searched) instead of a datalist
6865535
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.edit.css]]
*
* The searchable dropdown's styles, moved out of the wizard's stylesheet. util.js builds this widget
* (wrapNativeSelect) and DefSideBoxes' panels use it too, so leaving the styles with the wizard meant the
* part-of-speech picker rendered as a bare <ul> on any page where the wizard had not loaded. They belong
* with the code that emits them: wikt.edit, which both callers already depend on.
*
* Colour and spacing tokens come from wikt.core.css.
*/
/* ===================================================================
Searchable dropdown (wrapNativeSelect in start.js)
.wiktku-native-select-wrap > input.wiktku-ac-input (search box)
> ul.wiktku-ac-results (result list)
> select (hidden, holds the value)
=================================================================== */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
vertical-align: top;
}
/* Matches the width the native select would have had (see
select.wiktku-start-narrow). */
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: var(--wikt-r-field, 10px);
background: var(--wikt-palette-white);
color: inherit;
font-size: inherit;
font-family: inherit;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
input.wiktku-ac-input:focus {
border-color: var(--wikt-palette-blue-9);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9);
outline: 0;
}
input.wiktku-ac-input::placeholder {
color: var(--wikt-palette-grey-9);
opacity: 1;
}
/* Hide Safari/Chrome's built-in search decorations. */
input.wiktku-ac-input::-webkit-search-decoration,
input.wiktku-ac-input::-webkit-search-cancel-button,
input.wiktku-ac-input::-webkit-search-results-button {
-webkit-appearance: none;
appearance: none;
}
/* ── Result list ── */
/* Stays below MediaWiki's own .mw-notification-area (z-index: 10000) so the dropdown never covers a toast notification. */
ul.wiktku-ac-results {
position: absolute;
z-index: 500;
top: 100%;
bottom: auto;
left: 0;
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 13em;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: var(--wikt-r-field, 10px);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
/* Kategorî:Çavkanî search rows (see cite.js's buildCategoryTemplateSearch): the label/arrow shrink to
make room, the 🔗 link never does. */
.wiktku-cat-tpl-row {
gap: 8px;
}
.wiktku-cat-tpl-text {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.wiktku-cat-tpl-arrow {
color: var(--wikt-muted);
}
.wiktku-cat-tpl-link {
flex: 0 0 auto;
width: 26px;
height: 26px;
display: grid;
place-items: center;
font-size: 12px;
line-height: 1;
border: 1px solid var(--wikt-accent-edge);
border-radius: 7px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
text-decoration: none;
}
.wiktku-cat-tpl-link:hover {
border-color: var(--wikt-accent);
}
.wiktku-cat-tpl-status {
display: block;
margin-top: 3px;
font-size: 12.5px;
color: var(--wikt-palette-grey-9, #767676);
}
ul.wiktku-ac-results li {
margin: 0;
padding: 4px 8px;
cursor: pointer;
font-size: 0.92em;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .wiktku-ac-active is the keyboard cursor; hover mirrors it; :active
covers the touch press state. */
ul.wiktku-ac-results li.wiktku-ac-active,
ul.wiktku-ac-results li:hover {
background: var(--wikt-palette-lightblue);
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* Touch devices: bigger tap targets, no sticky hover. */
@media (pointer: coarse) {
ul.wiktku-ac-results li {
padding: 6px 10px;
min-height: 2.2em;
font-size: 1em;
}
ul.wiktku-ac-results li:hover {
background: transparent;
}
ul.wiktku-ac-results li.wiktku-ac-active {
background: var(--wikt-palette-lightblue);
}
input.wiktku-ac-input {
/* >=16px stops iOS Safari zooming the page when the box is focused. */
font-size: 16px;
min-height: 2.2em;
}
}
/* Single column on narrow screens: the search box and its list take the full width. */
@media screen and (max-width: 720px) {
.wiktku-native-select-wrap,
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
}
/* A drop shadow is invisible against a dark page, so the border stands in for it. */
@media screen {
html.skin-theme-clientpref-night .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
}
/* ===================================================================
Word row with inline modifiers (makeModifierRow in util.js)
wikt.edit emits this markup, and both callers use it: DefSideBoxes' word lists and cite.js's
template dialog. The styles ship with the code rather than with either caller, so the dialog
looks the same wherever it is opened from.
=================================================================== */
/* ── IPA palette ─────────────────────────────────────────────
Shown by the wizard's pronunciation step and by DefSideBoxes' Bilêvkirin box. */
.wiktku-ipa-panel {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 0 0 12px;
margin-bottom: 8px;
overflow: hidden;
}
.wiktku-ipa-subtabs {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px 0;
border-bottom: 1px solid var(--wikt-rule-soft);
margin-bottom: 12px;
flex-wrap: wrap;
}
.wiktku-ipa-subtab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
padding: 7px 12px 9px;
border-bottom: 3px solid transparent;
border-radius: 8px 8px 0 0;
cursor: pointer;
}
.wiktku-ipa-subtab:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
.wiktku-ipa-subtab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
.wiktku-ipa-tplbtns {
margin-left: auto;
display: flex;
gap: 6px;
padding-bottom: 6px;
}
.wiktku-ipa-tplbtn {
font-family: var(--wikt-mono);
font-size: 12px;
padding: 6px 10px;
border-radius: 7px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
white-space: nowrap;
}
.wiktku-ipa-tplbtn:hover {
border-color: var(--wikt-accent);
}
.wiktku-ipa-grid {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 12px;
}
.wiktku-ipa-row {
display: flex;
align-items: flex-start;
gap: 10px;
}
.wiktku-ipa-rowlabel {
flex: 0 0 74px;
font-size: 11.5px;
color: var(--wikt-muted);
padding-top: 8px;
line-height: 1.3;
}
.wiktku-ipa-keys {
display: flex;
flex-wrap: wrap;
gap: 4px;
flex: 1 1 auto;
}
.wiktku-ipa-group-row {
padding-bottom: 4px;
}
select.wiktku-ipa-group-select {
width: 14em;
max-width: 100%;
}
.wiktku-ipa-key {
width: 30px;
height: 30px;
padding: 0;
border-radius: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-ink);
font-family: var(--wikt-mono);
font-size: 14px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.1s ease, border-color 0.1s ease;
}
.wiktku-ipa-key:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.wiktku-ipa-key:active {
transform: translateY(1px);
}
.wiktku-ipa-key {
width: auto;
min-width: 30px;
padding: 0 5px;
}
.wiktku-ipa-key-combining {
color: var(--wikt-ink-2);
}
.wiktku-ipa-row {
flex-direction: column;
gap: 3px;
}
.wiktku-ipa-rowlabel {
flex: 0 0 auto;
padding-top: 0;
}
.wiktku-ipa-key {
min-width: 34px;
height: 34px;
}
/* ── The shared box ───────────────────────────────────────────
posparams.js is drawn by both gadgets, so its fields cannot take their look from either skin's own
stylesheet -- in DefSideBoxes they came out as bare browser boxes with square corners. .wikt-box is
the look itself; the posparams selectors are listed with it so the card needs no markup change. A
skin's own id-scoped rules still outrank all of this where it wants something different. */
.wikt-box,
textarea.wiktku-posparams-text,
input.wiktku-posparams-field,
select.wiktku-posparams-cinsiyet-sel,
select.wiktku-posparams-jimar-sel,
select.wiktku-posparams-lektur-sel,
select.wiktku-posparams-lekturform-sel {
box-sizing: border-box;
padding: 6px 10px;
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.wikt-box:focus,
textarea.wiktku-posparams-text:focus,
input.wiktku-posparams-field:focus,
select.wiktku-posparams-cinsiyet-sel:focus,
select.wiktku-posparams-jimar-sel:focus,
select.wiktku-posparams-lektur-sel:focus,
select.wiktku-posparams-lekturform-sel:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template line is read as code, so it keeps its monospace. */
textarea.wiktku-posparams-text {
font-family: var(--wikt-mono);
}
/* ── Drag-to-resize handle ─────────────────────────────────
util.js replaces the browser's own resize corner with this one: the native handle drags in two
directions, keeps no height between visits and cannot be styled. Built by initManualResize(), so both
the wizard and DefSideBoxes get it. */
.wiktku-resize-wrap {
position: relative;
display: block;
}
/* Separate from .wiktku-resize-wrap (used only for textareas flagged with a linebreak); a plain
position:relative anchor for the backdrop, with no size of its own. */
.wiktku-linebreak-wrap {
position: relative;
display: block;
}
.wiktku-resize-grip {
position: absolute;
right: 2px;
bottom: 2px;
width: 14px;
height: 14px;
cursor: ns-resize;
touch-action: none;
opacity: 0.5;
z-index: 2;
/* Follows the field's own rounded corner rather than cutting a square one out of it. The gradient's
two-percent feather takes the jaggedness off the diagonal at the same time. */
border-bottom-right-radius: var(--wikt-r-field, 10px);
background-image: linear-gradient(135deg, transparent 0 49%, var(--wikt-palette-grey-9, #767676) 51% 100%);
background-repeat: no-repeat;
}
.wiktku-resize-grip:hover,
.wiktku-resize-grip:active {
opacity: 0.9;
}
/* ── Word list with modifier pills ───────────────────────────────────
Every word is its own always-visible row: a word box plus a "+" that attaches one of the 10 WORD_MODIFIERS
as a small pill shown under the box. A fresh empty row is appended once the current last one gets text. */
.dsb-wordlist {
margin: 0;
padding-left: 0;
list-style: none;
}
/* Two columns once the list is long enough that one column runs off the panel. Rows are self-contained
(box, its own chips, its own picker), so they tile without anything spanning the gutter. */
@media (min-width: 700px) {
.dsb-wordlist-2col {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 14px;
align-items: start;
}
}
/* Roomier between words than between a word and its own chips below it -- that difference is the only
thing saying which chip belongs to which box. */
.dsb-word-li {
margin-bottom: 12px;
}
.dsb-word-li:last-child {
margin-bottom: 0;
}
.dsb-word-mods {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 3px;
/* Indented under the box, clear of the ⊕/× buttons at its right edge. */
margin-left: 10px;
}
.dsb-word-mod-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 11.5px;
padding: 3px 4px 3px 10px;
cursor: pointer;
}
.dsb-word-mod-chip-remove {
border: 0;
background: transparent;
color: var(--wikt-accent-deep);
font-size: 12px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
opacity: 0.7;
}
.dsb-word-mod-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
.dsb-word-box-row {
display: flex;
align-items: center;
gap: 6px;
}
.dsb-word-box-row .dsb-word-input {
flex: 1 1 auto;
min-width: 0;
}
/* The word boxes are textareas built by util.js's makeModifierRow, not .dsb-input, so they were left
with the browser's default chrome while every other box in the panel had a border and a focus ring.
Same look as .dsb-input above, minus its min-width -- these size themselves from the flex row. */
.dsb-word-input {
box-sizing: border-box;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
vertical-align: top;
resize: vertical;
overflow: auto;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-word-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-word-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* Offered only while the box holds a comma that separates two words. */
.dsb-word-split-row {
margin-top: 4px;
}
.dsb-word-split {
flex: 0 0 auto;
box-sizing: border-box;
height: 26px;
padding: 0 8px;
line-height: 1;
font-size: 12px;
border: 1px solid var(--wikt-accent-edge);
border-radius: 7px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
transition: border-color 0.12s ease, color 0.12s ease;
}
.dsb-word-split:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-mod-btn,
.dsb-word-remove {
flex: 0 0 auto;
box-sizing: border-box;
width: 26px;
height: 26px;
padding: 0;
line-height: 1;
font-size: 13px;
border-radius: 7px;
cursor: pointer;
transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}
.dsb-mod-btn {
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
}
.dsb-mod-btn:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-word-remove {
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-muted);
}
.dsb-word-remove:hover {
border-color: var(--wikt-danger);
color: var(--wikt-danger);
}
.dsb-mod-picker {
display: flex;
flex-wrap: wrap;
gap: 6px;
width: 100%;
margin-top: 6px;
padding: 8px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-card);
box-shadow: none;
}
.dsb-mod-pill {
font-family: var(--wikt-sans);
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
white-space: nowrap;
padding: 5px 10px;
cursor: pointer;
transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.dsb-mod-pill:hover {
border-color: var(--wikt-accent);
background: var(--wikt-accent);
color: #fff;
}
.dsb-mod-pill:disabled,
.dsb-mod-pill.dsb-mod-pill-used {
background: var(--wikt-card-sunken);
border-color: var(--wikt-rule);
color: var(--wikt-muted);
cursor: not-allowed;
opacity: 0.7;
}
.dsb-mod-pill:disabled:hover,
.dsb-mod-pill.dsb-mod-pill-used:hover {
background: var(--wikt-card-sunken);
border-color: var(--wikt-rule);
color: var(--wikt-muted);
}
.dsb-mod-val-wrap {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
width: 100%;
margin-top: 6px;
}
/* The key's full name -- the chip itself shows only "crz:", so this is where it is spelled out. */
.dsb-mod-val-label {
flex: 0 0 auto;
font-size: 11.5px;
color: var(--wikt-ink-2);
white-space: nowrap;
}
.dsb-mod-val-input {
flex: 1 1 auto;
min-width: 0;
max-width: 16em;
}
.dsb-mod-confirm,
.dsb-mod-cancel {
flex: 0 0 auto;
box-sizing: border-box;
width: 26px;
height: 26px;
padding: 0;
line-height: 1;
font-size: 13px;
border-radius: 7px;
background: var(--wikt-accent-tint);
cursor: pointer;
}
.dsb-mod-confirm {
border: 1px solid var(--wikt-ok);
color: var(--wikt-ok);
}
.dsb-mod-cancel {
border: 1px solid var(--wikt-danger);
color: var(--wikt-danger);
}
/* Touch: the 26px controls are below the 44px minimum, so they grow with the row. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-mod-btn,
.dsb-word-remove,
.dsb-mod-confirm,
.dsb-mod-cancel {
width: 36px;
height: 36px;
font-size: 15px;
}
.dsb-mod-pill {
padding: 8px 12px;
}
.dsb-mod-val-input {
max-width: none;
}
}
/* Last in the file on purpose: these override the sizes set above, and a media query adds no specificity
of its own -- placed earlier, the plain .dsb-word-input rule would simply win. Under 16px iOS Safari
zooms the page whenever it focuses a field. */
@media (pointer: coarse) {
.dsb-word-input,
.dsb-mod-val-input,
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
font-size: 16px;
}
/* Full width on a touch device, whatever the viewport reports -- the max-width query above covers a
phone in mobile view, but one asked to render the desktop site reports ~980px and misses it. The
attribute selector is repeated because the 20em default is set through it, and it outranks a plain
class selector. Desktop keeps the 20em. */
.wiktku-native-select-wrap,
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
}
/* </nowiki> */
/* Language dropdown from wikt.edit/langs.js, the same as the translation adder's. */
.wiktku-langmenu {
position: fixed;
z-index: 1100;
min-width: 14em;
max-height: 20em;
overflow-y: auto;
margin: 0;
padding: 2px 0;
list-style: none;
background: var(--background-color-base, #fff);
color: var(--color-base, #202122);
border: 1px solid var(--border-color-base, #a2a9b1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.wiktku-langmenu li {
margin: 0;
padding: 2px 8px;
cursor: pointer;
white-space: nowrap;
}
.wiktku-langmenu li:hover,
.wiktku-langmenu .wiktku-langmenu-active {
background: var(--background-color-progressive-subtle, #eaf3ff);
}
.wiktku-langmenu-code {
color: var(--color-subtle, #54595d);
font-size: 85%;
}
80cm45tcd2iybuar00g713vxr9xwaox
6865552
6865535
2026-10-23T20:12:05Z
Wikihez
7344
Shared dropdown: open upwards near the bottom of the window and fit the room available
6865552
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.edit.css]]
*
* The searchable dropdown's styles, moved out of the wizard's stylesheet. util.js builds this widget
* (wrapNativeSelect) and DefSideBoxes' panels use it too, so leaving the styles with the wizard meant the
* part-of-speech picker rendered as a bare <ul> on any page where the wizard had not loaded. They belong
* with the code that emits them: wikt.edit, which both callers already depend on.
*
* Colour and spacing tokens come from wikt.core.css.
*/
/* ===================================================================
Searchable dropdown (wrapNativeSelect in start.js)
.wiktku-native-select-wrap > input.wiktku-ac-input (search box)
> ul.wiktku-ac-results (result list)
> select (hidden, holds the value)
=================================================================== */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
vertical-align: top;
}
/* Matches the width the native select would have had (see
select.wiktku-start-narrow). */
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: var(--wikt-r-field, 10px);
background: var(--wikt-palette-white);
color: inherit;
font-size: inherit;
font-family: inherit;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
input.wiktku-ac-input:focus {
border-color: var(--wikt-palette-blue-9);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9);
outline: 0;
}
input.wiktku-ac-input::placeholder {
color: var(--wikt-palette-grey-9);
opacity: 1;
}
/* Hide Safari/Chrome's built-in search decorations. */
input.wiktku-ac-input::-webkit-search-decoration,
input.wiktku-ac-input::-webkit-search-cancel-button,
input.wiktku-ac-input::-webkit-search-results-button {
-webkit-appearance: none;
appearance: none;
}
/* ── Result list ── */
/* Stays below MediaWiki's own .mw-notification-area (z-index: 10000) so the dropdown never covers a toast notification. */
ul.wiktku-ac-results {
position: absolute;
z-index: 500;
top: 100%;
bottom: auto;
left: 0;
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 13em;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: var(--wikt-r-field, 10px);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
/* Kategorî:Çavkanî search rows (see cite.js's buildCategoryTemplateSearch): the label/arrow shrink to
make room, the 🔗 link never does. */
.wiktku-cat-tpl-row {
gap: 8px;
}
.wiktku-cat-tpl-text {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.wiktku-cat-tpl-arrow {
color: var(--wikt-muted);
}
.wiktku-cat-tpl-link {
flex: 0 0 auto;
width: 26px;
height: 26px;
display: grid;
place-items: center;
font-size: 12px;
line-height: 1;
border: 1px solid var(--wikt-accent-edge);
border-radius: 7px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
text-decoration: none;
}
.wiktku-cat-tpl-link:hover {
border-color: var(--wikt-accent);
}
.wiktku-cat-tpl-status {
display: block;
margin-top: 3px;
font-size: 12.5px;
color: var(--wikt-palette-grey-9, #767676);
}
ul.wiktku-ac-results li {
margin: 0;
padding: 4px 8px;
cursor: pointer;
font-size: 0.92em;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .wiktku-ac-active is the keyboard cursor; hover mirrors it; :active
covers the touch press state. */
ul.wiktku-ac-results li.wiktku-ac-active,
ul.wiktku-ac-results li:hover {
background: var(--wikt-palette-lightblue);
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* Touch devices: bigger tap targets, no sticky hover. */
@media (pointer: coarse) {
ul.wiktku-ac-results li {
padding: 6px 10px;
min-height: 2.2em;
font-size: 1em;
}
ul.wiktku-ac-results li:hover {
background: transparent;
}
ul.wiktku-ac-results li.wiktku-ac-active {
background: var(--wikt-palette-lightblue);
}
input.wiktku-ac-input {
/* >=16px stops iOS Safari zooming the page when the box is focused. */
font-size: 16px;
min-height: 2.2em;
}
}
/* Single column on narrow screens: the search box and its list take the full width. */
@media screen and (max-width: 720px) {
.wiktku-native-select-wrap,
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
}
/* A drop shadow is invisible against a dark page, so the border stands in for it. */
@media screen {
html.skin-theme-clientpref-night .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
}
/* ===================================================================
Word row with inline modifiers (makeModifierRow in util.js)
wikt.edit emits this markup, and both callers use it: DefSideBoxes' word lists and cite.js's
template dialog. The styles ship with the code rather than with either caller, so the dialog
looks the same wherever it is opened from.
=================================================================== */
/* ── IPA palette ─────────────────────────────────────────────
Shown by the wizard's pronunciation step and by DefSideBoxes' Bilêvkirin box. */
.wiktku-ipa-panel {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 0 0 12px;
margin-bottom: 8px;
overflow: hidden;
}
.wiktku-ipa-subtabs {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px 0;
border-bottom: 1px solid var(--wikt-rule-soft);
margin-bottom: 12px;
flex-wrap: wrap;
}
.wiktku-ipa-subtab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
padding: 7px 12px 9px;
border-bottom: 3px solid transparent;
border-radius: 8px 8px 0 0;
cursor: pointer;
}
.wiktku-ipa-subtab:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
.wiktku-ipa-subtab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
.wiktku-ipa-tplbtns {
margin-left: auto;
display: flex;
gap: 6px;
padding-bottom: 6px;
}
.wiktku-ipa-tplbtn {
font-family: var(--wikt-mono);
font-size: 12px;
padding: 6px 10px;
border-radius: 7px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
white-space: nowrap;
}
.wiktku-ipa-tplbtn:hover {
border-color: var(--wikt-accent);
}
.wiktku-ipa-grid {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 12px;
}
.wiktku-ipa-row {
display: flex;
align-items: flex-start;
gap: 10px;
}
.wiktku-ipa-rowlabel {
flex: 0 0 74px;
font-size: 11.5px;
color: var(--wikt-muted);
padding-top: 8px;
line-height: 1.3;
}
.wiktku-ipa-keys {
display: flex;
flex-wrap: wrap;
gap: 4px;
flex: 1 1 auto;
}
.wiktku-ipa-group-row {
padding-bottom: 4px;
}
select.wiktku-ipa-group-select {
width: 14em;
max-width: 100%;
}
.wiktku-ipa-key {
width: 30px;
height: 30px;
padding: 0;
border-radius: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-ink);
font-family: var(--wikt-mono);
font-size: 14px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.1s ease, border-color 0.1s ease;
}
.wiktku-ipa-key:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.wiktku-ipa-key:active {
transform: translateY(1px);
}
.wiktku-ipa-key {
width: auto;
min-width: 30px;
padding: 0 5px;
}
.wiktku-ipa-key-combining {
color: var(--wikt-ink-2);
}
.wiktku-ipa-row {
flex-direction: column;
gap: 3px;
}
.wiktku-ipa-rowlabel {
flex: 0 0 auto;
padding-top: 0;
}
.wiktku-ipa-key {
min-width: 34px;
height: 34px;
}
/* ── The shared box ───────────────────────────────────────────
posparams.js is drawn by both gadgets, so its fields cannot take their look from either skin's own
stylesheet -- in DefSideBoxes they came out as bare browser boxes with square corners. .wikt-box is
the look itself; the posparams selectors are listed with it so the card needs no markup change. A
skin's own id-scoped rules still outrank all of this where it wants something different. */
.wikt-box,
textarea.wiktku-posparams-text,
input.wiktku-posparams-field,
select.wiktku-posparams-cinsiyet-sel,
select.wiktku-posparams-jimar-sel,
select.wiktku-posparams-lektur-sel,
select.wiktku-posparams-lekturform-sel {
box-sizing: border-box;
padding: 6px 10px;
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.wikt-box:focus,
textarea.wiktku-posparams-text:focus,
input.wiktku-posparams-field:focus,
select.wiktku-posparams-cinsiyet-sel:focus,
select.wiktku-posparams-jimar-sel:focus,
select.wiktku-posparams-lektur-sel:focus,
select.wiktku-posparams-lekturform-sel:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template line is read as code, so it keeps its monospace. */
textarea.wiktku-posparams-text {
font-family: var(--wikt-mono);
}
/* ── Drag-to-resize handle ─────────────────────────────────
util.js replaces the browser's own resize corner with this one: the native handle drags in two
directions, keeps no height between visits and cannot be styled. Built by initManualResize(), so both
the wizard and DefSideBoxes get it. */
.wiktku-resize-wrap {
position: relative;
display: block;
}
/* Separate from .wiktku-resize-wrap (used only for textareas flagged with a linebreak); a plain
position:relative anchor for the backdrop, with no size of its own. */
.wiktku-linebreak-wrap {
position: relative;
display: block;
}
.wiktku-resize-grip {
position: absolute;
right: 2px;
bottom: 2px;
width: 14px;
height: 14px;
cursor: ns-resize;
touch-action: none;
opacity: 0.5;
z-index: 2;
/* Follows the field's own rounded corner rather than cutting a square one out of it. The gradient's
two-percent feather takes the jaggedness off the diagonal at the same time. */
border-bottom-right-radius: var(--wikt-r-field, 10px);
background-image: linear-gradient(135deg, transparent 0 49%, var(--wikt-palette-grey-9, #767676) 51% 100%);
background-repeat: no-repeat;
}
.wiktku-resize-grip:hover,
.wiktku-resize-grip:active {
opacity: 0.9;
}
/* ── Word list with modifier pills ───────────────────────────────────
Every word is its own always-visible row: a word box plus a "+" that attaches one of the 10 WORD_MODIFIERS
as a small pill shown under the box. A fresh empty row is appended once the current last one gets text. */
.dsb-wordlist {
margin: 0;
padding-left: 0;
list-style: none;
}
/* Two columns once the list is long enough that one column runs off the panel. Rows are self-contained
(box, its own chips, its own picker), so they tile without anything spanning the gutter. */
@media (min-width: 700px) {
.dsb-wordlist-2col {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 14px;
align-items: start;
}
}
/* Roomier between words than between a word and its own chips below it -- that difference is the only
thing saying which chip belongs to which box. */
.dsb-word-li {
margin-bottom: 12px;
}
.dsb-word-li:last-child {
margin-bottom: 0;
}
.dsb-word-mods {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 3px;
/* Indented under the box, clear of the ⊕/× buttons at its right edge. */
margin-left: 10px;
}
.dsb-word-mod-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 11.5px;
padding: 3px 4px 3px 10px;
cursor: pointer;
}
.dsb-word-mod-chip-remove {
border: 0;
background: transparent;
color: var(--wikt-accent-deep);
font-size: 12px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
opacity: 0.7;
}
.dsb-word-mod-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
.dsb-word-box-row {
display: flex;
align-items: center;
gap: 6px;
}
.dsb-word-box-row .dsb-word-input {
flex: 1 1 auto;
min-width: 0;
}
/* The word boxes are textareas built by util.js's makeModifierRow, not .dsb-input, so they were left
with the browser's default chrome while every other box in the panel had a border and a focus ring.
Same look as .dsb-input above, minus its min-width -- these size themselves from the flex row. */
.dsb-word-input {
box-sizing: border-box;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
vertical-align: top;
resize: vertical;
overflow: auto;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-word-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-word-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* Offered only while the box holds a comma that separates two words. */
.dsb-word-split-row {
margin-top: 4px;
}
.dsb-word-split {
flex: 0 0 auto;
box-sizing: border-box;
height: 26px;
padding: 0 8px;
line-height: 1;
font-size: 12px;
border: 1px solid var(--wikt-accent-edge);
border-radius: 7px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
transition: border-color 0.12s ease, color 0.12s ease;
}
.dsb-word-split:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-mod-btn,
.dsb-word-remove {
flex: 0 0 auto;
box-sizing: border-box;
width: 26px;
height: 26px;
padding: 0;
line-height: 1;
font-size: 13px;
border-radius: 7px;
cursor: pointer;
transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}
.dsb-mod-btn {
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
}
.dsb-mod-btn:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-word-remove {
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-muted);
}
.dsb-word-remove:hover {
border-color: var(--wikt-danger);
color: var(--wikt-danger);
}
.dsb-mod-picker {
display: flex;
flex-wrap: wrap;
gap: 6px;
width: 100%;
margin-top: 6px;
padding: 8px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-card);
box-shadow: none;
}
.dsb-mod-pill {
font-family: var(--wikt-sans);
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
white-space: nowrap;
padding: 5px 10px;
cursor: pointer;
transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.dsb-mod-pill:hover {
border-color: var(--wikt-accent);
background: var(--wikt-accent);
color: #fff;
}
.dsb-mod-pill:disabled,
.dsb-mod-pill.dsb-mod-pill-used {
background: var(--wikt-card-sunken);
border-color: var(--wikt-rule);
color: var(--wikt-muted);
cursor: not-allowed;
opacity: 0.7;
}
.dsb-mod-pill:disabled:hover,
.dsb-mod-pill.dsb-mod-pill-used:hover {
background: var(--wikt-card-sunken);
border-color: var(--wikt-rule);
color: var(--wikt-muted);
}
.dsb-mod-val-wrap {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
width: 100%;
margin-top: 6px;
}
/* The key's full name -- the chip itself shows only "crz:", so this is where it is spelled out. */
.dsb-mod-val-label {
flex: 0 0 auto;
font-size: 11.5px;
color: var(--wikt-ink-2);
white-space: nowrap;
}
.dsb-mod-val-input {
flex: 1 1 auto;
min-width: 0;
max-width: 16em;
}
.dsb-mod-confirm,
.dsb-mod-cancel {
flex: 0 0 auto;
box-sizing: border-box;
width: 26px;
height: 26px;
padding: 0;
line-height: 1;
font-size: 13px;
border-radius: 7px;
background: var(--wikt-accent-tint);
cursor: pointer;
}
.dsb-mod-confirm {
border: 1px solid var(--wikt-ok);
color: var(--wikt-ok);
}
.dsb-mod-cancel {
border: 1px solid var(--wikt-danger);
color: var(--wikt-danger);
}
/* Touch: the 26px controls are below the 44px minimum, so they grow with the row. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-mod-btn,
.dsb-word-remove,
.dsb-mod-confirm,
.dsb-mod-cancel {
width: 36px;
height: 36px;
font-size: 15px;
}
.dsb-mod-pill {
padding: 8px 12px;
}
.dsb-mod-val-input {
max-width: none;
}
}
/* Last in the file on purpose: these override the sizes set above, and a media query adds no specificity
of its own -- placed earlier, the plain .dsb-word-input rule would simply win. Under 16px iOS Safari
zooms the page whenever it focuses a field. */
@media (pointer: coarse) {
.dsb-word-input,
.dsb-mod-val-input,
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
font-size: 16px;
}
/* Full width on a touch device, whatever the viewport reports -- the max-width query above covers a
phone in mobile view, but one asked to render the desktop site reports ~980px and misses it. The
attribute selector is repeated because the 20em default is set through it, and it outranks a plain
class selector. Desktop keeps the 20em. */
.wiktku-native-select-wrap,
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
}
/* </nowiki> */
/* Language dropdown from wikt.edit/langs.js, the same as the translation adder's. */
.wiktku-langmenu {
position: fixed;
box-sizing: border-box;
z-index: 1100;
min-width: 14em;
max-height: 20em;
overflow-y: auto;
margin: 0;
padding: 2px 0;
list-style: none;
background: var(--background-color-base, #fff);
color: var(--color-base, #202122);
border: 1px solid var(--border-color-base, #a2a9b1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.wiktku-langmenu li {
margin: 0;
padding: 2px 8px;
cursor: pointer;
white-space: nowrap;
}
.wiktku-langmenu li:hover,
.wiktku-langmenu .wiktku-langmenu-active {
background: var(--background-color-progressive-subtle, #eaf3ff);
}
.wiktku-langmenu-code {
color: var(--color-subtle, #54595d);
font-size: 85%;
}
o065pyqkljhojgz0e3tp3lucit5wm9f
6865557
6865552
2026-10-23T20:17:05Z
Wikihez
7344
Use the dropdown from wikt.suggest
6865557
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.edit.css]]
*
* The searchable dropdown's styles, moved out of the wizard's stylesheet. util.js builds this widget
* (wrapNativeSelect) and DefSideBoxes' panels use it too, so leaving the styles with the wizard meant the
* part-of-speech picker rendered as a bare <ul> on any page where the wizard had not loaded. They belong
* with the code that emits them: wikt.edit, which both callers already depend on.
*
* Colour and spacing tokens come from wikt.core.css.
*/
/* ===================================================================
Searchable dropdown (wrapNativeSelect in start.js)
.wiktku-native-select-wrap > input.wiktku-ac-input (search box)
> ul.wiktku-ac-results (result list)
> select (hidden, holds the value)
=================================================================== */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
vertical-align: top;
}
/* Matches the width the native select would have had (see
select.wiktku-start-narrow). */
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: var(--wikt-r-field, 10px);
background: var(--wikt-palette-white);
color: inherit;
font-size: inherit;
font-family: inherit;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
input.wiktku-ac-input:focus {
border-color: var(--wikt-palette-blue-9);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9);
outline: 0;
}
input.wiktku-ac-input::placeholder {
color: var(--wikt-palette-grey-9);
opacity: 1;
}
/* Hide Safari/Chrome's built-in search decorations. */
input.wiktku-ac-input::-webkit-search-decoration,
input.wiktku-ac-input::-webkit-search-cancel-button,
input.wiktku-ac-input::-webkit-search-results-button {
-webkit-appearance: none;
appearance: none;
}
/* ── Result list ── */
/* Stays below MediaWiki's own .mw-notification-area (z-index: 10000) so the dropdown never covers a toast notification. */
ul.wiktku-ac-results {
position: absolute;
z-index: 500;
top: 100%;
bottom: auto;
left: 0;
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 13em;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: var(--wikt-r-field, 10px);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
/* Kategorî:Çavkanî search rows (see cite.js's buildCategoryTemplateSearch): the label/arrow shrink to
make room, the 🔗 link never does. */
.wiktku-cat-tpl-row {
gap: 8px;
}
.wiktku-cat-tpl-text {
flex: 1 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.wiktku-cat-tpl-arrow {
color: var(--wikt-muted);
}
.wiktku-cat-tpl-link {
flex: 0 0 auto;
width: 26px;
height: 26px;
display: grid;
place-items: center;
font-size: 12px;
line-height: 1;
border: 1px solid var(--wikt-accent-edge);
border-radius: 7px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
text-decoration: none;
}
.wiktku-cat-tpl-link:hover {
border-color: var(--wikt-accent);
}
.wiktku-cat-tpl-status {
display: block;
margin-top: 3px;
font-size: 12.5px;
color: var(--wikt-palette-grey-9, #767676);
}
ul.wiktku-ac-results li {
margin: 0;
padding: 4px 8px;
cursor: pointer;
font-size: 0.92em;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .wiktku-ac-active is the keyboard cursor; hover mirrors it; :active
covers the touch press state. */
ul.wiktku-ac-results li.wiktku-ac-active,
ul.wiktku-ac-results li:hover {
background: var(--wikt-palette-lightblue);
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* Touch devices: bigger tap targets, no sticky hover. */
@media (pointer: coarse) {
ul.wiktku-ac-results li {
padding: 6px 10px;
min-height: 2.2em;
font-size: 1em;
}
ul.wiktku-ac-results li:hover {
background: transparent;
}
ul.wiktku-ac-results li.wiktku-ac-active {
background: var(--wikt-palette-lightblue);
}
input.wiktku-ac-input {
/* >=16px stops iOS Safari zooming the page when the box is focused. */
font-size: 16px;
min-height: 2.2em;
}
}
/* Single column on narrow screens: the search box and its list take the full width. */
@media screen and (max-width: 720px) {
.wiktku-native-select-wrap,
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
}
/* A drop shadow is invisible against a dark page, so the border stands in for it. */
@media screen {
html.skin-theme-clientpref-night .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
}
/* ===================================================================
Word row with inline modifiers (makeModifierRow in util.js)
wikt.edit emits this markup, and both callers use it: DefSideBoxes' word lists and cite.js's
template dialog. The styles ship with the code rather than with either caller, so the dialog
looks the same wherever it is opened from.
=================================================================== */
/* ── IPA palette ─────────────────────────────────────────────
Shown by the wizard's pronunciation step and by DefSideBoxes' Bilêvkirin box. */
.wiktku-ipa-panel {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 0 0 12px;
margin-bottom: 8px;
overflow: hidden;
}
.wiktku-ipa-subtabs {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px 0;
border-bottom: 1px solid var(--wikt-rule-soft);
margin-bottom: 12px;
flex-wrap: wrap;
}
.wiktku-ipa-subtab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
padding: 7px 12px 9px;
border-bottom: 3px solid transparent;
border-radius: 8px 8px 0 0;
cursor: pointer;
}
.wiktku-ipa-subtab:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
.wiktku-ipa-subtab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
.wiktku-ipa-tplbtns {
margin-left: auto;
display: flex;
gap: 6px;
padding-bottom: 6px;
}
.wiktku-ipa-tplbtn {
font-family: var(--wikt-mono);
font-size: 12px;
padding: 6px 10px;
border-radius: 7px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
white-space: nowrap;
}
.wiktku-ipa-tplbtn:hover {
border-color: var(--wikt-accent);
}
.wiktku-ipa-grid {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 12px;
}
.wiktku-ipa-row {
display: flex;
align-items: flex-start;
gap: 10px;
}
.wiktku-ipa-rowlabel {
flex: 0 0 74px;
font-size: 11.5px;
color: var(--wikt-muted);
padding-top: 8px;
line-height: 1.3;
}
.wiktku-ipa-keys {
display: flex;
flex-wrap: wrap;
gap: 4px;
flex: 1 1 auto;
}
.wiktku-ipa-group-row {
padding-bottom: 4px;
}
select.wiktku-ipa-group-select {
width: 14em;
max-width: 100%;
}
.wiktku-ipa-key {
width: 30px;
height: 30px;
padding: 0;
border-radius: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-ink);
font-family: var(--wikt-mono);
font-size: 14px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.1s ease, border-color 0.1s ease;
}
.wiktku-ipa-key:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.wiktku-ipa-key:active {
transform: translateY(1px);
}
.wiktku-ipa-key {
width: auto;
min-width: 30px;
padding: 0 5px;
}
.wiktku-ipa-key-combining {
color: var(--wikt-ink-2);
}
.wiktku-ipa-row {
flex-direction: column;
gap: 3px;
}
.wiktku-ipa-rowlabel {
flex: 0 0 auto;
padding-top: 0;
}
.wiktku-ipa-key {
min-width: 34px;
height: 34px;
}
/* ── The shared box ───────────────────────────────────────────
posparams.js is drawn by both gadgets, so its fields cannot take their look from either skin's own
stylesheet -- in DefSideBoxes they came out as bare browser boxes with square corners. .wikt-box is
the look itself; the posparams selectors are listed with it so the card needs no markup change. A
skin's own id-scoped rules still outrank all of this where it wants something different. */
.wikt-box,
textarea.wiktku-posparams-text,
input.wiktku-posparams-field,
select.wiktku-posparams-cinsiyet-sel,
select.wiktku-posparams-jimar-sel,
select.wiktku-posparams-lektur-sel,
select.wiktku-posparams-lekturform-sel {
box-sizing: border-box;
padding: 6px 10px;
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.wikt-box:focus,
textarea.wiktku-posparams-text:focus,
input.wiktku-posparams-field:focus,
select.wiktku-posparams-cinsiyet-sel:focus,
select.wiktku-posparams-jimar-sel:focus,
select.wiktku-posparams-lektur-sel:focus,
select.wiktku-posparams-lekturform-sel:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The template line is read as code, so it keeps its monospace. */
textarea.wiktku-posparams-text {
font-family: var(--wikt-mono);
}
/* ── Drag-to-resize handle ─────────────────────────────────
util.js replaces the browser's own resize corner with this one: the native handle drags in two
directions, keeps no height between visits and cannot be styled. Built by initManualResize(), so both
the wizard and DefSideBoxes get it. */
.wiktku-resize-wrap {
position: relative;
display: block;
}
/* Separate from .wiktku-resize-wrap (used only for textareas flagged with a linebreak); a plain
position:relative anchor for the backdrop, with no size of its own. */
.wiktku-linebreak-wrap {
position: relative;
display: block;
}
.wiktku-resize-grip {
position: absolute;
right: 2px;
bottom: 2px;
width: 14px;
height: 14px;
cursor: ns-resize;
touch-action: none;
opacity: 0.5;
z-index: 2;
/* Follows the field's own rounded corner rather than cutting a square one out of it. The gradient's
two-percent feather takes the jaggedness off the diagonal at the same time. */
border-bottom-right-radius: var(--wikt-r-field, 10px);
background-image: linear-gradient(135deg, transparent 0 49%, var(--wikt-palette-grey-9, #767676) 51% 100%);
background-repeat: no-repeat;
}
.wiktku-resize-grip:hover,
.wiktku-resize-grip:active {
opacity: 0.9;
}
/* ── Word list with modifier pills ───────────────────────────────────
Every word is its own always-visible row: a word box plus a "+" that attaches one of the 10 WORD_MODIFIERS
as a small pill shown under the box. A fresh empty row is appended once the current last one gets text. */
.dsb-wordlist {
margin: 0;
padding-left: 0;
list-style: none;
}
/* Two columns once the list is long enough that one column runs off the panel. Rows are self-contained
(box, its own chips, its own picker), so they tile without anything spanning the gutter. */
@media (min-width: 700px) {
.dsb-wordlist-2col {
display: grid;
grid-template-columns: 1fr 1fr;
column-gap: 14px;
align-items: start;
}
}
/* Roomier between words than between a word and its own chips below it -- that difference is the only
thing saying which chip belongs to which box. */
.dsb-word-li {
margin-bottom: 12px;
}
.dsb-word-li:last-child {
margin-bottom: 0;
}
.dsb-word-mods {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-top: 3px;
/* Indented under the box, clear of the ⊕/× buttons at its right edge. */
margin-left: 10px;
}
.dsb-word-mod-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 11.5px;
padding: 3px 4px 3px 10px;
cursor: pointer;
}
.dsb-word-mod-chip-remove {
border: 0;
background: transparent;
color: var(--wikt-accent-deep);
font-size: 12px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
opacity: 0.7;
}
.dsb-word-mod-chip-remove:hover {
color: var(--wikt-danger);
opacity: 1;
}
.dsb-word-box-row {
display: flex;
align-items: center;
gap: 6px;
}
.dsb-word-box-row .dsb-word-input {
flex: 1 1 auto;
min-width: 0;
}
/* The word boxes are textareas built by util.js's makeModifierRow, not .dsb-input, so they were left
with the browser's default chrome while every other box in the panel had a border and a focus ring.
Same look as .dsb-input above, minus its min-width -- these size themselves from the flex row. */
.dsb-word-input {
box-sizing: border-box;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
vertical-align: top;
resize: vertical;
overflow: auto;
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-word-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-word-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* Offered only while the box holds a comma that separates two words. */
.dsb-word-split-row {
margin-top: 4px;
}
.dsb-word-split {
flex: 0 0 auto;
box-sizing: border-box;
height: 26px;
padding: 0 8px;
line-height: 1;
font-size: 12px;
border: 1px solid var(--wikt-accent-edge);
border-radius: 7px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
transition: border-color 0.12s ease, color 0.12s ease;
}
.dsb-word-split:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-mod-btn,
.dsb-word-remove {
flex: 0 0 auto;
box-sizing: border-box;
width: 26px;
height: 26px;
padding: 0;
line-height: 1;
font-size: 13px;
border-radius: 7px;
cursor: pointer;
transition: border-color 0.12s ease, color 0.12s ease, background 0.12s ease;
}
.dsb-mod-btn {
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
}
.dsb-mod-btn:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
.dsb-word-remove {
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-muted);
}
.dsb-word-remove:hover {
border-color: var(--wikt-danger);
color: var(--wikt-danger);
}
.dsb-mod-picker {
display: flex;
flex-wrap: wrap;
gap: 6px;
width: 100%;
margin-top: 6px;
padding: 8px;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-card);
box-shadow: none;
}
.dsb-mod-pill {
font-family: var(--wikt-sans);
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
white-space: nowrap;
padding: 5px 10px;
cursor: pointer;
transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
}
.dsb-mod-pill:hover {
border-color: var(--wikt-accent);
background: var(--wikt-accent);
color: #fff;
}
.dsb-mod-pill:disabled,
.dsb-mod-pill.dsb-mod-pill-used {
background: var(--wikt-card-sunken);
border-color: var(--wikt-rule);
color: var(--wikt-muted);
cursor: not-allowed;
opacity: 0.7;
}
.dsb-mod-pill:disabled:hover,
.dsb-mod-pill.dsb-mod-pill-used:hover {
background: var(--wikt-card-sunken);
border-color: var(--wikt-rule);
color: var(--wikt-muted);
}
.dsb-mod-val-wrap {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 6px;
width: 100%;
margin-top: 6px;
}
/* The key's full name -- the chip itself shows only "crz:", so this is where it is spelled out. */
.dsb-mod-val-label {
flex: 0 0 auto;
font-size: 11.5px;
color: var(--wikt-ink-2);
white-space: nowrap;
}
.dsb-mod-val-input {
flex: 1 1 auto;
min-width: 0;
max-width: 16em;
}
.dsb-mod-confirm,
.dsb-mod-cancel {
flex: 0 0 auto;
box-sizing: border-box;
width: 26px;
height: 26px;
padding: 0;
line-height: 1;
font-size: 13px;
border-radius: 7px;
background: var(--wikt-accent-tint);
cursor: pointer;
}
.dsb-mod-confirm {
border: 1px solid var(--wikt-ok);
color: var(--wikt-ok);
}
.dsb-mod-cancel {
border: 1px solid var(--wikt-danger);
color: var(--wikt-danger);
}
/* Touch: the 26px controls are below the 44px minimum, so they grow with the row. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-mod-btn,
.dsb-word-remove,
.dsb-mod-confirm,
.dsb-mod-cancel {
width: 36px;
height: 36px;
font-size: 15px;
}
.dsb-mod-pill {
padding: 8px 12px;
}
.dsb-mod-val-input {
max-width: none;
}
}
/* Last in the file on purpose: these override the sizes set above, and a media query adds no specificity
of its own -- placed earlier, the plain .dsb-word-input rule would simply win. Under 16px iOS Safari
zooms the page whenever it focuses a field. */
@media (pointer: coarse) {
.dsb-word-input,
.dsb-mod-val-input,
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
font-size: 16px;
}
/* Full width on a touch device, whatever the viewport reports -- the max-width query above covers a
phone in mobile view, but one asked to render the desktop site reports ~980px and misses it. The
attribute selector is repeated because the 20em default is set through it, and it outranks a plain
class selector. Desktop keeps the 20em. */
.wiktku-native-select-wrap,
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input,
ul.wiktku-ac-results {
width: 100%;
max-width: 100%;
}
}
/* </nowiki> */
2hkw73cqim3mibvoowcfzb4v63q9fen
MediaWiki:Gadget-wikt.cite.js
8
1359166
6865537
6865400
2026-10-23T19:43:16Z
Wikihez
7344
Language fields: the translation adder's dropdown (names and codes searched) instead of a datalist
6865537
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.cite.js]]
*
* Citations for both add-examples and the wizard: jêder-* templates, [[Kategorî:Çavkanî]] templates,
* Citoid, and the "#*" line they become. fieldSpecs() returns plain field descriptions because the two
* callers draw them differently (OOUI vs the wizard's own grid); everything else is shared. Also the
* template-param dialog (openTemplateDialog), used the same way by DefSideBoxes and start.js.
*
* The only caller of window.wikt.citoid, which the citoid-helper gadget owns.
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var wt = edit.wt;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
var tdText = wt.tdText;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var jederDefs = edit.data.jederDefs || {};
// Guarded: between deploying this file and the Gadgets-definition line that adds etimon.js to the
// package, a cached startup module would have this require throw and take the whole gadget with it.
// Without the builder, an {{etîmon}} call falls through to the generic dialog below.
var etimon = null;
try {
etimon = require("./wikt.cite/etimon.js");
} catch (e) {
etimon = null;
}
var api = new mw.Api();
// Sentinel _currentJederKey / dropdown value for "pick a template from Kategorî:Çavkanî". Not a real
// jederDefs key. Both gadgets use this same string, so a draft saved by one is read by the other.
var CATEGORY_KEY = "__category__";
// Sentinel for "the example box holds a finished template; write it out untouched".
var RAW_KEY = "__raw__";
// Covered by the form's own dedicated boxes, so they are never drawn again as generic template fields.
var COVERED_PARAMS = ["jêgirtin", "jg", "t", "tr"];
// A Kategorî:Çavkanî template whose TemplateData labels a parameter exactly this is fed the language
// from the language dropdown instead of showing a box for it. Unlike jêder-*, an arbitrary template has
// no "parameter 1 is the language" convention, so nothing is assumed positionally.
var LANG_LABEL = "koda zimanî";
// Field key -> {span, multiline}, taken from whichever jêder-* template defines it first. Most
// Kategorî:Çavkanî templates reuse the same bibliographic parameter names, so "sernav" comes out
// full-width and multiline there too rather than as a generic one-line box.
var SIZE_BY_KEY = {};
Object.keys(jederDefs).forEach(function (jederKey) {
(jederDefs[jederKey].fields || []).forEach(function (field) {
if (!SIZE_BY_KEY[field.key]) {
SIZE_BY_KEY[field.key] = {
span: field.span || 4,
multiline: !!field.multiline,
width: field.width || ""
};
}
});
});
// Field pairs Modul:quote refuses together ("Divê tenê yek ji |tarîx= an |sal= were dayîn"): a full
// date and a bare year say the same thing twice. Both are offered because either may be all a source
// gives, so the forms clear one when the other is filled rather than letting the citation break.
var EXCLUSIVE_PAIRS = [["sal", "tarîx"]];
function exclusivePartner(key) {
var found = null;
EXCLUSIVE_PAIRS.forEach(function (pair) {
if (pair[0] === key) {
found = pair[1];
} else if (pair[1] === key) {
found = pair[0];
}
});
return found;
}
function fieldSize(key) {
return SIZE_BY_KEY[key] || { span: 4, multiline: false, width: "" };
}
// ---- text helpers -------------------------------------------------------------------------------
// The entry name as a pattern. A Kurdish word ending in -î loses the circumflex once something is
// attached to it ("safî" is written "safiye"), so a title ending that way matches either spelling --
// otherwise the name is simply never found in the quotation.
function titlePattern(title) {
var escaped = escapeRegExp(String(title || ""));
return /î$/i.test(escaped) ? escaped.slice(0, -1) + "[îi]" : escaped;
}
// Wraps the entry's own name in bold wherever it appears in the quotation, which is how quotations are
// written here, and reports whether the name ends up bold so a caller can say so.
//
// The name may carry an attached suffix ("celbê"), so the match runs on to the end of the word. A word
// ends at whitespace, punctuation or markup: excluding markup is what keeps "celb<br>me’şûq" from
// bolding as one lump. The typographic apostrophe stays a word character -- it is one inside Kurdish
// words like "me’şûq".
function boldenPageTitle(text, title) {
var source = String(text || "");
var escapedTitle = titlePattern(title);
var suffixPattern = "([^\\s.,;:?!()'\"<>\\[\\]{}|=/\\\\«»„“”–—]+)?";
var titleRe = new RegExp("(" + escapedTitle + suffixPattern + ")", "ig");
var titleAnywhere = new RegExp(escapedTitle, "i");
// Whatever already sits between ''' marks is copied through untouched, so a quotation somebody
// bolded by hand -- or one an earlier run bolded -- never collects a second pair.
var boldSpan = /'''[\s\S]*?'''/g;
var out = "";
var last = 0;
var alreadyBold = false;
var span;
while ((span = boldSpan.exec(source)) !== null) {
out += source.slice(last, span.index).replace(titleRe, "'''$1'''");
if (titleAnywhere.test(span[0])) {
alreadyBold = true;
}
out += span[0];
last = span.index + span[0].length;
}
out += source.slice(last).replace(titleRe, "'''$1'''");
return { text: out, bolded: out !== source || alreadyBold };
}
// A literal newline would end the single-line "#*" wikitext, so display text keeps its break as <br>...
function nlToBr(text) {
return String(text || "").replace(/\r\n|\r|\n/g, "<br>");
}
// ...while a plain parameter value (an author, a title) just loses it.
function nlToSpace(text) {
return String(text || "").replace(/\s*(?:\r\n|\r|\n)\s*/g, " ");
}
// Bolds the entry name inside a pasted citation's jêgirtin=/jg= value only, so a book title or author
// containing it is left alone. With no such parameter nothing is bolded: running the plain-text bolder
// over wikitext would wrap the braces too.
function boldQuoteParam(text, title) {
var match = /(\|\s*(?:jêgirtin|jg)\s*=)([\s\S]*?)(?=\|[^|=]*=|\}\}\s*$)/.exec(text);
if (!match) {
return text;
}
var bolded = boldenPageTitle(match[2], title).text;
return text.slice(0, match.index) + match[1] + bolded + text.slice(match.index + match[0].length);
}
// "name — description" for each Kategorî:Çavkanî option in a <select>, from the description half of
// the same TemplateData every citation picker already fetches -- so a search box built over these
// options can also match on what a template documents itself as doing, not only its name. An
// undescribed option, or a failed fetch, just keeps its plain label; never blocks on this.
// A self-contained "search Kategorî:Çavkanî" combo: a text input plus its own results list, matching
// add-examples' own jêgirtin picker -- each row shows its label, a " → target" arrow when a redirect's
// label differs from its target, and a 🔗 link to the canonical template's own page. opts.onPick(entry)
// fires when a row is chosen; the input is then left holding the plain label, not "label → target", so
// re-focusing it to search again doesn't start from that arrow text. opts.describe: true also fills in
// each visible row's own TemplateData description once it loads (batched, so scrolling through matches
// costs a handful of requests, not one per row).
function buildCategoryTemplateSearch(opts) {
opts = opts || {};
var shell = util.buildSearchList({
placeholder: opts.placeholder || util.msg.searchTemplate
});
var $input = shell.$input;
var $results = shell.$results;
var $status = $("<span class='wiktku-cat-tpl-status'></span>").text(util.msg.templatesLoading);
var list = [];
function decorate($text, target, wanted) {
if (!wanted) {
return;
}
edit.wt.fetchTemplateData(target).then(function (doc) {
var desc = doc && edit.wt.tdText(doc.description);
if (desc && $text.closest("body").length) {
$text.append(document.createTextNode(" — " + edit.wt.shortDesc(desc)));
}
});
}
function showResults(query) {
var q = $.trim(query || "").toLowerCase();
$results.empty();
var matched = q ? list.filter(function (entry) {
return entry.label.toLowerCase().indexOf(q) !== -1;
}) : list;
if (!matched.length) {
shell.close();
return;
}
var shown = matched.slice(0, 50);
// Only what a search has narrowed to: an empty box is the whole category, and describing its
// first fifty would ask the wiki about templates nobody has looked for.
var describing = !!opts.describe && !!q;
if (describing) {
edit.wt.fetchTemplateDataBatch(shown.map(function (entry) { return entry.target; }));
}
shown.forEach(function (entry) {
var $li = $("<li class='wiktku-cat-tpl-row'></li>");
var $text = $("<span class='wiktku-cat-tpl-text'></span>").text(entry.label);
if (entry.label !== entry.target) {
$text.append($("<span class='wiktku-cat-tpl-arrow'></span>").text(" → " + entry.target));
}
decorate($text, entry.target, describing);
var $link = $("<a class='wiktku-cat-tpl-link' target='_blank' rel='noopener'>🔗</a>")
.attr("title", util.msg.openTemplatePage)
.attr("href", util.tplPageUrl(entry.target));
// stopPropagation only -- preventDefault would stop the link from actually navigating.
$link.on("mousedown click", function (e) { e.stopPropagation(); });
$li.append($text, $link);
shell.bindPick($li, function () {
shell.close();
$input.val(entry.label);
opts.onPick(entry);
});
$results.append($li);
});
shell.open();
}
$input.on("input focus", function () {
showResults($(this).val());
});
$input.on("blur", function () {
setTimeout(shell.close, 200); // lets the pick handler fire first
});
fetchCategoryTemplates().then(function (l) {
list = l;
$status.text("");
}, function () {
$status.text(util.msg.templatesFailed);
});
return {
$element: $("<span class='wiktku-native-select-wrap wiktku-cat-tpl-picker'></span>").append($input, $results, $status),
$input: $input,
$status: $status
};
}
// ---- Kategorî:Çavkanî -----------------------------------------------------------------------------
// The category's template members plus their redirects, each redirect its own searchable entry pointing
// at the same canonical target, so a template is findable by any of its names. Cached for the session.
var catTemplatesPromise = null;
function fetchCategoryTemplates() {
if (catTemplatesPromise) {
return catTemplatesPromise;
}
catTemplatesPromise = api.get({
action: "query",
generator: "categorymembers",
gcmtitle: "Kategorî:Çavkanî",
gcmnamespace: 10,
gcmlimit: "max",
prop: "redirects",
rdnamespace: 10,
rdlimit: "max",
formatversion: 2
}).then(function (data) {
var list = [];
((data.query && data.query.pages) || []).forEach(function (page) {
var target = page.title.replace(/^[^:]+:/, "");
list.push({ label: target, target: target });
(page.redirects || []).forEach(function (redirect) {
list.push({ label: redirect.title.replace(/^[^:]+:/, ""), target: target });
});
});
list.sort(function (a, b) {
return a.label.localeCompare(b.label, mw.config.get("wgUserLanguage"));
});
return list;
}, function () {
return [];
});
return catTemplatesPromise;
}
// ---- template-param dialog: fills a {{Name|...}} call from its own TemplateData -------------------
// Shared by DefSideBoxes (a fresh insert from Etîmolojî/Çavkanî, or double-clicking an existing chip
// to edit it) and start.js's own Etîmolojî/Çavkanî pickers -- one dialog, so all of them read the same
// TemplateData the same way.
// OOUI's own dialog stack -- the same widgetry VisualEditor's template dialog is built on.
var citeWindowManager = null;
function templateDialogManager() {
if (!citeWindowManager) {
citeWindowManager = new OO.ui.WindowManager();
$(document.body).append(citeWindowManager.$element);
}
return citeWindowManager;
}
// Opens one template call in a modal built from its TemplateData. Resolves with the edited wikitext, or
// null when the user cancels.
// One at a time: a second open while one is loading or showing (a chip clicked again before its dialog
// appeared) stacks a window the manager does not track, and that window's buttons do nothing. The
// second call resolves null, as a cancel does.
var templateDialogBusy = false;
function openTemplateDialog(callWikitext) {
var done = $.Deferred();
var call = parseTemplateCalls(callWikitext).calls[0];
if (!call || templateDialogBusy) {
return done.resolve(null).promise();
}
// {{etîmon}} has no form TemplateData can describe -- a chain of steps, each with its own words
// and their own modifiers -- so it has a builder of its own, on the same contract as this dialog.
if (etimon && etimon.isEtimonName(call.name)) {
return etimon.openDialog(callWikitext);
}
templateDialogBusy = true;
done.always(function () {
templateDialogBusy = false;
});
$.when(
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"]),
edit.wt.fetchTemplateData(call.name),
templateAliases(call.name)
).then(function (_loaded, td, aliases) {
var result = null;
// The parent is captured rather than reached through TplDialog.super: "super" is a reserved word,
// and OOjs has moved between .super and .parent for it over the years.
var ParentDialog = OO.ui.ProcessDialog;
function TplDialog(config) {
ParentDialog.call(this, config);
}
OO.inheritClass(TplDialog, ParentDialog);
TplDialog.static.name = "citeTemplateDialog";
TplDialog.static.title = "{{" + call.name + "}}";
TplDialog.static.actions = [
{ action: "save", label: "Biguhêre", flags: ["primary", "progressive"] },
{ action: "remove", label: "Jê bibe", flags: "destructive" },
{ label: util.msg.cancel, flags: "safe" }
];
TplDialog.prototype.initialize = function () {
ParentDialog.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.form = buildTemplateParamForm(call, td, aliases);
this.panel.$element.append(this.form.$element);
this.$body.append(this.panel.$element);
};
TplDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
TplDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save" || action === "remove") {
return new OO.ui.Process(function () {
result = action === "remove" ? "" : dialog.form.values();
dialog.close();
});
}
return ParentDialog.prototype.getActionProcess.call(this, action);
};
// Marks the page while this is showing, so a floating box of ours (DefSideBoxes' queue) does
// not sit over a dialog the page's stacking order puts underneath it.
$(document.body).addClass("wikt-dialog-open");
done.always(function () {
$(document.body).removeClass("wikt-dialog-open");
});
var manager = templateDialogManager();
var dialog = new TplDialog({ size: "medium" });
manager.addWindows([dialog]);
var instance = manager.openWindow(dialog);
instance.closed.then(function () {
manager.clearWindows();
done.resolve(result);
});
instance.opening.fail(function () {
manager.clearWindows();
done.resolve(null);
});
}, function () {
done.resolve(null);
});
return done.promise();
}
// TemplateData descriptions are plain text, but authors write wikilinks in them; render those as links.
function tdDescription(text) {
var $out = $("<span></span>");
var re = /\[\[([^\[\]|]+)(?:\|([^\[\]]*))?\]\]/g;
var last = 0;
var m;
while ((m = re.exec(text)) !== null) {
if (m.index > last) {
$out.append(document.createTextNode(text.slice(last, m.index)));
}
$out.append($("<a></a>").attr("href", mw.util.getUrl(m[1])).text(m[2] || m[1]));
last = re.lastIndex;
}
if (last < text.length) {
$out.append(document.createTextNode(text.slice(last)));
}
return $out;
}
// A parameter whose documentation says so takes "kelîme<t:...>" inline modifiers, so it gets the pill
// widget instead of a plain box. TemplateData has no field for this, hence the marker in the description.
var INLINE_MODIFIER_MARKER = "WF:Guherkerên rêzê";
function acceptsInlineModifiers(doc) {
return tdText(doc && doc.description).indexOf(INLINE_MODIFIER_MARKER) !== -1;
}
// A language-code parameter, per its own label.
function isLanguageParam(doc) {
return /ziman/i.test(tdText(doc && doc.label));
}
// alias -> documented name, from the TemplateData's own "aliases" lists plus any given (aliases from
// templateAliases(), for a template whose baked parameter list carries none).
function aliasMap(td, extra) {
var map = $.extend({}, extra || {});
$.each((td && td.params) || {}, function (key, doc) {
((doc && doc.aliases) || []).forEach(function (alias) {
map[alias] = key;
});
});
return map;
}
// The call's values under the documented names, as {values, used, spelling}: a value written under an
// alias ("cuda=" for "alt") fills the documented field, and spelling remembers the alias so the call is
// written back the way the page has it. A call giving both names keeps both as they are.
function documentedValues(params, docParams, aliases) {
var parsed = splitCallParams(params);
var spelling = {};
var used = [];
parsed.used.forEach(function (key) {
var doc = aliases[key];
if (doc && docParams[doc] && parsed.used.indexOf(doc) === -1 && !spelling[doc]) {
parsed.values[doc] = parsed.values[key];
spelling[doc] = key;
used.push(doc);
} else {
used.push(key);
}
});
return { values: parsed.values, used: used, spelling: spelling };
}
function buildTemplateParamForm(call, td, aliases) {
var docParams = (td && td.params) || {};
var parsed = documentedValues(call.params, docParams, aliasMap(td, aliases));
var keys = (td && td.paramOrder) ? td.paramOrder.slice() : Object.keys(docParams);
parsed.used.forEach(function (k) {
if (keys.indexOf(k) === -1) {
keys.push(k);
}
});
var $element = $("<div class='dsb-tpl-form'></div>");
var readers = {}; // key -> () => the parameter's current text
$element.append($("<div class='dsb-tpl-name'></div>").append(
$("<a></a>").attr("href", mw.util.getUrl("Şablon:" + call.name))
.attr("target", "_blank").text("{{" + call.name + "}}")
));
var desc = tdText(td && td.description);
if (desc) {
$element.append($("<div class='dsb-tpl-desc'></div>").append(tdDescription(desc)));
}
keys.forEach(function (key) {
var doc = docParams[key] || {};
var value = parsed.values[key] || "";
var $field;
if (acceptsInlineModifiers(doc)) {
// The same word row the sense lists use: plain text plus removable "<t:...>" pills.
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: tdText(doc.label) || key,
modTitle: util.msg.addModifier,
removeTitle: "Vê nirxê jê bibe",
removeModTitle: util.msg.removeModifier
});
row.setRaw(value);
// A <ul> so the row's own "close other pickers" lookup has something to scope to.
$field = $("<ul class='dsb-wordlist dsb-tpl-modifier-list'></ul>").append(row.$li);
readers[key] = row.getRaw;
} else {
var $input = $("<input type='text' class='dsb-input'>").val(value);
if (isLanguageParam(doc)) {
$input.attr("placeholder", "koda zimanî…");
langs.picker($input);
} else {
var suggested = doc.suggestedvalues || [];
if (suggested.length) {
var listId = "dsb-sv-" + call.name.replace(/[^A-Za-z0-9]/g, "") + "-" + key;
var $list = $("<datalist></datalist>").attr("id", listId);
suggested.forEach(function (v) {
$list.append($("<option></option>").val(v));
});
$input.attr("list", listId);
$element.append($list);
}
}
$field = $input;
readers[key] = function () {
return $input.val();
};
}
var $label = $("<label class='dsb-adv-label'></label>").text(tdText(doc.label) || key);
if (!docParams[key]) {
$label.append($("<span class='dsb-tpl-undocumented'></span>").text(" (ne di belgeyê de)"));
}
var $row = $("<div class='dsb-tpl-param-row'></div>").append($label, $field);
var help = tdText(doc.description);
if (help) {
$row.append($("<div class='dsb-tpl-help'></div>").append(tdDescription(help)));
}
if (doc.deprecated) {
$row.addClass("dsb-tpl-deprecated");
}
$element.append($row);
});
return {
$element: $element,
values: function () {
var vals = {};
var written = keys.map(function (k) {
var name = parsed.spelling[k] || k;
vals[name] = $.trim(readers[k]() || "");
return name;
});
return buildTemplateCall(call.name, vals, written);
}
};
}
// The documented parameters of one template (name without the "Şablon:" prefix), in its own
// paramOrder; [] when it has no TemplateData. The fetch, the shaping and the cache all live in
// wikt.edit, so the wizard, DefSideBoxes and this module share one request and one copy per template.
//
// The key stands in for a missing label, because these become form fields and a field needs something
// written on it.
// A template's parameters: its TemplateData, or -- for one with none, like most Kategorî:Çavkanî
// templates -- every {{{name}}} its own source reads.
function templateParams(name) {
return edit.wt.templateDoc(name).then(function (td) {
var params = ((td && td.params) || []).map(function (p) {
return {
key: p.key,
label: p.label || p.key,
required: p.required,
deprecated: p.deprecated
};
});
return params.length ? params : sourceParams(name);
});
}
var sourceParamsCache = {};
function sourceParams(name) {
if (!sourceParamsCache[name]) {
sourceParamsCache[name] = new mw.Api().get({
action: "query", prop: "revisions", titles: "Template:" + name, rvprop: "content", rvslots: "main",
redirects: 1, formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
var source = page.revisions ? page.revisions[0].slots.main.content : "";
var keys = [];
var re = /\{\{\{\s*([^{}|]+?)\s*[|}]/g;
var m;
while ((m = re.exec(source)) !== null) {
if (!/^\d+$/.test(m[1]) && keys.indexOf(m[1]) === -1) {
keys.push(m[1]);
}
}
return keys.map(function (key) {
return { key: key, label: key, required: false, deprecated: false };
});
}, function () {
return [];
});
}
return sourceParamsCache[name];
}
// alias -> the name a template's TemplateData documents it under ({r: "rûpel"} for jêder-kitêb). Asked
// of the wiki itself: the parameter lists baked into data-main.json carry no aliases.
var aliasCache = {};
function templateAliases(name) {
if (!aliasCache[name]) {
aliasCache[name] = new mw.Api().get({
action: "templatedata", titles: "Template:" + name, redirects: 1, formatversion: 2
}).then(function (data) {
var map = {};
$.each(data.pages || {}, function (_, page) {
$.each(page.params || {}, function (key, p) {
(p.aliases || []).forEach(function (alias) {
map[alias] = key;
});
});
});
return map;
}, function () {
return {};
});
}
return aliasCache[name];
}
// What to draw for one Kategorî:Çavkanî template: a field per documented parameter, minus the ones the
// form already has boxes for and minus the language parameter, which is filled from the dropdown.
// A template with no TemplateData at all falls back to a lone "Rûpel" box, which is what such citations
// almost always need ({{Şablon|rûpel=12|jêgirtin=…}}).
function fieldSpecs(name) {
return templateParams(name).then(function (params) {
var langParamKey = null;
(params || []).forEach(function (p) {
if ((p.label || "").trim().toLowerCase() === LANG_LABEL) {
langParamKey = p.key;
}
});
var filtered = (params || []).filter(function (p) {
return COVERED_PARAMS.indexOf(p.key) === -1 && p.key !== langParamKey;
});
var list = filtered.length ? filtered : [{ key: "rûpel", label: "Rûpel", required: false }];
return {
langParamKey: langParamKey,
fields: list.map(function (p) {
var size = fieldSize(p.key);
return {
key: p.key,
label: p.label,
required: p.required,
span: size.span,
width: size.width,
multiline: size.multiline
};
})
};
});
}
// The same list for a jêder-* template, straight out of the shared table.
function jederFieldSpecs(jederKey) {
var def = jederDefs[jederKey];
return def ? (def.fields || []).slice() : [];
}
// ---- Citoid --------------------------------------------------------------------------------------
// Today, as Modul:quote wants it (it renders "2024-05-05" as "5 gulan 2024").
function today() {
var d = new Date();
function pad(n) { return (n < 10 ? "0" : "") + n; }
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}
// Carries across the parts of a Citoid result that citoid-helper's own mapping drops: the access date
// and the identifiers for the fields the forms have only just gained. Applied here rather than in
// citoid-helper so it reaches both gadgets without an edit to the MediaWiki: gadget page.
function enrichCitoidFields(fields, raw) {
fields = fields || {};
if (fields.url && !fields["tarîxa-gihiştinê"]) {
// Citoid reports when it actually fetched the page ("accessDate": "2026-09-03"), so use that
// rather than the clock -- they differ whenever the answer came from a cache. today() is only
// the fallback for a result that carries no accessDate.
var accessed = raw && (raw.accessDate || raw.accessdate);
fields["tarîxa-gihiştinê"] = (accessed && String(accessed).slice(0, 10)) || today();
}
if (raw) {
var issn = Array.isArray(raw.ISSN) ? raw.ISSN[0] : raw.ISSN;
if (issn && !fields.issn) {
fields.issn = issn;
}
var doi = Array.isArray(raw.DOI) ? raw.DOI[0] : raw.DOI;
if (doi && !fields.doi) {
fields.doi = doi;
}
if (raw.volume && !fields.cild) {
fields.cild = raw.volume;
}
}
return fields;
}
// One URL/DOI/ISBN -> {jederKey, fields}. The lookup itself lives in the citoid-helper gadget; this
// wrapper exists so neither caller has to know that, and so a missing gadget fails in one place.
function fetchFromCitoid(query) {
if (!(window.wikt && window.wikt.citoid && window.wikt.citoid.fetchJederFields)) {
return $.Deferred().reject("Amûra Citoidê ne barkirî ye.").promise();
}
return window.wikt.citoid.fetchJederFields(query).then(function (result) {
result.fields = enrichCitoidFields(result.fields, result.raw);
return result;
});
}
// Re-derives the fields for a different jêder-* template than the one citoid detected, enriched the
// same way -- used when the person had already chosen a template and we keep their choice.
// The form's link box stands in for a jêder-* template's own url= when that is left empty.
function withLinkAsUrl(jederKey, fields, link) {
var def = jederDefs[jederKey];
var hasUrl = !!def && (def.fields || []).some(function (field) {
return field.key === "url";
});
if (hasUrl && !fields.url && /^https?:\/\//.test(link || "")) {
fields.url = link;
}
return fields;
}
function citoidFieldsFor(jederKey, raw, query) {
if (!(window.wikt && window.wikt.citoid && window.wikt.citoid.dataToJederFields)) {
return {};
}
return enrichCitoidFields(window.wikt.citoid.dataToJederFields(jederKey, raw, query), raw);
}
// ---- the "#*" line ------------------------------------------------------------------------------
// One finished citation line. Modes:
// "jeder" {{jêder-kitêb|ku|sernav=…|jêgirtin=…}}
// "category" the Kategorî:Çavkanî template as the citation template: {{Ç:Kitêb|rûpel=12|…}}
// "plain" free-text source, or none: {{jêder|ku|2=…|jêgirtin=…}} / {{nimûne|ku|…}}
// "raw" a finished template, passed through with only the entry name bolded
// opts: {mode, template, language, fields, langParamKey, quote, translation, transcription,
// source, sourceURL, title, marker, keepTranslation, extra}
function buildCitation(opts) {
var marker = opts.marker || "#*";
var title = opts.title || mw.config.get("wgTitle");
var quote = boldenPageTitle(String(opts.quote || ""), title).text;
if (opts.mode === "raw") {
// Written as given: the point is to paste a citation built elsewhere and have it survive intact.
// A line copied off another entry usually still carries its own "#*"/"#:" -- the marker is added
// back below, so strip it rather than emitting "#* #* {{...}}".
var pasted = String(opts.quote || "").replace(LIST_MARKER, "");
return marker + " " + nlToBr(boldQuoteParam(pasted, title)).trim();
}
var isCategory = opts.mode === "category";
var isTemplate = isCategory || opts.mode === "jeder";
var name = isTemplate ? opts.template : (opts.source || opts.sourceURL ? "jêder" : "nimûne");
var line = marker + " {{" + name;
// jêder-*, jêder and nimûne all take the language as parameter 1. A Kategorî:Çavkanî template only
// gets one when its own TemplateData names it, under its own key -- never assumed positionally.
if (!isCategory) {
line += "|" + opts.language;
}
if (isTemplate) {
var fields = opts.fields || {};
Object.keys(fields).forEach(function (key) {
var value = nlToSpace(String(fields[key] || "").trim());
if (value) {
line += "|" + key + "=" + value;
}
});
if (isCategory && opts.langParamKey) {
line += "|" + opts.langParamKey + "=" + opts.language;
}
// "Ç:" templates are quotation-equivalents rather than usage examples (see Modul:quote).
if (isCategory && /^Ç:/.test(name)) {
line += "|wekjêgirtin=1";
}
} else {
if (opts.source) {
// A positional value containing "=" would be read as its own key=value pair, so name it.
line += (String(opts.source).indexOf("=") !== -1 ? "|2=" : "|") + opts.source;
}
if (opts.sourceURL) {
line += "|url=" + opts.sourceURL;
}
}
var isUsex = name === "nimûne";
var translation = nlToBr(String(opts.translation || "").trim());
// {{nimûne}} always takes one, a Kurmancî example's being its explanation; a quotation's is left out
// for Kurmancî unless the edited line already had one (keepTranslation).
var writeTranslation = !!translation && (isUsex || opts.language !== "ku" || opts.keepTranslation);
// {{nimûne}}'s example is parameter 2 and its translation parameter 3, both positional. A value
// containing "=" would be read as key=value, so then both are named: naming only the example would
// slide the translation into slot 2.
var namedSlots = isUsex && (quote.indexOf("=") !== -1 || (writeTranslation && translation.indexOf("=") !== -1));
line += "|" + (isUsex ? (namedSlots ? "2=" : "") : "jêgirtin=") + nlToBr(quote);
if (writeTranslation) {
// Kategorî:Çavkanî templates route through Modul:quote and take its "t="; jêder-* uses "tercime=".
line += "|" + (isUsex ? (namedSlots ? "3=" : "") : isCategory ? "t=" : "tercime=") + translation;
}
var transcription = nlToBr(String(opts.transcription || "").trim());
if (transcription) {
line += "|tr=" + transcription;
}
// Parameters the form has no box for, carried through from the line being edited.
(opts.extra || []).forEach(function (param) {
line += "|" + param;
});
// A short example and its translation read best on one line, which {{nimûne}} does only when told.
// Short is Modul:usex's own measure for "rêz=auto": at most 100 characters shown between them.
if (isUsex && writeTranslation && !hasInlineParam(opts.extra) &&
shownLength(quote) + shownLength(translation) <= 100) {
line += "|rêz=1";
}
return line + "}}";
}
// True when the parameters already say inline or not: {{nimûne}}'s rêz=, under any of its names.
function hasInlineParam(params) {
return (params || []).some(function (param) {
return /^\s*(?:rêz|birêz|inline)\s*=/.test(param);
});
}
// How many characters a bit of wikitext shows: link targets, bold/italic marks and tags don't count.
function shownLength(text) {
return String(text || "")
.replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g, "$1")
.replace(/'{2,}/g, "")
.replace(/<[^>]*>/g, " ")
.length;
}
// A leading "#*" / "#:" / "##*" ... list marker.
var LIST_MARKER = /^\s*#+[*:]\s*/;
// True when the text is a whole citation line copied from somewhere else: its own list marker followed
// by a template call. That is the shape of every line this family writes -- {{jêder}}, {{jêder-kitêb}},
// {{nm}}, {{nimûne}}, {{mk}}, any Kategorî:Çavkanî template -- so the template name is not checked
// against a list, which could never cover every citation template on the wiki.
//
// Used to switch the form into raw mode by itself: pasting a finished line and forgetting to change the
// dropdown would otherwise wrap it in a second {{nimûne}}.
function looksLikeCitationLine(text) {
var trimmed = String(text || "");
if (!LIST_MARKER.test(trimmed)) {
return false;
}
return trimmed.replace(LIST_MARKER, "").slice(0, 2) === "{{";
}
// True when the example box holds nothing but one finished template call -- the cue to offer, or to
// switch to, the "write it out as-is" mode.
function looksLikeTemplate(text) {
var trimmed = String(text || "").trim();
if (trimmed.slice(0, 2) !== "{{" || trimmed.slice(-2) !== "}}") {
return false;
}
var parsed = edit.wt.parseTemplateCalls(trimmed);
return parsed.calls.length === 1 && !parsed.otherContent;
}
// ---- reading a finished line back --------------------------------------------------------------
// [[Şablon:nimûne]] and every redirect to it.
var USEX_NAMES = ["nimûne", "mk", "ux", "mînak", "nim", "nm"];
var USEX_TRANSLATION = ["3", "t", "tercime", "translation", "w", "werger"];
var USEX_TRANSCRIPTION = ["tr", "transliteration"];
// A quotation template's translation, under the name Modul:quote documents and its aliases.
var QUOTE_TRANSLATION = ["t", "tercime", "werger", "w"];
// [[Şablon:bênimûne]] and its redirects: the "no example yet" placeholder, with or without a language.
var EMPTY_EXAMPLE_MARKER = /^#+\*\s*\{\{\s*(?:bênimûne|bêmînak|mînak\?|nimûne\?)\s*(?:\|[^{}]*)?\}\}\s*$/i;
// The text of a line written as one italic run ("''…''", bold inside allowed -- "'''''X''' …''" opens
// with both), or null.
function italicRun(text) {
var t = $.trim(text || "");
var opening = /^'*/.exec(t)[0].length;
var m = /^''([\s\S]+)''$/.exec(t);
if ((opening !== 2 && opening !== 5) || !m || /''/.test(m[1].replace(/'''/g, ""))) {
return null;
}
return m[1];
}
// "''…'' (…)": an italic example with its explanation in brackets on the same line, as
// {text, translation}, or null. One level of brackets inside the explanation is allowed.
function italicWithGloss(text) {
var m = /^([\s\S]*'')\s*\(([^()]*(?:\([^()]*\)[^()]*)*)\)$/.exec($.trim(text || ""));
var italic = m && italicRun(m[1]);
if (italic === null || !m) {
return null;
}
var gloss = $.trim(m[2]);
return { text: italic, translation: italicRun(gloss) || gloss };
}
function brToNl(text) {
return String(text || "").replace(/<br\s*\/?>/gi, "\n");
}
/**
* One "#:"/"#*" block -- its line plus any deeper "#::"/"#*:" lines -- as the form's values, or null when
* it is not an example: a word list, the "no example yet" placeholder, a plain note.
* {mode: "nimune"|"jeder"|"source"|"raw", marker, template, text, translation, transcription,
* source, sourceURL, fields, extra}
* extra is every parameter the form has no box for, to be written back as it was. A "raw" block is
* edited as written: text is the whole block, list markers included.
*/
function parseCitationLine(block) {
var all = String(block || "").split("\n");
var k = 1;
while (k < all.length && !/^#/.test(all[k])) {
k++;
}
var head = /^#+([:*])\s*([\s\S]*)$/.exec(all.slice(0, k).join("\n"));
var sub = all.slice(k);
if (!head || EMPTY_EXAMPLE_MARKER.test(all[0])) {
return null;
}
var out = { mode: "raw", marker: head[1], template: "", text: "", translation: "", transcription: "",
source: "", sourceURL: "", fields: {}, extra: [] };
var body = $.trim(head[2]);
var parsed = edit.wt.parseTemplateCalls(body);
var call = parsed.calls.length === 1 && !parsed.otherContent ? parsed.calls[0] : null;
var name = call ? edit.wt.canonicalTemplateKey(call.name) : "";
name = name.charAt(0).toLowerCase() + name.slice(1);
var isUsex = USEX_NAMES.indexOf(name) !== -1;
var def = call && jederDefs[name];
if (call && !sub.length && (isUsex || def || name === "jêder")) {
var split = edit.wt.splitCallParams(call.params);
var fieldKeys = def ? def.fields.map(function (f) { return f.key; }) : [];
out.mode = isUsex ? "nimune" : def ? "jeder" : "source";
out.template = def ? name : "";
split.used.forEach(function (key, i) {
var value = brToNl(split.values[key]);
if (key === "1") {
return; // the language, which the section already says
}
if (isUsex ? key === "2" : (key === "jêgirtin" || key === "jg")) {
out.text = value;
} else if ((isUsex ? USEX_TRANSLATION : QUOTE_TRANSLATION).indexOf(key) !== -1) {
out.translation = value;
} else if (USEX_TRANSCRIPTION.indexOf(key) !== -1) {
out.transcription = value;
} else if (def && fieldKeys.indexOf(key) !== -1) {
out.fields[key] = split.values[key];
} else if (name === "jêder" && key === "2") {
out.source = split.values[key];
} else if (name === "jêder" && key === "url") {
out.sourceURL = split.values[key];
} else {
// Named when it was positional, so it cannot shift into a slot the form now fills.
out.extra.push(/^\d+$/.test(key) && !/=/.test(call.params[i]) ? key + "=" + split.values[key] : $.trim(call.params[i]));
}
});
return out;
}
// Any other citation template on a "#*" line -- the Kategorî:Çavkanî ones, {{J:…}} and {{Ç:…}} --
// reads into the category mode: quote, translation and transcription in their boxes, every other
// named parameter as a field. Which of those get a box is known once the template's parameters
// are fetched (see applyAliases).
if (call && !sub.length && out.marker === "*" &&
(/^[JÇ]:/.test($.trim(call.name)) || /(?:^|\|)\s*(?:jêgirtin|jg)\s*=/.test(call.params.join("|")))) {
var catSplit = edit.wt.splitCallParams(call.params);
out.mode = "category";
out.template = $.trim(call.name);
catSplit.used.forEach(function (key, i) {
var value = catSplit.values[key];
if (key === "jêgirtin" || key === "jg") {
out.text = brToNl(value);
} else if (QUOTE_TRANSLATION.indexOf(key) !== -1) {
out.translation = brToNl(value);
} else if (USEX_TRANSCRIPTION.indexOf(key) !== -1) {
out.transcription = brToNl(value);
} else if (key === "wekjêgirtin" && /^Ç:/.test(out.template)) {
return; // buildCitation() writes it for every "Ç:" template
} else if (/^\d+$/.test(key)) {
out.extra.push(!/=/.test(call.params[i]) ? key + "=" + value : $.trim(call.params[i]));
} else {
out.fields[key] = value;
}
});
return out;
}
if (out.marker === ":") {
// The hand-written kind: "#: ''…''", its translation, if any, on a "#::" line under it.
var italic = italicRun(body);
var translationLine = sub.length === 1 ? /^#+::\s*([\s\S]*)$/.exec(sub[0]) : null;
if (italic !== null && (!sub.length || translationLine)) {
out.mode = "nimune";
out.text = brToNl(italic);
out.translation = translationLine ? brToNl(italicRun(translationLine[1]) || $.trim(translationLine[1])) : "";
return out;
}
var withGloss = sub.length ? null : italicWithGloss(body);
if (withGloss) {
out.mode = "nimune";
out.text = brToNl(withGloss.text);
out.translation = brToNl(withGloss.translation);
return out;
}
if (!isUsex) {
return null;
}
}
out.text = all.join("\n");
return out;
}
/**
* Settles which parameters of a read line get a box. aliases maps an alias to its documented name
* (templateAliases); fieldKeys are the boxes the form drew. A parameter written under an alias -- "r="
* for "rûpel=" -- goes to its box; one with no box goes to extra, written back as it was.
*/
function applyAliases(example, aliases, fieldKeys) {
var pending = example.extra.slice();
Object.keys(example.fields).forEach(function (key) {
if (fieldKeys.indexOf(key) === -1) {
pending.push(key + "=" + example.fields[key]);
delete example.fields[key];
}
});
example.extra = [];
pending.forEach(function (param) {
var m = /^\s*([^=]+?)\s*=([\s\S]*)$/.exec(param);
var key = m ? (aliases[m[1]] || m[1]) : null;
var value = m ? $.trim(m[2]) : "";
if (m && fieldKeys.indexOf(key) !== -1 && !example.fields[key]) {
example.fields[key] = value;
} else if (m && key === "jêgirtin" && !example.text) {
example.text = brToNl(value);
} else if (m && QUOTE_TRANSLATION.indexOf(key) !== -1 && !example.translation) {
example.translation = brToNl(value);
} else {
example.extra.push(param);
}
});
return example;
}
/**
* newLine with its parameters in the order originalLine had them, so an edit's diff shows only what
* changed. Parameters match by name -- an alias counting as the name it stands for (aliases, from
* templateAliases) -- and positional ones by position; any the original lacked follow in newLine's own
* order. A line that is not one template call on either side comes back as it was.
*/
function keepOrder(newLine, originalLine, aliases) {
aliases = aliases || {};
var marker = /^#+[:*]\s*/.exec(newLine || "");
var oldBody = /^#+[:*]\s*([\s\S]*)$/.exec(originalLine || "");
if (!marker || !oldBody) {
return newLine;
}
var fresh = edit.wt.parseTemplateCalls(newLine.slice(marker[0].length));
var old = edit.wt.parseTemplateCalls($.trim(oldBody[1]));
if (fresh.calls.length !== 1 || fresh.otherContent || old.calls.length !== 1 || old.otherContent) {
return newLine;
}
function keyed(params) {
var positional = 0;
return params.map(function (raw) {
var m = /^\s*([^|={}\n]+?)\s*=/.exec(raw);
var key = m ? m[1] : String(++positional);
return { key: aliases[key] || key, raw: raw };
});
}
var rest = keyed(fresh.calls[0].params);
var ordered = [];
keyed(old.calls[0].params).forEach(function (o) {
for (var i = 0; i < rest.length; i++) {
if (rest[i].key === o.key) {
ordered.push(rest.splice(i, 1)[0]);
return;
}
}
});
return marker[0] + "{{" + fresh.calls[0].name + ordered.concat(rest).map(function (p) {
return "|" + p.raw;
}).join("") + "}}";
}
// Where the {{ … }} opened on lines[start] closes, so a template written over several lines stays whole.
function braceEnd(lines, start) {
var depth = 0;
for (var i = start; i < lines.length; i++) {
depth += (lines[i].match(/\{\{/g) || []).length - (lines[i].match(/\}\}/g) || []).length;
if (depth <= 0) {
return i;
}
}
return lines.length - 1;
}
// The "#:"/"#*" blocks right under the definition on lines[defIndex], each {start, end, text, example}:
// example is parseCitationLine's reading, or null for a block that is not an example.
function exampleBlocks(lines, defIndex) {
var depth = /^#+/.exec(lines[defIndex])[0].length;
var own = new RegExp("^#{" + depth + "}[:*]");
var deeper = new RegExp("^#{" + depth + "}[:*][:*]");
var blocks = [];
var i = defIndex + 1;
while (i < lines.length && own.test(lines[i])) {
var start = i;
var end = braceEnd(lines, i);
while (end + 1 < lines.length && deeper.test(lines[end + 1])) {
end = braceEnd(lines, end + 1);
}
var text = lines.slice(start, end + 1).join("\n");
blocks.push({ start: start, end: end, text: text, example: parseCitationLine(text) });
i = end + 1;
}
return blocks;
}
/**
* Replaces the index-th example under a definition with code, or removes it when code is null.
* original is the block as it read when the form opened. When the index no longer holds it (an earlier
* queued change moved it) the one block still reading exactly that is used; otherwise nothing is touched.
*/
function replaceExample(pageContent, language, sectionType, sectionOrdinal, definitionLevel, index, original, code) {
var loc = edit.wt.locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defIndex = edit.wt.findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var examples = exampleBlocks(lines, defIndex).filter(function (b) {
return b.example;
});
var block = examples[index] && examples[index].text === original ? examples[index] : null;
if (!block) {
var same = examples.filter(function (b) {
return b.text === original;
});
block = same.length === 1 ? same[0] : null;
}
if (!block) {
throw new edit.util.SubmitAbort("Ev nimûne ji dema vekirina formê ve hatiye guhartin — rûpelê nû bike û dîsa biceribîne.");
}
var replacement = code === null ? [] : String(code).split("\n");
lines.splice.apply(lines, [block.start, block.end - block.start + 1].concat(replacement));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Words only, lower-cased: links, templates, tags and quote marks out, so the page's drawn text and the
// line's wikitext can be compared.
function normalizeWords(text) {
return String(text || "")
.replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g, "$1")
.replace(/\{\{[^{}]*\}\}/g, " ")
.replace(/<[^>]*>/g, " ")
.replace(/'{2,}/g, "")
.toLowerCase()
.replace(/[\s.,;:!?"'“”‘’«»()\[\]\-–—…\/]+/g, "");
}
// True when the text a page draws for an example comes from this line.
function exampleMatches(drawnText, example) {
var source = example.text;
if (example.mode === "raw") {
var quote = /\|\s*(?:jêgirtin|jg)\s*=([^|}]*)/.exec(source);
source = quote ? quote[1] : source.replace(/^#+[:*]\s*/, "");
}
var drawn = normalizeWords(drawnText);
var written = normalizeWords(source);
if (!drawn || !written) {
return false;
}
return drawn === written || written.indexOf(drawn.slice(0, 24)) !== -1;
}
module.exports = {
CATEGORY_KEY: CATEGORY_KEY,
RAW_KEY: RAW_KEY,
COVERED_PARAMS: COVERED_PARAMS,
LANG_LABEL: LANG_LABEL,
jederDefs: jederDefs,
fieldSize: fieldSize,
exclusivePartner: exclusivePartner,
boldenPageTitle: boldenPageTitle,
nlToBr: nlToBr,
nlToSpace: nlToSpace,
fetchCategoryTemplates: fetchCategoryTemplates,
buildCategoryTemplateSearch: buildCategoryTemplateSearch,
openTemplateDialog: openTemplateDialog,
buildTemplateParamForm: buildTemplateParamForm,
documentedValues: documentedValues,
templateParams: templateParams,
fieldSpecs: fieldSpecs,
jederFieldSpecs: jederFieldSpecs,
fetchFromCitoid: fetchFromCitoid,
citoidFieldsFor: citoidFieldsFor,
withLinkAsUrl: withLinkAsUrl,
today: today,
buildCitation: buildCitation,
looksLikeTemplate: looksLikeTemplate,
looksLikeCitationLine: looksLikeCitationLine,
USEX_NAMES: USEX_NAMES,
EMPTY_EXAMPLE_MARKER: EMPTY_EXAMPLE_MARKER,
parseCitationLine: parseCitationLine,
applyAliases: applyAliases,
keepOrder: keepOrder,
templateAliases: templateAliases,
exampleBlocks: exampleBlocks,
replaceExample: replaceExample,
exampleMatches: exampleMatches
};
}());
// </nowiki>
8h7mj6zh6930ezqlfm9ylgmfe2kbn1u
6865545
6865537
2026-10-23T19:58:00Z
Wikihez
7344
Template dialogs: move focus off the clicked chip before the modal hides the page (aria-hidden on a focused ancestor), return it on close
6865545
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.cite.js]]
*
* Citations for both add-examples and the wizard: jêder-* templates, [[Kategorî:Çavkanî]] templates,
* Citoid, and the "#*" line they become. fieldSpecs() returns plain field descriptions because the two
* callers draw them differently (OOUI vs the wizard's own grid); everything else is shared. Also the
* template-param dialog (openTemplateDialog), used the same way by DefSideBoxes and start.js.
*
* The only caller of window.wikt.citoid, which the citoid-helper gadget owns.
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var wt = edit.wt;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
var tdText = wt.tdText;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var jederDefs = edit.data.jederDefs || {};
// Guarded: between deploying this file and the Gadgets-definition line that adds etimon.js to the
// package, a cached startup module would have this require throw and take the whole gadget with it.
// Without the builder, an {{etîmon}} call falls through to the generic dialog below.
var etimon = null;
try {
etimon = require("./wikt.cite/etimon.js");
} catch (e) {
etimon = null;
}
var api = new mw.Api();
// Sentinel _currentJederKey / dropdown value for "pick a template from Kategorî:Çavkanî". Not a real
// jederDefs key. Both gadgets use this same string, so a draft saved by one is read by the other.
var CATEGORY_KEY = "__category__";
// Sentinel for "the example box holds a finished template; write it out untouched".
var RAW_KEY = "__raw__";
// Covered by the form's own dedicated boxes, so they are never drawn again as generic template fields.
var COVERED_PARAMS = ["jêgirtin", "jg", "t", "tr"];
// A Kategorî:Çavkanî template whose TemplateData labels a parameter exactly this is fed the language
// from the language dropdown instead of showing a box for it. Unlike jêder-*, an arbitrary template has
// no "parameter 1 is the language" convention, so nothing is assumed positionally.
var LANG_LABEL = "koda zimanî";
// Field key -> {span, multiline}, taken from whichever jêder-* template defines it first. Most
// Kategorî:Çavkanî templates reuse the same bibliographic parameter names, so "sernav" comes out
// full-width and multiline there too rather than as a generic one-line box.
var SIZE_BY_KEY = {};
Object.keys(jederDefs).forEach(function (jederKey) {
(jederDefs[jederKey].fields || []).forEach(function (field) {
if (!SIZE_BY_KEY[field.key]) {
SIZE_BY_KEY[field.key] = {
span: field.span || 4,
multiline: !!field.multiline,
width: field.width || ""
};
}
});
});
// Field pairs Modul:quote refuses together ("Divê tenê yek ji |tarîx= an |sal= were dayîn"): a full
// date and a bare year say the same thing twice. Both are offered because either may be all a source
// gives, so the forms clear one when the other is filled rather than letting the citation break.
var EXCLUSIVE_PAIRS = [["sal", "tarîx"]];
function exclusivePartner(key) {
var found = null;
EXCLUSIVE_PAIRS.forEach(function (pair) {
if (pair[0] === key) {
found = pair[1];
} else if (pair[1] === key) {
found = pair[0];
}
});
return found;
}
function fieldSize(key) {
return SIZE_BY_KEY[key] || { span: 4, multiline: false, width: "" };
}
// ---- text helpers -------------------------------------------------------------------------------
// The entry name as a pattern. A Kurdish word ending in -î loses the circumflex once something is
// attached to it ("safî" is written "safiye"), so a title ending that way matches either spelling --
// otherwise the name is simply never found in the quotation.
function titlePattern(title) {
var escaped = escapeRegExp(String(title || ""));
return /î$/i.test(escaped) ? escaped.slice(0, -1) + "[îi]" : escaped;
}
// Wraps the entry's own name in bold wherever it appears in the quotation, which is how quotations are
// written here, and reports whether the name ends up bold so a caller can say so.
//
// The name may carry an attached suffix ("celbê"), so the match runs on to the end of the word. A word
// ends at whitespace, punctuation or markup: excluding markup is what keeps "celb<br>me’şûq" from
// bolding as one lump. The typographic apostrophe stays a word character -- it is one inside Kurdish
// words like "me’şûq".
function boldenPageTitle(text, title) {
var source = String(text || "");
var escapedTitle = titlePattern(title);
var suffixPattern = "([^\\s.,;:?!()'\"<>\\[\\]{}|=/\\\\«»„“”–—]+)?";
var titleRe = new RegExp("(" + escapedTitle + suffixPattern + ")", "ig");
var titleAnywhere = new RegExp(escapedTitle, "i");
// Whatever already sits between ''' marks is copied through untouched, so a quotation somebody
// bolded by hand -- or one an earlier run bolded -- never collects a second pair.
var boldSpan = /'''[\s\S]*?'''/g;
var out = "";
var last = 0;
var alreadyBold = false;
var span;
while ((span = boldSpan.exec(source)) !== null) {
out += source.slice(last, span.index).replace(titleRe, "'''$1'''");
if (titleAnywhere.test(span[0])) {
alreadyBold = true;
}
out += span[0];
last = span.index + span[0].length;
}
out += source.slice(last).replace(titleRe, "'''$1'''");
return { text: out, bolded: out !== source || alreadyBold };
}
// A literal newline would end the single-line "#*" wikitext, so display text keeps its break as <br>...
function nlToBr(text) {
return String(text || "").replace(/\r\n|\r|\n/g, "<br>");
}
// ...while a plain parameter value (an author, a title) just loses it.
function nlToSpace(text) {
return String(text || "").replace(/\s*(?:\r\n|\r|\n)\s*/g, " ");
}
// Bolds the entry name inside a pasted citation's jêgirtin=/jg= value only, so a book title or author
// containing it is left alone. With no such parameter nothing is bolded: running the plain-text bolder
// over wikitext would wrap the braces too.
function boldQuoteParam(text, title) {
var match = /(\|\s*(?:jêgirtin|jg)\s*=)([\s\S]*?)(?=\|[^|=]*=|\}\}\s*$)/.exec(text);
if (!match) {
return text;
}
var bolded = boldenPageTitle(match[2], title).text;
return text.slice(0, match.index) + match[1] + bolded + text.slice(match.index + match[0].length);
}
// "name — description" for each Kategorî:Çavkanî option in a <select>, from the description half of
// the same TemplateData every citation picker already fetches -- so a search box built over these
// options can also match on what a template documents itself as doing, not only its name. An
// undescribed option, or a failed fetch, just keeps its plain label; never blocks on this.
// A self-contained "search Kategorî:Çavkanî" combo: a text input plus its own results list, matching
// add-examples' own jêgirtin picker -- each row shows its label, a " → target" arrow when a redirect's
// label differs from its target, and a 🔗 link to the canonical template's own page. opts.onPick(entry)
// fires when a row is chosen; the input is then left holding the plain label, not "label → target", so
// re-focusing it to search again doesn't start from that arrow text. opts.describe: true also fills in
// each visible row's own TemplateData description once it loads (batched, so scrolling through matches
// costs a handful of requests, not one per row).
function buildCategoryTemplateSearch(opts) {
opts = opts || {};
var shell = util.buildSearchList({
placeholder: opts.placeholder || util.msg.searchTemplate
});
var $input = shell.$input;
var $results = shell.$results;
var $status = $("<span class='wiktku-cat-tpl-status'></span>").text(util.msg.templatesLoading);
var list = [];
function decorate($text, target, wanted) {
if (!wanted) {
return;
}
edit.wt.fetchTemplateData(target).then(function (doc) {
var desc = doc && edit.wt.tdText(doc.description);
if (desc && $text.closest("body").length) {
$text.append(document.createTextNode(" — " + edit.wt.shortDesc(desc)));
}
});
}
function showResults(query) {
var q = $.trim(query || "").toLowerCase();
$results.empty();
var matched = q ? list.filter(function (entry) {
return entry.label.toLowerCase().indexOf(q) !== -1;
}) : list;
if (!matched.length) {
shell.close();
return;
}
var shown = matched.slice(0, 50);
// Only what a search has narrowed to: an empty box is the whole category, and describing its
// first fifty would ask the wiki about templates nobody has looked for.
var describing = !!opts.describe && !!q;
if (describing) {
edit.wt.fetchTemplateDataBatch(shown.map(function (entry) { return entry.target; }));
}
shown.forEach(function (entry) {
var $li = $("<li class='wiktku-cat-tpl-row'></li>");
var $text = $("<span class='wiktku-cat-tpl-text'></span>").text(entry.label);
if (entry.label !== entry.target) {
$text.append($("<span class='wiktku-cat-tpl-arrow'></span>").text(" → " + entry.target));
}
decorate($text, entry.target, describing);
var $link = $("<a class='wiktku-cat-tpl-link' target='_blank' rel='noopener'>🔗</a>")
.attr("title", util.msg.openTemplatePage)
.attr("href", util.tplPageUrl(entry.target));
// stopPropagation only -- preventDefault would stop the link from actually navigating.
$link.on("mousedown click", function (e) { e.stopPropagation(); });
$li.append($text, $link);
shell.bindPick($li, function () {
shell.close();
$input.val(entry.label);
opts.onPick(entry);
});
$results.append($li);
});
shell.open();
}
$input.on("input focus", function () {
showResults($(this).val());
});
$input.on("blur", function () {
setTimeout(shell.close, 200); // lets the pick handler fire first
});
fetchCategoryTemplates().then(function (l) {
list = l;
$status.text("");
}, function () {
$status.text(util.msg.templatesFailed);
});
return {
$element: $("<span class='wiktku-native-select-wrap wiktku-cat-tpl-picker'></span>").append($input, $results, $status),
$input: $input,
$status: $status
};
}
// ---- Kategorî:Çavkanî -----------------------------------------------------------------------------
// The category's template members plus their redirects, each redirect its own searchable entry pointing
// at the same canonical target, so a template is findable by any of its names. Cached for the session.
var catTemplatesPromise = null;
function fetchCategoryTemplates() {
if (catTemplatesPromise) {
return catTemplatesPromise;
}
catTemplatesPromise = api.get({
action: "query",
generator: "categorymembers",
gcmtitle: "Kategorî:Çavkanî",
gcmnamespace: 10,
gcmlimit: "max",
prop: "redirects",
rdnamespace: 10,
rdlimit: "max",
formatversion: 2
}).then(function (data) {
var list = [];
((data.query && data.query.pages) || []).forEach(function (page) {
var target = page.title.replace(/^[^:]+:/, "");
list.push({ label: target, target: target });
(page.redirects || []).forEach(function (redirect) {
list.push({ label: redirect.title.replace(/^[^:]+:/, ""), target: target });
});
});
list.sort(function (a, b) {
return a.label.localeCompare(b.label, mw.config.get("wgUserLanguage"));
});
return list;
}, function () {
return [];
});
return catTemplatesPromise;
}
// ---- template-param dialog: fills a {{Name|...}} call from its own TemplateData -------------------
// Shared by DefSideBoxes (a fresh insert from Etîmolojî/Çavkanî, or double-clicking an existing chip
// to edit it) and start.js's own Etîmolojî/Çavkanî pickers -- one dialog, so all of them read the same
// TemplateData the same way.
// OOUI's own dialog stack -- the same widgetry VisualEditor's template dialog is built on.
var citeWindowManager = null;
function templateDialogManager() {
if (!citeWindowManager) {
citeWindowManager = new OO.ui.WindowManager();
$(document.body).append(citeWindowManager.$element);
}
return citeWindowManager;
}
// Opens one template call in a modal built from its TemplateData. Resolves with the edited wikitext, or
// null when the user cancels.
// One at a time: a second open while one is loading or showing (a chip clicked again before its dialog
// appeared) stacks a window the manager does not track, and that window's buttons do nothing. The
// second call resolves null, as a cancel does.
var templateDialogBusy = false;
function openTemplateDialog(callWikitext) {
var done = $.Deferred();
var call = parseTemplateCalls(callWikitext).calls[0];
if (!call || templateDialogBusy) {
return done.resolve(null).promise();
}
// {{etîmon}} has no form TemplateData can describe -- a chain of steps, each with its own words
// and their own modifiers -- so it has a builder of its own, on the same contract as this dialog.
if (etimon && etimon.isEtimonName(call.name)) {
return etimon.openDialog(callWikitext);
}
templateDialogBusy = true;
done.always(function () {
templateDialogBusy = false;
});
$.when(
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"]),
edit.wt.fetchTemplateData(call.name),
templateAliases(call.name)
).then(function (_loaded, td, aliases) {
var result = null;
// The parent is captured rather than reached through TplDialog.super: "super" is a reserved word,
// and OOjs has moved between .super and .parent for it over the years.
var ParentDialog = OO.ui.ProcessDialog;
function TplDialog(config) {
ParentDialog.call(this, config);
}
OO.inheritClass(TplDialog, ParentDialog);
TplDialog.static.name = "citeTemplateDialog";
TplDialog.static.title = "{{" + call.name + "}}";
TplDialog.static.actions = [
{ action: "save", label: "Biguhêre", flags: ["primary", "progressive"] },
{ action: "remove", label: "Jê bibe", flags: "destructive" },
{ label: util.msg.cancel, flags: "safe" }
];
TplDialog.prototype.initialize = function () {
ParentDialog.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.form = buildTemplateParamForm(call, td, aliases);
this.panel.$element.append(this.form.$element);
this.$body.append(this.panel.$element);
};
TplDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
TplDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save" || action === "remove") {
return new OO.ui.Process(function () {
result = action === "remove" ? "" : dialog.form.values();
dialog.close();
});
}
return ParentDialog.prototype.getActionProcess.call(this, action);
};
// Marks the page while this is showing, so a floating box of ours (DefSideBoxes' queue) does
// not sit over a dialog the page's stacking order puts underneath it.
$(document.body).addClass("wikt-dialog-open");
done.always(function () {
$(document.body).removeClass("wikt-dialog-open");
});
var manager = templateDialogManager();
var dialog = new TplDialog({ size: "medium" });
manager.addWindows([dialog]);
// The modal hides the page from screen readers, which is refused while the clicked chip still
// has focus; so the focus leaves it first, and OOUI hands it back when the dialog closes.
var $opener = $(document.activeElement);
$opener.trigger("blur");
var instance = manager.openWindow(dialog, { $returnFocusTo: $opener });
instance.closed.then(function () {
manager.clearWindows();
done.resolve(result);
});
instance.opening.fail(function () {
manager.clearWindows();
done.resolve(null);
});
}, function () {
done.resolve(null);
});
return done.promise();
}
// TemplateData descriptions are plain text, but authors write wikilinks in them; render those as links.
function tdDescription(text) {
var $out = $("<span></span>");
var re = /\[\[([^\[\]|]+)(?:\|([^\[\]]*))?\]\]/g;
var last = 0;
var m;
while ((m = re.exec(text)) !== null) {
if (m.index > last) {
$out.append(document.createTextNode(text.slice(last, m.index)));
}
$out.append($("<a></a>").attr("href", mw.util.getUrl(m[1])).text(m[2] || m[1]));
last = re.lastIndex;
}
if (last < text.length) {
$out.append(document.createTextNode(text.slice(last)));
}
return $out;
}
// A parameter whose documentation says so takes "kelîme<t:...>" inline modifiers, so it gets the pill
// widget instead of a plain box. TemplateData has no field for this, hence the marker in the description.
var INLINE_MODIFIER_MARKER = "WF:Guherkerên rêzê";
function acceptsInlineModifiers(doc) {
return tdText(doc && doc.description).indexOf(INLINE_MODIFIER_MARKER) !== -1;
}
// A language-code parameter, per its own label.
function isLanguageParam(doc) {
return /ziman/i.test(tdText(doc && doc.label));
}
// alias -> documented name, from the TemplateData's own "aliases" lists plus any given (aliases from
// templateAliases(), for a template whose baked parameter list carries none).
function aliasMap(td, extra) {
var map = $.extend({}, extra || {});
$.each((td && td.params) || {}, function (key, doc) {
((doc && doc.aliases) || []).forEach(function (alias) {
map[alias] = key;
});
});
return map;
}
// The call's values under the documented names, as {values, used, spelling}: a value written under an
// alias ("cuda=" for "alt") fills the documented field, and spelling remembers the alias so the call is
// written back the way the page has it. A call giving both names keeps both as they are.
function documentedValues(params, docParams, aliases) {
var parsed = splitCallParams(params);
var spelling = {};
var used = [];
parsed.used.forEach(function (key) {
var doc = aliases[key];
if (doc && docParams[doc] && parsed.used.indexOf(doc) === -1 && !spelling[doc]) {
parsed.values[doc] = parsed.values[key];
spelling[doc] = key;
used.push(doc);
} else {
used.push(key);
}
});
return { values: parsed.values, used: used, spelling: spelling };
}
function buildTemplateParamForm(call, td, aliases) {
var docParams = (td && td.params) || {};
var parsed = documentedValues(call.params, docParams, aliasMap(td, aliases));
var keys = (td && td.paramOrder) ? td.paramOrder.slice() : Object.keys(docParams);
parsed.used.forEach(function (k) {
if (keys.indexOf(k) === -1) {
keys.push(k);
}
});
var $element = $("<div class='dsb-tpl-form'></div>");
var readers = {}; // key -> () => the parameter's current text
$element.append($("<div class='dsb-tpl-name'></div>").append(
$("<a></a>").attr("href", mw.util.getUrl("Şablon:" + call.name))
.attr("target", "_blank").text("{{" + call.name + "}}")
));
var desc = tdText(td && td.description);
if (desc) {
$element.append($("<div class='dsb-tpl-desc'></div>").append(tdDescription(desc)));
}
keys.forEach(function (key) {
var doc = docParams[key] || {};
var value = parsed.values[key] || "";
var $field;
if (acceptsInlineModifiers(doc)) {
// The same word row the sense lists use: plain text plus removable "<t:...>" pills.
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: tdText(doc.label) || key,
modTitle: util.msg.addModifier,
removeTitle: "Vê nirxê jê bibe",
removeModTitle: util.msg.removeModifier
});
row.setRaw(value);
// A <ul> so the row's own "close other pickers" lookup has something to scope to.
$field = $("<ul class='dsb-wordlist dsb-tpl-modifier-list'></ul>").append(row.$li);
readers[key] = row.getRaw;
} else {
var $input = $("<input type='text' class='dsb-input'>").val(value);
if (isLanguageParam(doc)) {
$input.attr("placeholder", "koda zimanî…");
langs.picker($input);
} else {
var suggested = doc.suggestedvalues || [];
if (suggested.length) {
var listId = "dsb-sv-" + call.name.replace(/[^A-Za-z0-9]/g, "") + "-" + key;
var $list = $("<datalist></datalist>").attr("id", listId);
suggested.forEach(function (v) {
$list.append($("<option></option>").val(v));
});
$input.attr("list", listId);
$element.append($list);
}
}
$field = $input;
readers[key] = function () {
return $input.val();
};
}
var $label = $("<label class='dsb-adv-label'></label>").text(tdText(doc.label) || key);
if (!docParams[key]) {
$label.append($("<span class='dsb-tpl-undocumented'></span>").text(" (ne di belgeyê de)"));
}
var $row = $("<div class='dsb-tpl-param-row'></div>").append($label, $field);
var help = tdText(doc.description);
if (help) {
$row.append($("<div class='dsb-tpl-help'></div>").append(tdDescription(help)));
}
if (doc.deprecated) {
$row.addClass("dsb-tpl-deprecated");
}
$element.append($row);
});
return {
$element: $element,
values: function () {
var vals = {};
var written = keys.map(function (k) {
var name = parsed.spelling[k] || k;
vals[name] = $.trim(readers[k]() || "");
return name;
});
return buildTemplateCall(call.name, vals, written);
}
};
}
// The documented parameters of one template (name without the "Şablon:" prefix), in its own
// paramOrder; [] when it has no TemplateData. The fetch, the shaping and the cache all live in
// wikt.edit, so the wizard, DefSideBoxes and this module share one request and one copy per template.
//
// The key stands in for a missing label, because these become form fields and a field needs something
// written on it.
// A template's parameters: its TemplateData, or -- for one with none, like most Kategorî:Çavkanî
// templates -- every {{{name}}} its own source reads.
function templateParams(name) {
return edit.wt.templateDoc(name).then(function (td) {
var params = ((td && td.params) || []).map(function (p) {
return {
key: p.key,
label: p.label || p.key,
required: p.required,
deprecated: p.deprecated
};
});
return params.length ? params : sourceParams(name);
});
}
var sourceParamsCache = {};
function sourceParams(name) {
if (!sourceParamsCache[name]) {
sourceParamsCache[name] = new mw.Api().get({
action: "query", prop: "revisions", titles: "Template:" + name, rvprop: "content", rvslots: "main",
redirects: 1, formatversion: 2
}).then(function (data) {
var page = data.query.pages[0];
var source = page.revisions ? page.revisions[0].slots.main.content : "";
var keys = [];
var re = /\{\{\{\s*([^{}|]+?)\s*[|}]/g;
var m;
while ((m = re.exec(source)) !== null) {
if (!/^\d+$/.test(m[1]) && keys.indexOf(m[1]) === -1) {
keys.push(m[1]);
}
}
return keys.map(function (key) {
return { key: key, label: key, required: false, deprecated: false };
});
}, function () {
return [];
});
}
return sourceParamsCache[name];
}
// alias -> the name a template's TemplateData documents it under ({r: "rûpel"} for jêder-kitêb). Asked
// of the wiki itself: the parameter lists baked into data-main.json carry no aliases.
var aliasCache = {};
function templateAliases(name) {
if (!aliasCache[name]) {
aliasCache[name] = new mw.Api().get({
action: "templatedata", titles: "Template:" + name, redirects: 1, formatversion: 2
}).then(function (data) {
var map = {};
$.each(data.pages || {}, function (_, page) {
$.each(page.params || {}, function (key, p) {
(p.aliases || []).forEach(function (alias) {
map[alias] = key;
});
});
});
return map;
}, function () {
return {};
});
}
return aliasCache[name];
}
// What to draw for one Kategorî:Çavkanî template: a field per documented parameter, minus the ones the
// form already has boxes for and minus the language parameter, which is filled from the dropdown.
// A template with no TemplateData at all falls back to a lone "Rûpel" box, which is what such citations
// almost always need ({{Şablon|rûpel=12|jêgirtin=…}}).
function fieldSpecs(name) {
return templateParams(name).then(function (params) {
var langParamKey = null;
(params || []).forEach(function (p) {
if ((p.label || "").trim().toLowerCase() === LANG_LABEL) {
langParamKey = p.key;
}
});
var filtered = (params || []).filter(function (p) {
return COVERED_PARAMS.indexOf(p.key) === -1 && p.key !== langParamKey;
});
var list = filtered.length ? filtered : [{ key: "rûpel", label: "Rûpel", required: false }];
return {
langParamKey: langParamKey,
fields: list.map(function (p) {
var size = fieldSize(p.key);
return {
key: p.key,
label: p.label,
required: p.required,
span: size.span,
width: size.width,
multiline: size.multiline
};
})
};
});
}
// The same list for a jêder-* template, straight out of the shared table.
function jederFieldSpecs(jederKey) {
var def = jederDefs[jederKey];
return def ? (def.fields || []).slice() : [];
}
// ---- Citoid --------------------------------------------------------------------------------------
// Today, as Modul:quote wants it (it renders "2024-05-05" as "5 gulan 2024").
function today() {
var d = new Date();
function pad(n) { return (n < 10 ? "0" : "") + n; }
return d.getFullYear() + "-" + pad(d.getMonth() + 1) + "-" + pad(d.getDate());
}
// Carries across the parts of a Citoid result that citoid-helper's own mapping drops: the access date
// and the identifiers for the fields the forms have only just gained. Applied here rather than in
// citoid-helper so it reaches both gadgets without an edit to the MediaWiki: gadget page.
function enrichCitoidFields(fields, raw) {
fields = fields || {};
if (fields.url && !fields["tarîxa-gihiştinê"]) {
// Citoid reports when it actually fetched the page ("accessDate": "2026-09-03"), so use that
// rather than the clock -- they differ whenever the answer came from a cache. today() is only
// the fallback for a result that carries no accessDate.
var accessed = raw && (raw.accessDate || raw.accessdate);
fields["tarîxa-gihiştinê"] = (accessed && String(accessed).slice(0, 10)) || today();
}
if (raw) {
var issn = Array.isArray(raw.ISSN) ? raw.ISSN[0] : raw.ISSN;
if (issn && !fields.issn) {
fields.issn = issn;
}
var doi = Array.isArray(raw.DOI) ? raw.DOI[0] : raw.DOI;
if (doi && !fields.doi) {
fields.doi = doi;
}
if (raw.volume && !fields.cild) {
fields.cild = raw.volume;
}
}
return fields;
}
// One URL/DOI/ISBN -> {jederKey, fields}. The lookup itself lives in the citoid-helper gadget; this
// wrapper exists so neither caller has to know that, and so a missing gadget fails in one place.
function fetchFromCitoid(query) {
if (!(window.wikt && window.wikt.citoid && window.wikt.citoid.fetchJederFields)) {
return $.Deferred().reject("Amûra Citoidê ne barkirî ye.").promise();
}
return window.wikt.citoid.fetchJederFields(query).then(function (result) {
result.fields = enrichCitoidFields(result.fields, result.raw);
return result;
});
}
// Re-derives the fields for a different jêder-* template than the one citoid detected, enriched the
// same way -- used when the person had already chosen a template and we keep their choice.
// The form's link box stands in for a jêder-* template's own url= when that is left empty.
function withLinkAsUrl(jederKey, fields, link) {
var def = jederDefs[jederKey];
var hasUrl = !!def && (def.fields || []).some(function (field) {
return field.key === "url";
});
if (hasUrl && !fields.url && /^https?:\/\//.test(link || "")) {
fields.url = link;
}
return fields;
}
function citoidFieldsFor(jederKey, raw, query) {
if (!(window.wikt && window.wikt.citoid && window.wikt.citoid.dataToJederFields)) {
return {};
}
return enrichCitoidFields(window.wikt.citoid.dataToJederFields(jederKey, raw, query), raw);
}
// ---- the "#*" line ------------------------------------------------------------------------------
// One finished citation line. Modes:
// "jeder" {{jêder-kitêb|ku|sernav=…|jêgirtin=…}}
// "category" the Kategorî:Çavkanî template as the citation template: {{Ç:Kitêb|rûpel=12|…}}
// "plain" free-text source, or none: {{jêder|ku|2=…|jêgirtin=…}} / {{nimûne|ku|…}}
// "raw" a finished template, passed through with only the entry name bolded
// opts: {mode, template, language, fields, langParamKey, quote, translation, transcription,
// source, sourceURL, title, marker, keepTranslation, extra}
function buildCitation(opts) {
var marker = opts.marker || "#*";
var title = opts.title || mw.config.get("wgTitle");
var quote = boldenPageTitle(String(opts.quote || ""), title).text;
if (opts.mode === "raw") {
// Written as given: the point is to paste a citation built elsewhere and have it survive intact.
// A line copied off another entry usually still carries its own "#*"/"#:" -- the marker is added
// back below, so strip it rather than emitting "#* #* {{...}}".
var pasted = String(opts.quote || "").replace(LIST_MARKER, "");
return marker + " " + nlToBr(boldQuoteParam(pasted, title)).trim();
}
var isCategory = opts.mode === "category";
var isTemplate = isCategory || opts.mode === "jeder";
var name = isTemplate ? opts.template : (opts.source || opts.sourceURL ? "jêder" : "nimûne");
var line = marker + " {{" + name;
// jêder-*, jêder and nimûne all take the language as parameter 1. A Kategorî:Çavkanî template only
// gets one when its own TemplateData names it, under its own key -- never assumed positionally.
if (!isCategory) {
line += "|" + opts.language;
}
if (isTemplate) {
var fields = opts.fields || {};
Object.keys(fields).forEach(function (key) {
var value = nlToSpace(String(fields[key] || "").trim());
if (value) {
line += "|" + key + "=" + value;
}
});
if (isCategory && opts.langParamKey) {
line += "|" + opts.langParamKey + "=" + opts.language;
}
// "Ç:" templates are quotation-equivalents rather than usage examples (see Modul:quote).
if (isCategory && /^Ç:/.test(name)) {
line += "|wekjêgirtin=1";
}
} else {
if (opts.source) {
// A positional value containing "=" would be read as its own key=value pair, so name it.
line += (String(opts.source).indexOf("=") !== -1 ? "|2=" : "|") + opts.source;
}
if (opts.sourceURL) {
line += "|url=" + opts.sourceURL;
}
}
var isUsex = name === "nimûne";
var translation = nlToBr(String(opts.translation || "").trim());
// {{nimûne}} always takes one, a Kurmancî example's being its explanation; a quotation's is left out
// for Kurmancî unless the edited line already had one (keepTranslation).
var writeTranslation = !!translation && (isUsex || opts.language !== "ku" || opts.keepTranslation);
// {{nimûne}}'s example is parameter 2 and its translation parameter 3, both positional. A value
// containing "=" would be read as key=value, so then both are named: naming only the example would
// slide the translation into slot 2.
var namedSlots = isUsex && (quote.indexOf("=") !== -1 || (writeTranslation && translation.indexOf("=") !== -1));
line += "|" + (isUsex ? (namedSlots ? "2=" : "") : "jêgirtin=") + nlToBr(quote);
if (writeTranslation) {
// Kategorî:Çavkanî templates route through Modul:quote and take its "t="; jêder-* uses "tercime=".
line += "|" + (isUsex ? (namedSlots ? "3=" : "") : isCategory ? "t=" : "tercime=") + translation;
}
var transcription = nlToBr(String(opts.transcription || "").trim());
if (transcription) {
line += "|tr=" + transcription;
}
// Parameters the form has no box for, carried through from the line being edited.
(opts.extra || []).forEach(function (param) {
line += "|" + param;
});
// A short example and its translation read best on one line, which {{nimûne}} does only when told.
// Short is Modul:usex's own measure for "rêz=auto": at most 100 characters shown between them.
if (isUsex && writeTranslation && !hasInlineParam(opts.extra) &&
shownLength(quote) + shownLength(translation) <= 100) {
line += "|rêz=1";
}
return line + "}}";
}
// True when the parameters already say inline or not: {{nimûne}}'s rêz=, under any of its names.
function hasInlineParam(params) {
return (params || []).some(function (param) {
return /^\s*(?:rêz|birêz|inline)\s*=/.test(param);
});
}
// How many characters a bit of wikitext shows: link targets, bold/italic marks and tags don't count.
function shownLength(text) {
return String(text || "")
.replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g, "$1")
.replace(/'{2,}/g, "")
.replace(/<[^>]*>/g, " ")
.length;
}
// A leading "#*" / "#:" / "##*" ... list marker.
var LIST_MARKER = /^\s*#+[*:]\s*/;
// True when the text is a whole citation line copied from somewhere else: its own list marker followed
// by a template call. That is the shape of every line this family writes -- {{jêder}}, {{jêder-kitêb}},
// {{nm}}, {{nimûne}}, {{mk}}, any Kategorî:Çavkanî template -- so the template name is not checked
// against a list, which could never cover every citation template on the wiki.
//
// Used to switch the form into raw mode by itself: pasting a finished line and forgetting to change the
// dropdown would otherwise wrap it in a second {{nimûne}}.
function looksLikeCitationLine(text) {
var trimmed = String(text || "");
if (!LIST_MARKER.test(trimmed)) {
return false;
}
return trimmed.replace(LIST_MARKER, "").slice(0, 2) === "{{";
}
// True when the example box holds nothing but one finished template call -- the cue to offer, or to
// switch to, the "write it out as-is" mode.
function looksLikeTemplate(text) {
var trimmed = String(text || "").trim();
if (trimmed.slice(0, 2) !== "{{" || trimmed.slice(-2) !== "}}") {
return false;
}
var parsed = edit.wt.parseTemplateCalls(trimmed);
return parsed.calls.length === 1 && !parsed.otherContent;
}
// ---- reading a finished line back --------------------------------------------------------------
// [[Şablon:nimûne]] and every redirect to it.
var USEX_NAMES = ["nimûne", "mk", "ux", "mînak", "nim", "nm"];
var USEX_TRANSLATION = ["3", "t", "tercime", "translation", "w", "werger"];
var USEX_TRANSCRIPTION = ["tr", "transliteration"];
// A quotation template's translation, under the name Modul:quote documents and its aliases.
var QUOTE_TRANSLATION = ["t", "tercime", "werger", "w"];
// [[Şablon:bênimûne]] and its redirects: the "no example yet" placeholder, with or without a language.
var EMPTY_EXAMPLE_MARKER = /^#+\*\s*\{\{\s*(?:bênimûne|bêmînak|mînak\?|nimûne\?)\s*(?:\|[^{}]*)?\}\}\s*$/i;
// The text of a line written as one italic run ("''…''", bold inside allowed -- "'''''X''' …''" opens
// with both), or null.
function italicRun(text) {
var t = $.trim(text || "");
var opening = /^'*/.exec(t)[0].length;
var m = /^''([\s\S]+)''$/.exec(t);
if ((opening !== 2 && opening !== 5) || !m || /''/.test(m[1].replace(/'''/g, ""))) {
return null;
}
return m[1];
}
// "''…'' (…)": an italic example with its explanation in brackets on the same line, as
// {text, translation}, or null. One level of brackets inside the explanation is allowed.
function italicWithGloss(text) {
var m = /^([\s\S]*'')\s*\(([^()]*(?:\([^()]*\)[^()]*)*)\)$/.exec($.trim(text || ""));
var italic = m && italicRun(m[1]);
if (italic === null || !m) {
return null;
}
var gloss = $.trim(m[2]);
return { text: italic, translation: italicRun(gloss) || gloss };
}
function brToNl(text) {
return String(text || "").replace(/<br\s*\/?>/gi, "\n");
}
/**
* One "#:"/"#*" block -- its line plus any deeper "#::"/"#*:" lines -- as the form's values, or null when
* it is not an example: a word list, the "no example yet" placeholder, a plain note.
* {mode: "nimune"|"jeder"|"source"|"raw", marker, template, text, translation, transcription,
* source, sourceURL, fields, extra}
* extra is every parameter the form has no box for, to be written back as it was. A "raw" block is
* edited as written: text is the whole block, list markers included.
*/
function parseCitationLine(block) {
var all = String(block || "").split("\n");
var k = 1;
while (k < all.length && !/^#/.test(all[k])) {
k++;
}
var head = /^#+([:*])\s*([\s\S]*)$/.exec(all.slice(0, k).join("\n"));
var sub = all.slice(k);
if (!head || EMPTY_EXAMPLE_MARKER.test(all[0])) {
return null;
}
var out = { mode: "raw", marker: head[1], template: "", text: "", translation: "", transcription: "",
source: "", sourceURL: "", fields: {}, extra: [] };
var body = $.trim(head[2]);
var parsed = edit.wt.parseTemplateCalls(body);
var call = parsed.calls.length === 1 && !parsed.otherContent ? parsed.calls[0] : null;
var name = call ? edit.wt.canonicalTemplateKey(call.name) : "";
name = name.charAt(0).toLowerCase() + name.slice(1);
var isUsex = USEX_NAMES.indexOf(name) !== -1;
var def = call && jederDefs[name];
if (call && !sub.length && (isUsex || def || name === "jêder")) {
var split = edit.wt.splitCallParams(call.params);
var fieldKeys = def ? def.fields.map(function (f) { return f.key; }) : [];
out.mode = isUsex ? "nimune" : def ? "jeder" : "source";
out.template = def ? name : "";
split.used.forEach(function (key, i) {
var value = brToNl(split.values[key]);
if (key === "1") {
return; // the language, which the section already says
}
if (isUsex ? key === "2" : (key === "jêgirtin" || key === "jg")) {
out.text = value;
} else if ((isUsex ? USEX_TRANSLATION : QUOTE_TRANSLATION).indexOf(key) !== -1) {
out.translation = value;
} else if (USEX_TRANSCRIPTION.indexOf(key) !== -1) {
out.transcription = value;
} else if (def && fieldKeys.indexOf(key) !== -1) {
out.fields[key] = split.values[key];
} else if (name === "jêder" && key === "2") {
out.source = split.values[key];
} else if (name === "jêder" && key === "url") {
out.sourceURL = split.values[key];
} else {
// Named when it was positional, so it cannot shift into a slot the form now fills.
out.extra.push(/^\d+$/.test(key) && !/=/.test(call.params[i]) ? key + "=" + split.values[key] : $.trim(call.params[i]));
}
});
return out;
}
// Any other citation template on a "#*" line -- the Kategorî:Çavkanî ones, {{J:…}} and {{Ç:…}} --
// reads into the category mode: quote, translation and transcription in their boxes, every other
// named parameter as a field. Which of those get a box is known once the template's parameters
// are fetched (see applyAliases).
if (call && !sub.length && out.marker === "*" &&
(/^[JÇ]:/.test($.trim(call.name)) || /(?:^|\|)\s*(?:jêgirtin|jg)\s*=/.test(call.params.join("|")))) {
var catSplit = edit.wt.splitCallParams(call.params);
out.mode = "category";
out.template = $.trim(call.name);
catSplit.used.forEach(function (key, i) {
var value = catSplit.values[key];
if (key === "jêgirtin" || key === "jg") {
out.text = brToNl(value);
} else if (QUOTE_TRANSLATION.indexOf(key) !== -1) {
out.translation = brToNl(value);
} else if (USEX_TRANSCRIPTION.indexOf(key) !== -1) {
out.transcription = brToNl(value);
} else if (key === "wekjêgirtin" && /^Ç:/.test(out.template)) {
return; // buildCitation() writes it for every "Ç:" template
} else if (/^\d+$/.test(key)) {
out.extra.push(!/=/.test(call.params[i]) ? key + "=" + value : $.trim(call.params[i]));
} else {
out.fields[key] = value;
}
});
return out;
}
if (out.marker === ":") {
// The hand-written kind: "#: ''…''", its translation, if any, on a "#::" line under it.
var italic = italicRun(body);
var translationLine = sub.length === 1 ? /^#+::\s*([\s\S]*)$/.exec(sub[0]) : null;
if (italic !== null && (!sub.length || translationLine)) {
out.mode = "nimune";
out.text = brToNl(italic);
out.translation = translationLine ? brToNl(italicRun(translationLine[1]) || $.trim(translationLine[1])) : "";
return out;
}
var withGloss = sub.length ? null : italicWithGloss(body);
if (withGloss) {
out.mode = "nimune";
out.text = brToNl(withGloss.text);
out.translation = brToNl(withGloss.translation);
return out;
}
if (!isUsex) {
return null;
}
}
out.text = all.join("\n");
return out;
}
/**
* Settles which parameters of a read line get a box. aliases maps an alias to its documented name
* (templateAliases); fieldKeys are the boxes the form drew. A parameter written under an alias -- "r="
* for "rûpel=" -- goes to its box; one with no box goes to extra, written back as it was.
*/
function applyAliases(example, aliases, fieldKeys) {
var pending = example.extra.slice();
Object.keys(example.fields).forEach(function (key) {
if (fieldKeys.indexOf(key) === -1) {
pending.push(key + "=" + example.fields[key]);
delete example.fields[key];
}
});
example.extra = [];
pending.forEach(function (param) {
var m = /^\s*([^=]+?)\s*=([\s\S]*)$/.exec(param);
var key = m ? (aliases[m[1]] || m[1]) : null;
var value = m ? $.trim(m[2]) : "";
if (m && fieldKeys.indexOf(key) !== -1 && !example.fields[key]) {
example.fields[key] = value;
} else if (m && key === "jêgirtin" && !example.text) {
example.text = brToNl(value);
} else if (m && QUOTE_TRANSLATION.indexOf(key) !== -1 && !example.translation) {
example.translation = brToNl(value);
} else {
example.extra.push(param);
}
});
return example;
}
/**
* newLine with its parameters in the order originalLine had them, so an edit's diff shows only what
* changed. Parameters match by name -- an alias counting as the name it stands for (aliases, from
* templateAliases) -- and positional ones by position; any the original lacked follow in newLine's own
* order. A line that is not one template call on either side comes back as it was.
*/
function keepOrder(newLine, originalLine, aliases) {
aliases = aliases || {};
var marker = /^#+[:*]\s*/.exec(newLine || "");
var oldBody = /^#+[:*]\s*([\s\S]*)$/.exec(originalLine || "");
if (!marker || !oldBody) {
return newLine;
}
var fresh = edit.wt.parseTemplateCalls(newLine.slice(marker[0].length));
var old = edit.wt.parseTemplateCalls($.trim(oldBody[1]));
if (fresh.calls.length !== 1 || fresh.otherContent || old.calls.length !== 1 || old.otherContent) {
return newLine;
}
function keyed(params) {
var positional = 0;
return params.map(function (raw) {
var m = /^\s*([^|={}\n]+?)\s*=/.exec(raw);
var key = m ? m[1] : String(++positional);
return { key: aliases[key] || key, raw: raw };
});
}
var rest = keyed(fresh.calls[0].params);
var ordered = [];
keyed(old.calls[0].params).forEach(function (o) {
for (var i = 0; i < rest.length; i++) {
if (rest[i].key === o.key) {
ordered.push(rest.splice(i, 1)[0]);
return;
}
}
});
return marker[0] + "{{" + fresh.calls[0].name + ordered.concat(rest).map(function (p) {
return "|" + p.raw;
}).join("") + "}}";
}
// Where the {{ … }} opened on lines[start] closes, so a template written over several lines stays whole.
function braceEnd(lines, start) {
var depth = 0;
for (var i = start; i < lines.length; i++) {
depth += (lines[i].match(/\{\{/g) || []).length - (lines[i].match(/\}\}/g) || []).length;
if (depth <= 0) {
return i;
}
}
return lines.length - 1;
}
// The "#:"/"#*" blocks right under the definition on lines[defIndex], each {start, end, text, example}:
// example is parseCitationLine's reading, or null for a block that is not an example.
function exampleBlocks(lines, defIndex) {
var depth = /^#+/.exec(lines[defIndex])[0].length;
var own = new RegExp("^#{" + depth + "}[:*]");
var deeper = new RegExp("^#{" + depth + "}[:*][:*]");
var blocks = [];
var i = defIndex + 1;
while (i < lines.length && own.test(lines[i])) {
var start = i;
var end = braceEnd(lines, i);
while (end + 1 < lines.length && deeper.test(lines[end + 1])) {
end = braceEnd(lines, end + 1);
}
var text = lines.slice(start, end + 1).join("\n");
blocks.push({ start: start, end: end, text: text, example: parseCitationLine(text) });
i = end + 1;
}
return blocks;
}
/**
* Replaces the index-th example under a definition with code, or removes it when code is null.
* original is the block as it read when the form opened. When the index no longer holds it (an earlier
* queued change moved it) the one block still reading exactly that is used; otherwise nothing is touched.
*/
function replaceExample(pageContent, language, sectionType, sectionOrdinal, definitionLevel, index, original, code) {
var loc = edit.wt.locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defIndex = edit.wt.findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var examples = exampleBlocks(lines, defIndex).filter(function (b) {
return b.example;
});
var block = examples[index] && examples[index].text === original ? examples[index] : null;
if (!block) {
var same = examples.filter(function (b) {
return b.text === original;
});
block = same.length === 1 ? same[0] : null;
}
if (!block) {
throw new edit.util.SubmitAbort("Ev nimûne ji dema vekirina formê ve hatiye guhartin — rûpelê nû bike û dîsa biceribîne.");
}
var replacement = code === null ? [] : String(code).split("\n");
lines.splice.apply(lines, [block.start, block.end - block.start + 1].concat(replacement));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Words only, lower-cased: links, templates, tags and quote marks out, so the page's drawn text and the
// line's wikitext can be compared.
function normalizeWords(text) {
return String(text || "")
.replace(/\[\[(?:[^\]|]*\|)?([^\]]*)\]\]/g, "$1")
.replace(/\{\{[^{}]*\}\}/g, " ")
.replace(/<[^>]*>/g, " ")
.replace(/'{2,}/g, "")
.toLowerCase()
.replace(/[\s.,;:!?"'“”‘’«»()\[\]\-–—…\/]+/g, "");
}
// True when the text a page draws for an example comes from this line.
function exampleMatches(drawnText, example) {
var source = example.text;
if (example.mode === "raw") {
var quote = /\|\s*(?:jêgirtin|jg)\s*=([^|}]*)/.exec(source);
source = quote ? quote[1] : source.replace(/^#+[:*]\s*/, "");
}
var drawn = normalizeWords(drawnText);
var written = normalizeWords(source);
if (!drawn || !written) {
return false;
}
return drawn === written || written.indexOf(drawn.slice(0, 24)) !== -1;
}
module.exports = {
CATEGORY_KEY: CATEGORY_KEY,
RAW_KEY: RAW_KEY,
COVERED_PARAMS: COVERED_PARAMS,
LANG_LABEL: LANG_LABEL,
jederDefs: jederDefs,
fieldSize: fieldSize,
exclusivePartner: exclusivePartner,
boldenPageTitle: boldenPageTitle,
nlToBr: nlToBr,
nlToSpace: nlToSpace,
fetchCategoryTemplates: fetchCategoryTemplates,
buildCategoryTemplateSearch: buildCategoryTemplateSearch,
openTemplateDialog: openTemplateDialog,
buildTemplateParamForm: buildTemplateParamForm,
documentedValues: documentedValues,
templateParams: templateParams,
fieldSpecs: fieldSpecs,
jederFieldSpecs: jederFieldSpecs,
fetchFromCitoid: fetchFromCitoid,
citoidFieldsFor: citoidFieldsFor,
withLinkAsUrl: withLinkAsUrl,
today: today,
buildCitation: buildCitation,
looksLikeTemplate: looksLikeTemplate,
looksLikeCitationLine: looksLikeCitationLine,
USEX_NAMES: USEX_NAMES,
EMPTY_EXAMPLE_MARKER: EMPTY_EXAMPLE_MARKER,
parseCitationLine: parseCitationLine,
applyAliases: applyAliases,
keepOrder: keepOrder,
templateAliases: templateAliases,
exampleBlocks: exampleBlocks,
replaceExample: replaceExample,
exampleMatches: exampleMatches
};
}());
// </nowiki>
0ps8fnrbvlmjzzbhc72syff6an6qz3f
MediaWiki:Gadget-wikt.cite.css
8
1359167
6865598
6608804
2026-10-23T20:56:05Z
Wikihez
7344
Etimon form: ⊕ and × lead each word row, modifier chips after the word; Metn after id
6865598
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.cite.css]]
*
* Styles cite.js's own DOM: the Kategorî:Çavkanî search rows (buildCategoryTemplateSearch) and the
* template-param dialog (openTemplateDialog/buildTemplateParamForm) it shares with DefSideBoxes and
* start.js. .dsb-input and .dsb-adv-label also exist in dsb-panels.css for its own unrelated fields --
* duplicated here, not moved, since those other uses have nothing to do with this module.
*/
/* ── Template parameter form ─────────────────────────────────────────
Built by buildTemplateParamForm(): a fresh insert from Etîmolojî/Çavkanî, editing an existing chip, or a
gloss line that turns out to be one template call. */
.dsb-input {
box-sizing: border-box;
min-width: 16em;
padding: 6px 10px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
.dsb-input::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
.dsb-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
.dsb-adv-label {
margin-bottom: 4px;
font-size: 12px;
font-weight: 600;
color: var(--wikt-ink-3);
}
.dsb-tpl-name {
font-family: var(--wikt-mono);
font-size: 12px;
color: var(--wikt-ink-3);
}
.dsb-tpl-name a {
color: inherit;
text-decoration: none;
border-bottom: 1px dotted var(--wikt-ink-3);
}
.dsb-tpl-name a:hover {
color: var(--wikt-accent-deep);
}
.dsb-tpl-desc {
margin-top: 2px;
font-size: 12px;
color: var(--wikt-muted);
}
.dsb-tpl-param-row {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 8px;
}
.dsb-tpl-help {
font-size: 11.5px;
color: var(--wikt-muted);
}
.dsb-tpl-undocumented {
font-weight: 400;
color: var(--wikt-muted);
}
.dsb-tpl-deprecated .dsb-adv-label {
text-decoration: line-through;
color: var(--wikt-muted);
}
/* A parameter that takes inline modifiers reuses the word row, so strip the list styling. */
.dsb-tpl-modifier-list {
margin: 0;
padding: 0;
list-style: none;
}
/* ---- the {{etîmon}} builder --------------------------------------------------------------------- */
.dsb-etimon {
display: flex;
flex-direction: column;
gap: 10px;
}
.dsb-etimon-pagelang,
.dsb-etimon-metn {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.dsb-etimon-lang {
max-width: 11em;
}
/* One step of the chain: its keyword, that keyword's modifiers, and the words it applies to. */
.dsb-etimon-step {
border: 1px solid var(--wikt-card-edge);
border-radius: var(--wikt-r-card);
padding: 8px 10px;
background: var(--wikt-code-bg);
}
.dsb-etimon-step-head {
display: flex;
align-items: flex-start;
gap: 8px;
flex-wrap: wrap;
}
.dsb-etimon-words {
margin: 6px 0 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 6px;
}
.dsb-etimon-word-row {
display: flex;
align-items: flex-start;
gap: 6px;
flex-wrap: wrap;
}
/* At the head of a word row the picker floats over what is below instead of pushing the row apart. */
.dsb-etimon-word-row .dsb-mod-picker {
position: absolute;
top: 100%;
left: 0;
z-index: 5;
width: max-content;
max-width: min(32em, 80vw);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
}
.dsb-etimon-word-mods {
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
}
.dsb-etimon-word-mods:empty {
display: none;
}
.dsb-etimon-word-input {
flex: 1 1 12em;
min-width: 10em;
}
/* The ⊕ and its picker, which drops out underneath; in a word row the chips sit after the word instead. */
.dsb-etimon-mods {
position: relative;
display: flex;
align-items: center;
gap: 4px;
flex-wrap: wrap;
}
/* The page's language, above the steps. */
.dsb-etimon-settings {
display: flex;
flex-wrap: wrap;
gap: 8px 16px;
margin-bottom: 12px;
}
.dsb-etimon-settings > * {
flex: 1 1 15em;
margin: 0;
}
/* The keyword control and the description of whatever it has selected. */
.dsb-etimon-kw {
display: flex;
flex: 1 1 14em;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.dsb-etimon-hint {
color: var(--wikt-ink-3);
font-size: 13px;
line-height: 1.35;
}
/* An inline <etimon:…>: the same blocks again, one step deeper. */
.dsb-etimon-nested {
margin: 6px 0 0 14px;
padding-left: 10px;
border-left: 2px solid var(--wikt-accent-edge);
}
.dsb-etimon-nested-head {
display: flex;
align-items: center;
gap: 6px;
}
.dsb-etimon-add {
align-self: flex-start;
margin-top: 6px;
border: 0;
padding: 2px 0;
background: none;
color: var(--wikt-accent);
font-size: 12.5px;
cursor: pointer;
}
.dsb-etimon-add:hover {
text-decoration: underline;
}
/* Belongs to the word above it, not to the step: indented so it does not read as "+ Kelîme"'s sibling. */
.dsb-etimon-add-word {
margin-left: 24px;
}
.dsb-etimon-move {
border: 0;
background: none;
padding: 0 2px;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1.4;
cursor: pointer;
}
.dsb-etimon-move:hover:not(:disabled) {
color: var(--wikt-accent);
}
.dsb-etimon-move:disabled {
opacity: 0.35;
cursor: default;
}
.dsb-etimon-remove {
border: 0;
background: none;
padding: 0 4px;
color: var(--wikt-muted);
font-size: 15px;
line-height: 1.4;
cursor: pointer;
}
.dsb-etimon-remove:hover {
color: var(--wikt-danger);
}
.dsb-etimon-metn-free {
max-width: 9em;
}
.dsb-etimon-more summary {
cursor: pointer;
font-size: 12.5px;
color: var(--wikt-ink-3);
}
.dsb-etimon-param {
display: flex;
flex-direction: column;
gap: 3px;
margin-top: 8px;
}
/* A flag is a checkbox with its explanation on the label, so it reads as one line. */
.dsb-etimon-param-flag {
flex-direction: row;
align-items: center;
gap: 6px;
}
/* What the form will write, so the wikitext is never a surprise. */
.dsb-etimon-preview {
display: block;
padding: 6px 8px;
border-radius: var(--wikt-r-field);
background: var(--wikt-code-bg);
border: 1px solid var(--wikt-card-edge);
font-family: var(--wikt-mono);
font-size: 11.5px;
color: var(--wikt-ink-2);
word-break: break-all;
}
/* Touch: >=16px stops iOS Safari zooming the page when a field is focused, and the taller box clears the
44px tap minimum. dsb-panels.css says the same for its own fields; the dialog opens on the wizard too,
which never loads that sheet. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-input {
min-height: 40px;
font-size: 16px;
}
}
/* </nowiki> */
7mkc2k31wleo7cgynwwcibyu4ndjuqu
MediaWiki:Gadget-wikt.dsb.js
8
1359171
6865606
6865450
2026-10-23T21:31:40Z
Wikihez
7344
Heading tools: ▲ ▼ on === headings, h3/h4/h5 picker on === to =====
6865606
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb.js]]
*
* The part of DefSideBoxes that runs on a page view: finds the definition lines and hangs a pencil on
* each. Forms, dialogs and saves are in wikt.dsb-panels, fetched on the first click (never on hover).
*
* The "Nimûne/jêgirtin" item drives add-examples, which wikt.dsb-panels depends on -- so it is loaded by
* the time any panel exists, and never on a plain page view.
*/
(function () {
"use strict";
var core = require("ext.gadget.wikt.core");
var sections = core.sections;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var msg = core.msg;
var headingOuter = sections.headingOuter;
var headingDepth = sections.headingDepth;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var headingTextOrdinalWithinLanguage = sections.headingTextOrdinalWithinLanguage;
var languageCodeForOl = sections.languageCodeForOl;
var eachLeafDefinition = sections.eachLeafDefinition;
if (mw.config.get("wgAction") !== "view") {
return;
}
// An entry, a reconstruction, or a sandbox -- see sections.isEntryPage().
if (!sections.isEntryPage()) {
return;
}
if (/&printable=yes|&diff=|&oldid=/.test(window.location.search)) {
return;
}
window.wikt = window.wikt || {};
if (window.wikt.defSideBoxesLoaded) {
return;
}
window.wikt.defSideBoxesLoaded = true;
// A save from the queue box reloads with core's post-edit flag set (see dsb-panels.js). Core loads the
// module that reads the flag only after a form save, so it is loaded here; it shows the toast and clears the flag.
if (mw.storage.session.get("mw-PostEdit" + mw.config.get("wgPageName"))) {
mw.loader.load("mediawiki.action.view.postEdit");
}
// The panels are a separate module so their ~100 KB never lands on a page view. Fetched once, on the
// first click that actually needs a form -- never on hover; every later call reuses the same promise.
var panelsPromise = null;
var panelsApi = null;
// A panel is built once and merely shown again after that, so the reveal cannot live in the builders.
// Only click handlers call this -- hover must never pull the panels module.
function revealQueue() {
withPanels(function (panels) {
panels.openQueueBox();
});
}
function withPanels(fn) {
if (!panelsPromise) {
panelsPromise = mw.loader.using("ext.gadget.wikt.dsb-panels").then(function (require) {
panelsApi = require("ext.gadget.wikt.dsb-panels");
return panelsApi;
});
}
panelsPromise.then(fn, function () {
mw.notify("Nikare amûrên guhartinê bar bike.", { type: "error" });
});
}
// The element holding an example's last line of text: a quotation's source line and its quote are
// blocks, so a pencil after them would sit on a line of its own.
function lastTextLine(el) {
var last = el.lastElementChild;
while (last && /^(DIV|DL|DD|UL|OL|LI)$/.test(last.tagName)) {
el = last;
last = el.lastElementChild;
}
return el;
}
// A pencil on every example and quotation under a sense, opening add-examples' form on that one line.
function attachExampleEditors($li, language, sectionType, sectionOrdinal, definitionLevel, $h3) {
// A queued change redraws the sense (dsb-panels.js raises this); its examples get their pencils again.
if (!$li.data("dsbExampleRedraw")) {
$li.data("dsbExampleRedraw", true);
$li.on("dsbSenseRedrawn", function () {
attachExampleEditors($li, language, sectionType, sectionOrdinal, definitionLevel, $h3);
});
}
sections.exampleItemsOf($li).each(function () {
var item = this;
if ($(item).hasClass("dsb-has-example-toolbar")) {
return;
}
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", "Nimûneyê biguhêre");
var $toolbar = $("<span class='dsb-example-toolbar'></span>").append($toggle);
$toggle.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
withPanels(function (panels) {
panels.editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, item);
});
});
// Touch has no hover: a tap on the example shows its pencil, as a tap on a sense does.
$(lastTextLine(item)).append($toolbar);
$(item).addClass("dsb-has-example-toolbar").on("click.dsbReveal", function () {
$toolbar.addClass("dsb-revealed");
});
});
}
// How many rows the pencil is currently holding open. A pinned row means somebody is part-way through
// an edit, and opening another one under the pointer moves the page: the content below shifts, the
// pointer ends up over something else, that fires its own hover, and the page jumps back and forth.
// So while anything is pinned, hovering opens nothing.
var pinnedRows = 0;
var canHover = !window.matchMedia || window.matchMedia("(hover: hover)").matches;
// The first pencil clicked shows every pencil on the page, so any other part is one tap away. Capture
// phase, as each pencil's own handler stops the click there.
document.addEventListener("click", function (e) {
if (e.target.closest && e.target.closest(".dsb-toggle")) {
document.body.classList.add("dsb-pencils-shown");
}
}, true);
function attachToolbar($li, language, sectionType, sectionOrdinal, definitionLevel, $h3, isLastSense) {
if ($li.children(".dsb-toolbar").length) {
return;
}
var $toolbar = $("<span class='dsb-toolbar'></span>");
// The real InterlanguageLinks pencil icon (same asset defEditButton uses); image swap is in the CSS.
var $toggle = $("<a href='#' class='dsb-toggle' title='Guhartina pêşketî'></a>");
// Revealed by $toggle -- an indented panel under the sense, not always visible. Built lazily on
// first open rather than eagerly for every sense on the page.
var $quickRow = $("<div class='dsb-quick-row'></div>").hide();
var pinned = false; // the pencil pins the row open; hovering alone does not
var hovering = false;
// Builds the row -- fetching the panels module the first time -- then calls back. Asynchronous now,
// so the callback re-checks that the row is still wanted rather than showing it unconditionally.
function ensureBuilt(then) {
if ($quickRow.data("built")) {
then();
return;
}
withPanels(function (panels) {
if (!$quickRow.data("built")) {
panels.buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense);
$quickRow.data("built", true);
}
then();
});
}
// Anything the user has started here: an open field box, the open dropdown, or focus inside the row.
function engaged() {
return pinned ||
$quickRow.find(".dsb-open-boxes").children().length > 0 ||
$quickRow.find(".dsb-more-menu").is(":visible") ||
$.contains($quickRow.get(0), document.activeElement);
}
function setPinned(next) {
if (pinned === next) {
return;
}
pinned = next;
pinnedRows += next ? 1 : -1;
}
$toggle.on("click", function (e) {
e.preventDefault();
// Through their own cancel handler rather than a bare hide(), so a row this closes also drops
// its pinned state -- hiding it alone would leave pinnedRows counting a row nobody can see.
$(".dsb-quick-row").not($quickRow).trigger("dsbCancel");
setPinned(!pinned);
if (pinned) {
ensureBuilt(function () {
if (pinned) {
$quickRow.show();
}
});
} else {
$quickRow.hide();
}
});
// Hover reveals the row and takes it away again, the way en.wikt's does -- but never while something
// on it is in use, or a half-typed box would vanish on the way to the mouse leaving.
$li.on("mouseenter.dsbHover", function () {
// iOS plays a tap as mouseenter first, and a row shown here would swallow that tap's click.
if (!canHover) {
return;
}
hovering = true;
// A row the pencil is holding open is already open, and no row opens on hover while another
// one is: that is the case where the page is being edited and must stay still.
if (pinned || pinnedRows) {
return;
}
// Hover reveals a row that already exists; it never fetches the panels module. Otherwise
// simply moving the pointer across an entry would pull ~100 KB on every page view, which is
// the whole cost the split exists to avoid. The pencil is what asks for it.
if ($quickRow.data("built")) {
$quickRow.show();
}
});
// Raised by the panel's "Betal bike" (see dsb-panels.js): clear the pinned state as well as
// hiding, so one click on the pencil opens it again.
$quickRow.on("dsbCancel", function () {
setPinned(false);
hovering = false;
$quickRow.hide();
});
$li.on("mouseleave.dsbHover", function () {
hovering = false;
if (!engaged()) {
$quickRow.hide();
}
});
$toolbar.append($toggle);
// The class positions the <li>, so the toolbar's gutter slot is measured from the line itself and
// not from wherever it happens to sit in the child order -- ShowIDs prepends its id pill to this
// same <li>.
$li.addClass("dsb-def").prepend($toolbar);
// Touch has no :hover -- a tap anywhere on the line reveals the pencil the same way hovering does on
// desktop (CSS gates this to touch devices; harmless if it also fires on a mouse click).
$li.on("click.dsbReveal", function () {
$toolbar.addClass("dsb-revealed");
});
// Land $quickRow right after the sense's own gloss text -- before any existing "Hevmane: ..." <dl>,
// citation <ul>, or the Nimûne/jêgirtin proxy link's own <ul>, not appended after all of that.
var $firstBlock = $li.children("dl, ul, ol").first();
if ($firstBlock.length) {
$firstBlock.before($quickRow);
} else {
$li.append($quickRow);
}
}
// A section the data marks hidden is left alone.
function isHiddenSection(header) {
return PAGE_SUBSECTIONS.some(function (s) {
return s.hidden && s.header === header;
});
}
// The translation section, whose pencil opens the translation adder on its boxes.
function isWergerSection(header) {
return PAGE_SUBSECTIONS.some(function (s) {
return s.kind === "werger" && s.header === header;
});
}
// Everything between a heading and the next heading at its level or above.
function sectionBody($heading) {
var depth = headingDepth($heading);
var $body = $();
for (var $cursor = $heading.next(); $cursor.length; $cursor = $cursor.next()) {
var d = headingDepth($cursor);
if (d && d <= depth) {
break;
}
$body = $body.add($cursor);
}
return $body;
}
// An editor for the translation adder that fetches the panels only when a change is first made, so a
// page view with translation boxes does not pull them.
function lazyTranslationEditor($h3, language, sectionType) {
var real = null;
function withEditor(fn) {
withPanels(function (panels) {
real = real || panels.translationEditor($h3, language, sectionType);
panels.openQueueBox();
fn(real);
});
}
return {
withCurrentText: function (callback) {
withEditor(function (ed) {
ed.withCurrentText(callback);
});
},
addEdit: function (edit, node) {
withEditor(function (ed) {
ed.addEdit(edit, node);
});
},
error: function (message) {
mw.notify(message, { type: "error" });
},
undo: function () {
withEditor(function (ed) {
ed.undo();
});
}
};
}
// Under "==== Werger ====" every box gets the form for a new translation and a ± on its heading, from
// [[MediaWiki:Gadget-wikt.translation-adder.js]]. The heading's pencil opens the boxes' own form (sense,
// note, id of each box, and more boxes) and puts ✎ and × on the existing lines; "+ Qutiyeke din zêde
// bike" under the boxes opens that form on a new box. All their changes join the save queue. False
// when the section has no box the adder reads.
function attachWergerEditor($hh, $heading, $h3, header, language, sectionType, sectionOrdinal) {
var $body = sectionBody($heading);
if (!$body.find("table.translations").length) {
return false;
}
var editor = lazyTranslationEditor($h3, language, sectionType);
var adder = mw.loader.using("ext.gadget.wikt.translation-adder").then(function (require) {
var api = require("ext.gadget.wikt.translation-adder");
return api.attach($body, editor).then(function () {
return api;
});
});
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", header + " biguhêre");
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($hh, $toolbar);
revealHeadingToolbarOnTap($hh, $toolbar);
$heading.after($panel);
function lineEditing(open) {
if (open) {
// Collapsed boxes are opened, so their lines are in sight.
$body.find(".NavFrame").each(function () {
if ($(this).children(".NavContent").is(":hidden")) {
$(this).find(".NavToggle").first().trigger("click");
}
});
}
adder.then(function (api) {
api.setLineEditing($body, editor, open);
}, function () {
mw.notify("Nikare amûra tercimeyan bar bike.", { type: "error" });
});
}
function showPanel() {
$panel.show();
$toolbar.addClass("dsb-revealed dsb-werger-open");
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels(function (panels) {
$panel.empty();
panels.buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, header);
});
}
revealQueue();
}
$toggle.on("click", function (e) {
e.preventDefault();
var open = !$panel.is(":visible");
if (open) {
showPanel();
} else {
$panel.hide();
$toolbar.removeClass("dsb-revealed dsb-werger-open");
}
lineEditing(open);
});
// The panel's "Betal bike" closes it the way the pencil does.
$panel.on("dsbPanelClosed", function () {
$toolbar.removeClass("dsb-revealed dsb-werger-open");
lineEditing(false);
});
var $add = $("<a href='#' class='dsb-quick-link'>+ Qutiyeke din zêde bike</a>").on("click", function (e) {
e.preventDefault();
var addBox = $panel.data("dsbAddBox");
if ($panel.is(":visible") && addBox) {
addBox();
return;
}
$panel.data("dsbAddBoxPending", true);
showPanel();
// A form built before this click has its boxes already; the new one goes in now.
if (addBox) {
$panel.removeData("dsbAddBoxPending");
addBox();
}
});
$("<div class='dsb-werger-add'></div>").append($add).insertAfter($body.last());
return true;
}
// Every subsection under a part of speech, whether or not the data knows the header: one it does not
// know opens as wikitext, or as a word list when that is what it holds -- see buildSubsectionPanel.
function attachSubsectionEditors($h3, language, sectionType, sectionOrdinal, posDepth) {
posDepth = posDepth || 3;
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var depth = headingDepth($cursor);
if (depth && depth <= posDepth) {
break;
}
if (depth !== posDepth + 1) {
continue;
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var header = headingText($hh);
if (!header || $hh.data("dsbAttached")) {
continue;
}
if (isWergerSection(header)) {
$hh.data("dsbAttached", true);
// A Werger heading with no box gets the section pencil, whose form adds one; old-style
// boxes the translation adder cannot read are left alone.
if (attachWergerEditor($hh, $cursor, $h3, header, language, sectionType, sectionOrdinal) ||
sectionBody($cursor).find(".NavFrame").length) {
continue;
}
attachSubsectionEditor($hh, $cursor, $h3, header, language, sectionType, sectionOrdinal);
continue;
}
if (isHiddenSection(header)) {
continue;
}
$hh.data("dsbAttached", true);
attachSubsectionEditor($hh, $cursor, $h3, header, language, sectionType, sectionOrdinal);
}
}
// Where the pencil goes depends on what the skin does with its own edit links. Desktop skins put
// "[biguhêre]" right after the heading text, so the pencil follows it and the two read as one group.
// Minerva parks its edit icon at the far right of the heading row instead, a screen-width away from the
// text, so there the pencil goes inside the heading rather than chasing it.
function placeHeadingToolbar($heading, $toolbar) {
if (mw.config.get("skin") !== "minerva") {
var $editsection = headingOuter($heading).find(".mw-editsection").first();
if ($editsection.length) {
$editsection.after($toolbar);
return;
}
}
$heading.append($toolbar);
}
// Touch has no hover, so a heading's pencil is shown by tapping the heading itself. Taps that mean
// something else are left alone: the skin's own edit links, and the pencil once it is showing.
function revealHeadingToolbarOnTap($heading, $toolbar) {
headingOuter($heading).on("click", function (e) {
if ($(e.target).closest("a, .dsb-toolbar").length) {
return;
}
$toolbar.toggleClass("dsb-revealed");
});
}
// One pencil's worth of behaviour, shared by the "====" headings and by an image's own caption: the
// panel is built the first time it is opened, never before, and the pencil stays visible while it is.
// free, when given, pins the panel to a heading located by its own text rather than through a part of
// speech: {header, ordinal}.
function bindPanelToggle($toggle, $toolbar, $panel, $h3, header, language, sectionType, sectionOrdinal, free) {
$toggle.on("click", function (e) {
e.preventDefault();
$panel.toggle();
// Stays put while its panel is open, so it does not vanish from under the pointer.
$toolbar.toggleClass("dsb-revealed", $panel.is(":visible"));
if (!$panel.is(":visible")) {
return;
}
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels(function (panels) {
$panel.empty();
panels.buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, header, free);
});
}
revealQueue();
});
$panel.on("dsbPanelClosed", function () {
$toolbar.removeClass("dsb-revealed");
});
}
function attachSubsectionEditor($hh, $heading, $h3, header, language, sectionType, sectionOrdinal) {
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", header + " biguhêre");
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($hh, $toolbar);
revealHeadingToolbarOnTap($hh, $toolbar);
$heading.after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $h3, header, language, sectionType, sectionOrdinal);
}
// The "place" subsection that owns the thumbnails under a part-of-speech heading -- named in the data,
// not spelled out here, so renaming it there renames it everywhere.
var IMAGE_HEADER = (function () {
var found = null;
PAGE_SUBSECTIONS.forEach(function (s) {
if (s.place === "pos-top") {
found = s.header;
}
});
return found;
}());
// A pencil on the image's own caption. A wrong caption is noticed while looking at the picture, and
// this is where the writer reaches for it -- the same editor the section's own list offers, opened on
// the image that is already there.
// Every thumbnail between the heading and the first subsection, which is the run "pos-top" covers.
// Anything else in that run is stepped over: a headword line, a declension table or the stylesheet a
// template brings with it all sit before the pictures on plenty of entries.
function attachImageEditors($h3, language, sectionType, sectionOrdinal) {
if (!IMAGE_HEADER) {
return;
}
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if (headingDepth($cursor)) {
break;
}
if ($cursor.is(".thumb, figure, .floatright, .floatleft")) {
attachImageEditor($cursor, $h3, language, sectionType, sectionOrdinal);
}
}
}
// A "===" section that is not a part of speech but is one of ours: Bilêvkirin, which sits above the
// part-of-speech blocks and belongs to the whole language.
function placeSubsectionFor($h3) {
var text = headingText($h3);
var found = null;
PAGE_SUBSECTIONS.forEach(function (s) {
if (s.place === "pron" && s.header === text) {
found = s;
}
});
return found;
}
// The first part-of-speech heading under the same language. The writer works from a section, so
// without one there is nothing to anchor the box to.
function firstPosHeadingAfter($h3) {
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is(".mw-heading2, h2")) {
return null; // the next language starts
}
var depth = headingDepth($cursor);
if (depth !== 3 && depth !== 4) {
continue;
}
var $hh = $cursor.is("h3, h4") ? $cursor : $cursor.find("h3, h4").first();
if (sectionTypeOf($hh) || isManeHeading($hh)) {
return $hh;
}
}
return null;
}
// Its own pencil, opening the same box the section list offers -- a heading nobody can edit from the
// page is a heading people edit by hand.
function attachPlaceHeadingEditor($h3, subsection) {
if ($h3.data("dsbAttached")) {
return;
}
var $pos = firstPosHeadingAfter($h3);
if (!$pos) {
return;
}
var $ol = null;
for (var $cursor = headingOuter($pos).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("ol")) {
$ol = $cursor;
break;
}
if ($cursor.is(".mw-heading2, .mw-heading3, h2, h3")) {
break;
}
}
var language = $ol && languageCodeForOl($ol);
if (!language) {
return;
}
$h3.data("dsbAttached", true);
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", subsection.header + " " + msg.edit);
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($h3, $toolbar);
revealHeadingToolbarOnTap($h3, $toolbar);
headingOuter($h3).after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $pos, subsection.header, language,
sectionTypeOf($pos) || MANE_SECTION, sectionOrdinalWithinLanguage($pos));
}
// Every other heading inside a language: the ones that sit beside the parts of speech rather than
// under one ("=== Etîmolojî ===", "=== Çavkanî ==="), and the ones nobody planned for. Each opens
// the wikitext under its own heading, or the {{kol3}} form when that is what it holds.
function attachFreeSectionEditors() {
$("#mw-content-text").find("h3, h4, h5, h6").each(function () {
var $h = $(this);
// A heading that already carries one of this gadget's pencils is spoken for.
if ($h.data("dsbAttached") || headingOuter($h).find(".dsb-toolbar").length) {
return;
}
var header = headingText($h);
if (!header || isHiddenSection(header)) {
return;
}
var language = languageCodeForOl($h);
if (!language) {
return;
}
$h.data("dsbAttached", true);
attachFreeSectionEditor($h, header, language);
});
}
function attachFreeSectionEditor($h, header, language) {
var free = { header: header, ordinal: headingTextOrdinalWithinLanguage($h) };
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", header + " " + msg.edit);
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($h, $toolbar);
revealHeadingToolbarOnTap($h, $toolbar);
headingOuter($h).after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $h, header, language, null, 0, free);
}
function attachImageEditor($fig, $h3, language, sectionType, sectionOrdinal) {
if ($fig.data("dsbAttached")) {
return;
}
var $caption = $fig.find("figcaption, .thumbcaption").first();
if (!$caption.length) {
return;
}
$fig.data("dsbAttached", true);
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-caption'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", IMAGE_HEADER + " biguhêre");
// After the figure, not inside it: the panel is full width, and a floated thumbnail is not.
var $panel = $("<div class='dsb-subsection-panel dsb-panel-after-image'></div>").hide();
$toolbar.append($toggle);
$caption.append($toolbar);
revealHeadingToolbarOnTap($caption, $toolbar);
$fig.after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $h3, IMAGE_HEADER, language, sectionType, sectionOrdinal);
}
function attachSubsectionTrigger($h3, $ol, language, sectionType, sectionOrdinal) {
if ($h3.data("dsbAttached")) {
return;
}
$h3.data("dsbAttached", true);
var $anchor = $ol;
for (var $cursor = $ol.next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is(".mw-heading2, .mw-heading3, .mw-heading4, .mw-heading5, .mw-heading6, h2, h3, h4, h5, h6")) {
break;
}
$anchor = $cursor;
}
var $trigger = $("<a href='#' class='dsb-add-subsection'>+ Binebeşekê lê zêde bike</a>");
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$trigger.on("click", function (e) {
e.preventDefault();
$panel.toggle();
if (!$panel.is(":visible")) {
return;
}
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels(function (panels) {
$panel.empty();
panels.buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal);
});
}
revealQueue();
});
$anchor.after($panel);
$anchor.after($trigger);
}
// Attaches the "choose a part of speech" trigger under a heading whose part of speech is unsettled: an
// unclassified "=== Mane ===" placeholder, or a "=== Biwêj ===" that is really a noun, verb, ... .
function attachConvertPosTrigger($h3, sectionType) {
if ($h3.data("dsbConvertAttached")) {
return;
}
$h3.data("dsbConvertAttached", true);
// languageCodeForOl() is really just "nearest preceding .besazimani[id] before this element in
// document order" -- nothing ol-specific about it, so it works unchanged for a heading too.
var language = languageCodeForOl($h3);
if (!language) {
return;
}
var sectionOrdinal = sectionOrdinalWithinLanguage($h3);
// The same pencil the "====" subsections get, so reclassifying reads as an edit affordance rather
// than a line of body text.
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", "Cureyê rêzimanî hilbijêre");
var $panel = $("<div class='dsb-convert-pos-panel'></div>").hide();
$toolbar.append($toggle);
$toggle.on("click", function (e) {
e.preventDefault();
$panel.toggle();
// Stays put while its panel is open, so it does not vanish from under the pointer.
$toolbar.toggleClass("dsb-revealed", $panel.is(":visible"));
if (!$panel.is(":visible")) {
return;
}
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels(function (panels) {
$panel.empty();
panels.buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal);
});
}
revealQueue();
});
// Raised by the panel's "Betal bike": closes it and drops what was typed, so the pencil reopens it fresh.
$panel.on("dsbCancel", function () {
$panel.hide().empty().data("built", false);
$toolbar.removeClass("dsb-revealed");
});
placeHeadingToolbar($h3, $toolbar);
revealHeadingToolbarOnTap($h3, $toolbar);
headingOuter($h3).after($panel);
}
// ---- driver ----
function init() {
$("h3, h4").each(function () {
var $h3 = $(this);
var posDepth = headingDepth($h3) || 3;
var sectionType = sectionTypeOf($h3);
if (!sectionType) {
var place = placeSubsectionFor($h3);
if (place) {
attachPlaceHeadingEditor($h3, place);
return;
}
if (!isManeHeading($h3)) {
return; // not a recognized word-sense section (Etîmolojî, Çavkanî, ...)
}
// An unclassified placeholder: offer the POS picker, then carry on and wire up the same
// per-sense pencils, so the POS choice and the sense edits can be queued and saved together.
sectionType = MANE_SECTION;
attachConvertPosTrigger($h3, sectionType);
} else {
// Every part of speech, not just the ones most often mistaken: any section can turn out to
// be another, and the picker is what changes it.
attachConvertPosTrigger($h3, sectionType);
}
var sectionOrdinal = sectionOrdinalWithinLanguage($h3);
var $ol = null;
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("ol")) {
$ol = $cursor;
break;
}
if (headingDepth($cursor)) {
break;
}
}
if (!$ol) {
return;
}
var language = languageCodeForOl($ol);
if (!language) {
return;
}
var lastTopIndex = $ol.children("li").not(".mw-empty-elt").length - 1;
eachLeafDefinition($ol, function ($li) {
var definitionLevel = [];
var $definitionItem = $li;
while ($definitionItem.prop("tagName") === "LI") {
definitionLevel.splice(0, 0, $definitionItem.prevAll("li:not(.mw-empty-elt)").length);
$definitionItem = $definitionItem.parent().parent();
}
definitionLevel.splice(0, 0, sectionType + "|" + sectionOrdinal);
var isLastSense = definitionLevel.length === 2 && definitionLevel[1] === lastTopIndex;
attachToolbar($li, language, sectionType, sectionOrdinal, definitionLevel, $h3, isLastSense);
attachExampleEditors($li, language, sectionType, sectionOrdinal, definitionLevel, $h3);
});
attachSubsectionTrigger($h3, $ol, language, sectionType, sectionOrdinal, posDepth);
attachSubsectionEditors($h3, language, sectionType, sectionOrdinal, posDepth);
attachImageEditors($h3, language, sectionType, sectionOrdinal);
});
// Last, so a heading one of the walks above claimed keeps the pencil that knows more about it.
attachFreeSectionEditors();
attachHeadingTools();
}
// Beside each heading's pencil: ▲ ▼ to move an "===" section within its language, and its depth
// (h3, h4, h5) for any heading from "===" to "=====". Both go into the save queue.
function attachHeadingTools() {
$("#mw-content-text").find("h3, h4, h5").each(function () {
var $h = $(this);
var depth = headingDepth($h);
var language = languageCodeForOl($h);
if (depth < 3 || depth > 5 || !language || !headingText($h) || $h.data("dsbHeadingTools")) {
return;
}
$h.data("dsbHeadingTools", true);
var $toolbar = headingOuter($h).find(".dsb-toolbar-heading").first();
if (!$toolbar.length) {
$toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
placeHeadingToolbar($h, $toolbar);
revealHeadingToolbarOnTap($h, $toolbar);
}
var $tools = $("<span class='dsb-heading-tools'></span>");
if (depth === 3) {
[[-1, "▲", "Vê beşê bibe jor"], [1, "▼", "Vê beşê bibe jêr"]].forEach(function (b) {
$("<button type='button' class='dsb-heading-move'></button>").text(b[1]).attr("title", b[2])
.on("click", function (e) {
e.preventDefault();
withPanels(function (panels) {
panels.moveH3(language, $h, b[0]);
});
revealQueue();
})
.appendTo($tools);
});
}
var $depth = $("<select class='dsb-heading-depth'></select>").attr("title", "Dereceya sernavê");
[3, 4, 5].forEach(function (d) {
$depth.append($("<option></option>").val(d).text("h" + d));
});
$depth.val(depth).on("change", function () {
var to = Number($depth.val());
withPanels(function (panels) {
panels.setDepth(language, $h, to);
});
revealQueue();
});
$tools.append($depth);
$toolbar.append($tools);
});
}
// One listener for every "Hevmane û Bêhtir" dropdown on the page.
$(document).on("click.dsbMoreMenu", function (e) {
$(".dsb-more-menu:visible").each(function () {
var $menu = $(this);
if ($(e.target).closest($menu).length || $(e.target).closest($menu.prev(".dsb-more-toggle")).length) {
return;
}
$menu.slideUp();
var $arrow = $menu.data("dsbArrow");
if ($arrow) {
$arrow.text("▸");
}
});
});
// Closes every open panel/preview this gadget can leave in the DOM, so bfcache never freezes one mid-open.
function closeAllOpenUi() {
if (panelsApi) {
panelsApi.clearQueue();
panelsApi.closeAllPanels();
return;
}
// Before the panels arrive there is nothing built to tear down, only the view-path rows.
$(".dsb-quick-row, .dsb-more-menu, .dsb-preview-inline, .dsb-preview-block").hide();
}
$(window).on("pagehide", closeAllOpenUi);
// Belt-and-suspenders: also close on restore-from-bfcache, in case pagehide's own hide didn't stick.
$(window).on("pageshow", function (e) {
if (e.originalEvent && e.originalEvent.persisted) {
closeAllOpenUi();
}
});
$(init);
// </nowiki>
}());
// </nowiki>
g9yswbuwq9h8793xmacrzlbnxg952e7
6865617
6865606
2026-10-23T22:09:36Z
Wikihez
7344
DefSideBoxes: each kind of pencil loads only its own panel module
6865617
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb.js]]
*
* The part of DefSideBoxes that runs on a page view: finds the definition lines and hangs a pencil on
* each. Forms, dialogs and saves are in wikt.dsb-panels, fetched on the first click (never on hover).
*
* The "Nimûne/jêgirtin" item drives add-examples, which wikt.dsb-panels depends on -- so it is loaded by
* the time any panel exists, and never on a plain page view.
*/
(function () {
"use strict";
var core = require("ext.gadget.wikt.core");
var sections = core.sections;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var msg = core.msg;
var headingOuter = sections.headingOuter;
var headingDepth = sections.headingDepth;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var headingTextOrdinalWithinLanguage = sections.headingTextOrdinalWithinLanguage;
var languageCodeForOl = sections.languageCodeForOl;
var eachLeafDefinition = sections.eachLeafDefinition;
if (mw.config.get("wgAction") !== "view") {
return;
}
// An entry, a reconstruction, or a sandbox -- see sections.isEntryPage().
if (!sections.isEntryPage()) {
return;
}
if (/&printable=yes|&diff=|&oldid=/.test(window.location.search)) {
return;
}
window.wikt = window.wikt || {};
if (window.wikt.defSideBoxesLoaded) {
return;
}
window.wikt.defSideBoxesLoaded = true;
// A save from the queue box reloads with core's post-edit flag set (see dsb-panels.js). Core loads the
// module that reads the flag only after a form save, so it is loaded here; it shows the toast and clears the flag.
if (mw.storage.session.get("mw-PostEdit" + mw.config.get("wgPageName"))) {
mw.loader.load("mediawiki.action.view.postEdit");
}
// The panels never land on a page view: each module is fetched once, on the first click that needs it
// -- never on hover -- and every later call reuses the same promise.
var panelsApi = null;
// A panel is built once and merely shown again after that, so the reveal cannot live in the builders.
// Only click handlers call this -- hover must never pull the panels module.
function revealQueue() {
withPanels(function (panels) {
panels.openQueueBox();
});
}
// Each kind of panel is a module of its own, fetched the first time its kind of pencil is clicked; all of
// them build on wikt.dsb-panels, which carries the queue and everything they share.
var PANEL_MODULES = {
sense: "ext.gadget.wikt.dsb-sense",
subsection: "ext.gadget.wikt.dsb-subsection",
pos: "ext.gadget.wikt.dsb-pos"
};
var panelsPromises = {};
// fn gets wikt.dsb-panels' exports, plus those of the module for part (sense, subsection, pos) if given.
function withPanels(part, fn) {
if (typeof part === "function") {
fn = part;
part = null;
}
var name = PANEL_MODULES[part] || "ext.gadget.wikt.dsb-panels";
if (!panelsPromises[name]) {
panelsPromises[name] = mw.loader.using(name).then(function (require) {
panelsApi = require("ext.gadget.wikt.dsb-panels");
return name === "ext.gadget.wikt.dsb-panels" ? panelsApi : $.extend({}, panelsApi, require(name));
});
}
panelsPromises[name].then(fn, function () {
mw.notify("Nikare amûrên guhartinê bar bike.", { type: "error" });
});
}
// The element holding an example's last line of text: a quotation's source line and its quote are
// blocks, so a pencil after them would sit on a line of its own.
function lastTextLine(el) {
var last = el.lastElementChild;
while (last && /^(DIV|DL|DD|UL|OL|LI)$/.test(last.tagName)) {
el = last;
last = el.lastElementChild;
}
return el;
}
// A pencil on every example and quotation under a sense, opening add-examples' form on that one line.
function attachExampleEditors($li, language, sectionType, sectionOrdinal, definitionLevel, $h3) {
// A queued change redraws the sense (dsb-panels.js raises this); its examples get their pencils again.
if (!$li.data("dsbExampleRedraw")) {
$li.data("dsbExampleRedraw", true);
$li.on("dsbSenseRedrawn", function () {
attachExampleEditors($li, language, sectionType, sectionOrdinal, definitionLevel, $h3);
});
}
sections.exampleItemsOf($li).each(function () {
var item = this;
if ($(item).hasClass("dsb-has-example-toolbar")) {
return;
}
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", "Nimûneyê biguhêre");
var $toolbar = $("<span class='dsb-example-toolbar'></span>").append($toggle);
$toggle.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
withPanels("sense", function (panels) {
panels.editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, item);
});
});
// Touch has no hover: a tap on the example shows its pencil, as a tap on a sense does.
$(lastTextLine(item)).append($toolbar);
$(item).addClass("dsb-has-example-toolbar").on("click.dsbReveal", function () {
$toolbar.addClass("dsb-revealed");
});
});
}
// How many rows the pencil is currently holding open. A pinned row means somebody is part-way through
// an edit, and opening another one under the pointer moves the page: the content below shifts, the
// pointer ends up over something else, that fires its own hover, and the page jumps back and forth.
// So while anything is pinned, hovering opens nothing.
var pinnedRows = 0;
var canHover = !window.matchMedia || window.matchMedia("(hover: hover)").matches;
// The first pencil clicked shows every pencil on the page, so any other part is one tap away. Capture
// phase, as each pencil's own handler stops the click there.
document.addEventListener("click", function (e) {
if (e.target.closest && e.target.closest(".dsb-toggle")) {
document.body.classList.add("dsb-pencils-shown");
}
}, true);
function attachToolbar($li, language, sectionType, sectionOrdinal, definitionLevel, $h3, isLastSense) {
if ($li.children(".dsb-toolbar").length) {
return;
}
var $toolbar = $("<span class='dsb-toolbar'></span>");
// The real InterlanguageLinks pencil icon (same asset defEditButton uses); image swap is in the CSS.
var $toggle = $("<a href='#' class='dsb-toggle' title='Guhartina pêşketî'></a>");
// Revealed by $toggle -- an indented panel under the sense, not always visible. Built lazily on
// first open rather than eagerly for every sense on the page.
var $quickRow = $("<div class='dsb-quick-row'></div>").hide();
var pinned = false; // the pencil pins the row open; hovering alone does not
var hovering = false;
// Builds the row -- fetching the panels module the first time -- then calls back. Asynchronous now,
// so the callback re-checks that the row is still wanted rather than showing it unconditionally.
function ensureBuilt(then) {
if ($quickRow.data("built")) {
then();
return;
}
withPanels("sense", function (panels) {
if (!$quickRow.data("built")) {
panels.buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense);
$quickRow.data("built", true);
}
then();
});
}
// Anything the user has started here: an open field box, the open dropdown, or focus inside the row.
function engaged() {
return pinned ||
$quickRow.find(".dsb-open-boxes").children().length > 0 ||
$quickRow.find(".dsb-more-menu").is(":visible") ||
$.contains($quickRow.get(0), document.activeElement);
}
function setPinned(next) {
if (pinned === next) {
return;
}
pinned = next;
pinnedRows += next ? 1 : -1;
}
$toggle.on("click", function (e) {
e.preventDefault();
// Through their own cancel handler rather than a bare hide(), so a row this closes also drops
// its pinned state -- hiding it alone would leave pinnedRows counting a row nobody can see.
$(".dsb-quick-row").not($quickRow).trigger("dsbCancel");
setPinned(!pinned);
if (pinned) {
ensureBuilt(function () {
if (pinned) {
$quickRow.show();
}
});
} else {
$quickRow.hide();
}
});
// Hover reveals the row and takes it away again, the way en.wikt's does -- but never while something
// on it is in use, or a half-typed box would vanish on the way to the mouse leaving.
$li.on("mouseenter.dsbHover", function () {
// iOS plays a tap as mouseenter first, and a row shown here would swallow that tap's click.
if (!canHover) {
return;
}
hovering = true;
// A row the pencil is holding open is already open, and no row opens on hover while another
// one is: that is the case where the page is being edited and must stay still.
if (pinned || pinnedRows) {
return;
}
// Hover reveals a row that already exists; it never fetches the panels module. Otherwise
// simply moving the pointer across an entry would pull ~100 KB on every page view, which is
// the whole cost the split exists to avoid. The pencil is what asks for it.
if ($quickRow.data("built")) {
$quickRow.show();
}
});
// Raised by the panel's "Betal bike" (see dsb-panels.js): clear the pinned state as well as
// hiding, so one click on the pencil opens it again.
$quickRow.on("dsbCancel", function () {
setPinned(false);
hovering = false;
$quickRow.hide();
});
$li.on("mouseleave.dsbHover", function () {
hovering = false;
if (!engaged()) {
$quickRow.hide();
}
});
$toolbar.append($toggle);
// The class positions the <li>, so the toolbar's gutter slot is measured from the line itself and
// not from wherever it happens to sit in the child order -- ShowIDs prepends its id pill to this
// same <li>.
$li.addClass("dsb-def").prepend($toolbar);
// Touch has no :hover -- a tap anywhere on the line reveals the pencil the same way hovering does on
// desktop (CSS gates this to touch devices; harmless if it also fires on a mouse click).
$li.on("click.dsbReveal", function () {
$toolbar.addClass("dsb-revealed");
});
// Land $quickRow right after the sense's own gloss text -- before any existing "Hevmane: ..." <dl>,
// citation <ul>, or the Nimûne/jêgirtin proxy link's own <ul>, not appended after all of that.
var $firstBlock = $li.children("dl, ul, ol").first();
if ($firstBlock.length) {
$firstBlock.before($quickRow);
} else {
$li.append($quickRow);
}
}
// A section the data marks hidden is left alone.
function isHiddenSection(header) {
return PAGE_SUBSECTIONS.some(function (s) {
return s.hidden && s.header === header;
});
}
// The translation section, whose pencil opens the translation adder on its boxes.
function isWergerSection(header) {
return PAGE_SUBSECTIONS.some(function (s) {
return s.kind === "werger" && s.header === header;
});
}
// Everything between a heading and the next heading at its level or above.
function sectionBody($heading) {
var depth = headingDepth($heading);
var $body = $();
for (var $cursor = $heading.next(); $cursor.length; $cursor = $cursor.next()) {
var d = headingDepth($cursor);
if (d && d <= depth) {
break;
}
$body = $body.add($cursor);
}
return $body;
}
// An editor for the translation adder that fetches the panels only when a change is first made, so a
// page view with translation boxes does not pull them.
function lazyTranslationEditor($h3, language, sectionType) {
var real = null;
function withEditor(fn) {
withPanels(function (panels) {
real = real || panels.translationEditor($h3, language, sectionType);
panels.openQueueBox();
fn(real);
});
}
return {
withCurrentText: function (callback) {
withEditor(function (ed) {
ed.withCurrentText(callback);
});
},
addEdit: function (edit, node) {
withEditor(function (ed) {
ed.addEdit(edit, node);
});
},
error: function (message) {
mw.notify(message, { type: "error" });
},
undo: function () {
withEditor(function (ed) {
ed.undo();
});
}
};
}
// Under "==== Werger ====" every box gets the form for a new translation and a ± on its heading, from
// [[MediaWiki:Gadget-wikt.translation-adder.js]]. The heading's pencil opens the boxes' own form (sense,
// note, id of each box, and more boxes) and puts ✎ and × on the existing lines; "+ Qutiyeke din zêde
// bike" under the boxes opens that form on a new box. All their changes join the save queue. False
// when the section has no box the adder reads.
function attachWergerEditor($hh, $heading, $h3, header, language, sectionType, sectionOrdinal) {
var $body = sectionBody($heading);
if (!$body.find("table.translations").length) {
return false;
}
var editor = lazyTranslationEditor($h3, language, sectionType);
var adder = mw.loader.using("ext.gadget.wikt.translation-adder").then(function (require) {
var api = require("ext.gadget.wikt.translation-adder");
return api.attach($body, editor).then(function () {
return api;
});
});
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", header + " biguhêre");
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($hh, $toolbar);
revealHeadingToolbarOnTap($hh, $toolbar);
$heading.after($panel);
function lineEditing(open) {
if (open) {
// Collapsed boxes are opened, so their lines are in sight.
$body.find(".NavFrame").each(function () {
if ($(this).children(".NavContent").is(":hidden")) {
$(this).find(".NavToggle").first().trigger("click");
}
});
}
adder.then(function (api) {
api.setLineEditing($body, editor, open);
}, function () {
mw.notify("Nikare amûra tercimeyan bar bike.", { type: "error" });
});
}
function showPanel() {
$panel.show();
$toolbar.addClass("dsb-revealed dsb-werger-open");
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels("subsection", function (panels) {
$panel.empty();
panels.buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, header);
});
}
revealQueue();
}
$toggle.on("click", function (e) {
e.preventDefault();
var open = !$panel.is(":visible");
if (open) {
showPanel();
} else {
$panel.hide();
$toolbar.removeClass("dsb-revealed dsb-werger-open");
}
lineEditing(open);
});
// The panel's "Betal bike" closes it the way the pencil does.
$panel.on("dsbPanelClosed", function () {
$toolbar.removeClass("dsb-revealed dsb-werger-open");
lineEditing(false);
});
var $add = $("<a href='#' class='dsb-quick-link'>+ Qutiyeke din zêde bike</a>").on("click", function (e) {
e.preventDefault();
var addBox = $panel.data("dsbAddBox");
if ($panel.is(":visible") && addBox) {
addBox();
return;
}
$panel.data("dsbAddBoxPending", true);
showPanel();
// A form built before this click has its boxes already; the new one goes in now.
if (addBox) {
$panel.removeData("dsbAddBoxPending");
addBox();
}
});
$("<div class='dsb-werger-add'></div>").append($add).insertAfter($body.last());
return true;
}
// Every subsection under a part of speech, whether or not the data knows the header: one it does not
// know opens as wikitext, or as a word list when that is what it holds -- see buildSubsectionPanel.
function attachSubsectionEditors($h3, language, sectionType, sectionOrdinal, posDepth) {
posDepth = posDepth || 3;
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var depth = headingDepth($cursor);
if (depth && depth <= posDepth) {
break;
}
if (depth !== posDepth + 1) {
continue;
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var header = headingText($hh);
if (!header || $hh.data("dsbAttached")) {
continue;
}
if (isWergerSection(header)) {
$hh.data("dsbAttached", true);
// A Werger heading with no box gets the section pencil, whose form adds one; old-style
// boxes the translation adder cannot read are left alone.
if (attachWergerEditor($hh, $cursor, $h3, header, language, sectionType, sectionOrdinal) ||
sectionBody($cursor).find(".NavFrame").length) {
continue;
}
attachSubsectionEditor($hh, $cursor, $h3, header, language, sectionType, sectionOrdinal);
continue;
}
if (isHiddenSection(header)) {
continue;
}
$hh.data("dsbAttached", true);
attachSubsectionEditor($hh, $cursor, $h3, header, language, sectionType, sectionOrdinal);
}
}
// Where the pencil goes depends on what the skin does with its own edit links. Desktop skins put
// "[biguhêre]" right after the heading text, so the pencil follows it and the two read as one group.
// Minerva parks its edit icon at the far right of the heading row instead, a screen-width away from the
// text, so there the pencil goes inside the heading rather than chasing it.
function placeHeadingToolbar($heading, $toolbar) {
if (mw.config.get("skin") !== "minerva") {
var $editsection = headingOuter($heading).find(".mw-editsection").first();
if ($editsection.length) {
$editsection.after($toolbar);
return;
}
}
$heading.append($toolbar);
}
// Touch has no hover, so a heading's pencil is shown by tapping the heading itself. Taps that mean
// something else are left alone: the skin's own edit links, and the pencil once it is showing.
function revealHeadingToolbarOnTap($heading, $toolbar) {
headingOuter($heading).on("click", function (e) {
if ($(e.target).closest("a, .dsb-toolbar").length) {
return;
}
$toolbar.toggleClass("dsb-revealed");
});
}
// One pencil's worth of behaviour, shared by the "====" headings and by an image's own caption: the
// panel is built the first time it is opened, never before, and the pencil stays visible while it is.
// free, when given, pins the panel to a heading located by its own text rather than through a part of
// speech: {header, ordinal}.
function bindPanelToggle($toggle, $toolbar, $panel, $h3, header, language, sectionType, sectionOrdinal, free) {
$toggle.on("click", function (e) {
e.preventDefault();
$panel.toggle();
// Stays put while its panel is open, so it does not vanish from under the pointer.
$toolbar.toggleClass("dsb-revealed", $panel.is(":visible"));
if (!$panel.is(":visible")) {
return;
}
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels("subsection", function (panels) {
$panel.empty();
panels.buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, header, free);
});
}
revealQueue();
});
$panel.on("dsbPanelClosed", function () {
$toolbar.removeClass("dsb-revealed");
});
}
function attachSubsectionEditor($hh, $heading, $h3, header, language, sectionType, sectionOrdinal) {
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", header + " biguhêre");
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($hh, $toolbar);
revealHeadingToolbarOnTap($hh, $toolbar);
$heading.after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $h3, header, language, sectionType, sectionOrdinal);
}
// The "place" subsection that owns the thumbnails under a part-of-speech heading -- named in the data,
// not spelled out here, so renaming it there renames it everywhere.
var IMAGE_HEADER = (function () {
var found = null;
PAGE_SUBSECTIONS.forEach(function (s) {
if (s.place === "pos-top") {
found = s.header;
}
});
return found;
}());
// A pencil on the image's own caption. A wrong caption is noticed while looking at the picture, and
// this is where the writer reaches for it -- the same editor the section's own list offers, opened on
// the image that is already there.
// Every thumbnail between the heading and the first subsection, which is the run "pos-top" covers.
// Anything else in that run is stepped over: a headword line, a declension table or the stylesheet a
// template brings with it all sit before the pictures on plenty of entries.
function attachImageEditors($h3, language, sectionType, sectionOrdinal) {
if (!IMAGE_HEADER) {
return;
}
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if (headingDepth($cursor)) {
break;
}
if ($cursor.is(".thumb, figure, .floatright, .floatleft")) {
attachImageEditor($cursor, $h3, language, sectionType, sectionOrdinal);
}
}
}
// A "===" section that is not a part of speech but is one of ours: Bilêvkirin, which sits above the
// part-of-speech blocks and belongs to the whole language.
function placeSubsectionFor($h3) {
var text = headingText($h3);
var found = null;
PAGE_SUBSECTIONS.forEach(function (s) {
if (s.place === "pron" && s.header === text) {
found = s;
}
});
return found;
}
// The first part-of-speech heading under the same language. The writer works from a section, so
// without one there is nothing to anchor the box to.
function firstPosHeadingAfter($h3) {
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is(".mw-heading2, h2")) {
return null; // the next language starts
}
var depth = headingDepth($cursor);
if (depth !== 3 && depth !== 4) {
continue;
}
var $hh = $cursor.is("h3, h4") ? $cursor : $cursor.find("h3, h4").first();
if (sectionTypeOf($hh) || isManeHeading($hh)) {
return $hh;
}
}
return null;
}
// Its own pencil, opening the same box the section list offers -- a heading nobody can edit from the
// page is a heading people edit by hand.
function attachPlaceHeadingEditor($h3, subsection) {
if ($h3.data("dsbAttached")) {
return;
}
var $pos = firstPosHeadingAfter($h3);
if (!$pos) {
return;
}
var $ol = null;
for (var $cursor = headingOuter($pos).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("ol")) {
$ol = $cursor;
break;
}
if ($cursor.is(".mw-heading2, .mw-heading3, h2, h3")) {
break;
}
}
var language = $ol && languageCodeForOl($ol);
if (!language) {
return;
}
$h3.data("dsbAttached", true);
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", subsection.header + " " + msg.edit);
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($h3, $toolbar);
revealHeadingToolbarOnTap($h3, $toolbar);
headingOuter($h3).after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $pos, subsection.header, language,
sectionTypeOf($pos) || MANE_SECTION, sectionOrdinalWithinLanguage($pos));
}
// Every other heading inside a language: the ones that sit beside the parts of speech rather than
// under one ("=== Etîmolojî ===", "=== Çavkanî ==="), and the ones nobody planned for. Each opens
// the wikitext under its own heading, or the {{kol3}} form when that is what it holds.
function attachFreeSectionEditors() {
$("#mw-content-text").find("h3, h4, h5, h6").each(function () {
var $h = $(this);
// A heading that already carries one of this gadget's pencils is spoken for.
if ($h.data("dsbAttached") || headingOuter($h).find(".dsb-toolbar").length) {
return;
}
var header = headingText($h);
if (!header || isHiddenSection(header)) {
return;
}
var language = languageCodeForOl($h);
if (!language) {
return;
}
$h.data("dsbAttached", true);
attachFreeSectionEditor($h, header, language);
});
}
function attachFreeSectionEditor($h, header, language) {
var free = { header: header, ordinal: headingTextOrdinalWithinLanguage($h) };
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", header + " " + msg.edit);
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$toolbar.append($toggle);
placeHeadingToolbar($h, $toolbar);
revealHeadingToolbarOnTap($h, $toolbar);
headingOuter($h).after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $h, header, language, null, 0, free);
}
function attachImageEditor($fig, $h3, language, sectionType, sectionOrdinal) {
if ($fig.data("dsbAttached")) {
return;
}
var $caption = $fig.find("figcaption, .thumbcaption").first();
if (!$caption.length) {
return;
}
$fig.data("dsbAttached", true);
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-caption'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", IMAGE_HEADER + " biguhêre");
// After the figure, not inside it: the panel is full width, and a floated thumbnail is not.
var $panel = $("<div class='dsb-subsection-panel dsb-panel-after-image'></div>").hide();
$toolbar.append($toggle);
$caption.append($toolbar);
revealHeadingToolbarOnTap($caption, $toolbar);
$fig.after($panel);
bindPanelToggle($toggle, $toolbar, $panel, $h3, IMAGE_HEADER, language, sectionType, sectionOrdinal);
}
function attachSubsectionTrigger($h3, $ol, language, sectionType, sectionOrdinal) {
if ($h3.data("dsbAttached")) {
return;
}
$h3.data("dsbAttached", true);
var $anchor = $ol;
for (var $cursor = $ol.next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is(".mw-heading2, .mw-heading3, .mw-heading4, .mw-heading5, .mw-heading6, h2, h3, h4, h5, h6")) {
break;
}
$anchor = $cursor;
}
var $trigger = $("<a href='#' class='dsb-add-subsection'>+ Binebeşekê lê zêde bike</a>");
var $panel = $("<div class='dsb-subsection-panel'></div>").hide();
$trigger.on("click", function (e) {
e.preventDefault();
$panel.toggle();
if (!$panel.is(":visible")) {
return;
}
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels("subsection", function (panels) {
$panel.empty();
panels.buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal);
});
}
revealQueue();
});
$anchor.after($panel);
$anchor.after($trigger);
}
// Attaches the "choose a part of speech" trigger under a heading whose part of speech is unsettled: an
// unclassified "=== Mane ===" placeholder, or a "=== Biwêj ===" that is really a noun, verb, ... .
function attachConvertPosTrigger($h3, sectionType) {
if ($h3.data("dsbConvertAttached")) {
return;
}
$h3.data("dsbConvertAttached", true);
// languageCodeForOl() is really just "nearest preceding .besazimani[id] before this element in
// document order" -- nothing ol-specific about it, so it works unchanged for a heading too.
var language = languageCodeForOl($h3);
if (!language) {
return;
}
var sectionOrdinal = sectionOrdinalWithinLanguage($h3);
// The same pencil the "====" subsections get, so reclassifying reads as an edit affordance rather
// than a line of body text.
var $toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
var $toggle = $("<a href='#' class='dsb-toggle'></a>").attr("title", "Cureyê rêzimanî hilbijêre");
var $panel = $("<div class='dsb-convert-pos-panel'></div>").hide();
$toolbar.append($toggle);
$toggle.on("click", function (e) {
e.preventDefault();
$panel.toggle();
// Stays put while its panel is open, so it does not vanish from under the pointer.
$toolbar.toggleClass("dsb-revealed", $panel.is(":visible"));
if (!$panel.is(":visible")) {
return;
}
if (!$panel.data("built")) {
$panel.data("built", true);
$panel.text(msg.loading);
withPanels("pos", function (panels) {
$panel.empty();
panels.buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal);
});
}
revealQueue();
});
// Raised by the panel's "Betal bike": closes it and drops what was typed, so the pencil reopens it fresh.
$panel.on("dsbCancel", function () {
$panel.hide().empty().data("built", false);
$toolbar.removeClass("dsb-revealed");
});
placeHeadingToolbar($h3, $toolbar);
revealHeadingToolbarOnTap($h3, $toolbar);
headingOuter($h3).after($panel);
}
// ---- driver ----
function init() {
$("h3, h4").each(function () {
var $h3 = $(this);
var posDepth = headingDepth($h3) || 3;
var sectionType = sectionTypeOf($h3);
if (!sectionType) {
var place = placeSubsectionFor($h3);
if (place) {
attachPlaceHeadingEditor($h3, place);
return;
}
if (!isManeHeading($h3)) {
return; // not a recognized word-sense section (Etîmolojî, Çavkanî, ...)
}
// An unclassified placeholder: offer the POS picker, then carry on and wire up the same
// per-sense pencils, so the POS choice and the sense edits can be queued and saved together.
sectionType = MANE_SECTION;
attachConvertPosTrigger($h3, sectionType);
} else {
// Every part of speech, not just the ones most often mistaken: any section can turn out to
// be another, and the picker is what changes it.
attachConvertPosTrigger($h3, sectionType);
}
var sectionOrdinal = sectionOrdinalWithinLanguage($h3);
var $ol = null;
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("ol")) {
$ol = $cursor;
break;
}
if (headingDepth($cursor)) {
break;
}
}
if (!$ol) {
return;
}
var language = languageCodeForOl($ol);
if (!language) {
return;
}
var lastTopIndex = $ol.children("li").not(".mw-empty-elt").length - 1;
eachLeafDefinition($ol, function ($li) {
var definitionLevel = [];
var $definitionItem = $li;
while ($definitionItem.prop("tagName") === "LI") {
definitionLevel.splice(0, 0, $definitionItem.prevAll("li:not(.mw-empty-elt)").length);
$definitionItem = $definitionItem.parent().parent();
}
definitionLevel.splice(0, 0, sectionType + "|" + sectionOrdinal);
var isLastSense = definitionLevel.length === 2 && definitionLevel[1] === lastTopIndex;
attachToolbar($li, language, sectionType, sectionOrdinal, definitionLevel, $h3, isLastSense);
attachExampleEditors($li, language, sectionType, sectionOrdinal, definitionLevel, $h3);
});
attachSubsectionTrigger($h3, $ol, language, sectionType, sectionOrdinal, posDepth);
attachSubsectionEditors($h3, language, sectionType, sectionOrdinal, posDepth);
attachImageEditors($h3, language, sectionType, sectionOrdinal);
});
// Last, so a heading one of the walks above claimed keeps the pencil that knows more about it.
attachFreeSectionEditors();
attachHeadingTools();
}
// Beside each heading's pencil: ▲ ▼ to move an "===" section within its language, and its depth
// (h3, h4, h5) for any heading from "===" to "=====". Both go into the save queue.
function attachHeadingTools() {
$("#mw-content-text").find("h3, h4, h5").each(function () {
var $h = $(this);
var depth = headingDepth($h);
var language = languageCodeForOl($h);
if (depth < 3 || depth > 5 || !language || !headingText($h) || $h.data("dsbHeadingTools")) {
return;
}
$h.data("dsbHeadingTools", true);
var $toolbar = headingOuter($h).find(".dsb-toolbar-heading").first();
if (!$toolbar.length) {
$toolbar = $("<span class='dsb-toolbar dsb-toolbar-heading'></span>");
placeHeadingToolbar($h, $toolbar);
revealHeadingToolbarOnTap($h, $toolbar);
}
var $tools = $("<span class='dsb-heading-tools'></span>");
if (depth === 3) {
[[-1, "▲", "Vê beşê bibe jor"], [1, "▼", "Vê beşê bibe jêr"]].forEach(function (b) {
$("<button type='button' class='dsb-heading-move'></button>").text(b[1]).attr("title", b[2])
.on("click", function (e) {
e.preventDefault();
withPanels(function (panels) {
panels.moveH3(language, $h, b[0]);
});
revealQueue();
})
.appendTo($tools);
});
}
var $depth = $("<select class='dsb-heading-depth'></select>").attr("title", "Dereceya sernavê");
[3, 4, 5].forEach(function (d) {
$depth.append($("<option></option>").val(d).text("h" + d));
});
$depth.val(depth).on("change", function () {
var to = Number($depth.val());
withPanels(function (panels) {
panels.setDepth(language, $h, to);
});
revealQueue();
});
$tools.append($depth);
$toolbar.append($tools);
});
}
// One listener for every "Hevmane û Bêhtir" dropdown on the page.
$(document).on("click.dsbMoreMenu", function (e) {
$(".dsb-more-menu:visible").each(function () {
var $menu = $(this);
if ($(e.target).closest($menu).length || $(e.target).closest($menu.prev(".dsb-more-toggle")).length) {
return;
}
$menu.slideUp();
var $arrow = $menu.data("dsbArrow");
if ($arrow) {
$arrow.text("▸");
}
});
});
// Closes every open panel/preview this gadget can leave in the DOM, so bfcache never freezes one mid-open.
function closeAllOpenUi() {
if (panelsApi) {
panelsApi.clearQueue();
panelsApi.closeAllPanels();
return;
}
// Before the panels arrive there is nothing built to tear down, only the view-path rows.
$(".dsb-quick-row, .dsb-more-menu, .dsb-preview-inline, .dsb-preview-block").hide();
}
$(window).on("pagehide", closeAllOpenUi);
// Belt-and-suspenders: also close on restore-from-bfcache, in case pagehide's own hide didn't stick.
$(window).on("pageshow", function (e) {
if (e.originalEvent && e.originalEvent.persisted) {
closeAllOpenUi();
}
});
$(init);
// </nowiki>
}());
// </nowiki>
m02ovjf8u5www5zxkcbr9zf27zr2snq
MediaWiki:Gadget-wikt.dsb.css
8
1359172
6865607
6865452
2026-10-23T21:31:50Z
Wikihez
7344
Heading tools: ▲ ▼ on === headings, h3/h4/h5 picker on === to =====
6865607
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb.css]]
*
* Styles for the view path only: the pencil and its toolbar, which is all wikt.dsb draws before someone
* clicks. Everything the panels need is in wikt.dsb-panels.css, loaded with them.
* Colour and spacing tokens come from wikt.core.css.
*/
/* position:absolute with top/left auto keeps the static position but drops the box out of flow; the negative margin then slides the pencil into the list gutter, in front of the marker (en.wikt's .defEditButton trick). */
/* Anchors the gutter the pencil sits in. Without it the toolbar is placed from its static position, which
moves as soon as anything else is prepended to the line. */
li.dsb-def {
position: relative;
}
/* A sense line is positioned (its pencil hangs off it), so its own background -- the :target highlight
after clicking "mane 1" in a caption, or a queued edit's preview -- would paint over a picture floated
beside it. The pictures sit one layer up instead. */
.mw-parser-output figure,
.mw-parser-output .thumb {
position: relative;
z-index: 1;
}
/* {{bênimûne}}'s own "Nimûneyekê lê zêde bike" line: the sense's "+ Nimûne/jêgirtin" item offers the same.
Here rather than with add-examples, which only loads once a panel is opened. */
.mw-parser-output li:has(> .benimune) {
display: none;
}
.dsb-toolbar {
position: absolute;
left: -40px;
top: 0;
width: 40px;
height: 26px;
font-family: var(--wikt-sans);
}
/* A heading's pencil is inline, so it keeps the flow and needs none of the above. */
/* On an image's caption: inline after the caption text, and always visible -- an entry carries a
picture or two, not a column of headings, so there is nothing here for a hidden icon to spare.
The z-index lifts it over the sense lines: a sense's <li> is positioned and its box runs the full
width of the column, right under a floated picture, so it would otherwise take the click. */
.dsb-toolbar-caption {
position: relative;
left: auto;
top: auto;
z-index: 1;
display: inline-block;
width: auto;
height: auto;
margin-left: 0.35em;
vertical-align: middle;
white-space: nowrap;
}
.dsb-toolbar-caption .dsb-toggle {
display: inline-block;
margin: 0;
vertical-align: middle;
opacity: 0.55;
transition: opacity 0.12s ease;
}
.dsb-toolbar-caption .dsb-toggle:focus,
.dsb-toolbar-caption.dsb-revealed .dsb-toggle {
opacity: 1;
}
/* Beside the picture rather than below it: the thumbnails sit above the definitions, so a panel that
cleared them dropped past every float and took the whole definition list down with it. */
.dsb-panel-after-image {
display: flow-root;
}
.dsb-toolbar-heading {
left: auto;
top: auto;
}
.dsb-toggle {
display: block;
margin-top: 5px; /* sits on the "1." marker; a pencil reads low, so it is lifted off centre */
width: 13px;
height: 13px;
background-image: url(//upload.wikimedia.org/wikipedia/commons/d/d3/InterlanguageLinks-Asset-Pencil.gif);
background-repeat: no-repeat;
background-position: center;
background-size: contain;
text-decoration: none;
cursor: pointer;
opacity: 0;
transition: opacity 0.15s ease;
}
.dsb-toggle:hover,
.dsb-toggle:focus {
background-image: url(//upload.wikimedia.org/wikipedia/commons/d/d1/InterlanguageLinks-Asset-Pencil-Hover.gif);
}
/* Same pencil on a "====" or "===" heading: no list gutter to sit in, so it stays inline after the text. */
.dsb-toolbar-heading {
position: static;
display: inline-block;
width: auto;
height: auto;
margin-left: 0;
vertical-align: middle;
}
.dsb-toolbar-heading .dsb-toggle {
display: inline-block;
margin: 0 0 0 6px;
vertical-align: middle;
/* Hidden until the heading is hovered, like the per-sense pencil: a page with several subsections
otherwise carries an icon on every heading, all competing for the eye at once. */
opacity: 0;
}
/* Revealed by hovering anywhere on the heading, by keyboard focus, and while its own panel is open
(dsb-revealed, set in dsb.js) so it never disappears from under the pointer mid-edit. */
.dsb-toolbar-heading .dsb-toggle:focus,
.dsb-toolbar-heading.dsb-revealed .dsb-toggle {
opacity: 1;
}
/* Reveal rules, in ascending specificity so a later one never loses to an earlier one. */
.dsb-toggle:focus {
opacity: 1;
}
/* Touch has no hover, so a toolbar marks itself revealed on tap instead. A heading's pencil is revealed
by a tap on the heading (see revealHeadingToolbarOnTap in dsb.js), so a page of subsections is not a
column of icons before anything has been asked for. */
@media (hover: none), (pointer: coarse) {
.dsb-toolbar.dsb-revealed .dsb-toggle {
opacity: 1;
}
}
/* The glyph stays 13px -- the asset is only 10px, so scaling it up just blurs it. The tap target grows
invisibly instead, to the 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-toggle {
position: relative;
}
.dsb-toggle::after {
content: "";
position: absolute;
inset: -10px;
}
}
/* Hover reveals only where there is a real pointer. On iOS Safari a tap first plays :hover, and when that
makes something visible the tap is spent on it and the link underneath needs a second tap. Touch gets
the pencils from dsb-revealed instead. */
@media (hover: hover) {
li:hover .dsb-toolbar .dsb-toggle,
.dsb-has-example-toolbar:hover .dsb-example-toolbar .dsb-toggle,
.mw-heading:hover .dsb-toolbar-heading .dsb-toggle,
h2:hover .dsb-toolbar-heading .dsb-toggle,
h3:hover .dsb-toolbar-heading .dsb-toggle,
h4:hover .dsb-toolbar-heading .dsb-toggle,
figure:hover .dsb-toolbar-caption .dsb-toggle,
.thumb:hover .dsb-toolbar-caption .dsb-toggle {
opacity: 1;
}
}
/* The pencil on each example and quotation: at the end of its last line, shown while that example is
hovered. */
.dsb-example-toolbar {
display: inline-block;
margin-left: 6px;
vertical-align: middle;
}
.dsb-example-toolbar .dsb-toggle {
display: inline-block;
margin: 0;
opacity: 0;
transition: opacity 0.12s ease;
}
.dsb-example-toolbar .dsb-toggle:focus,
.dsb-example-toolbar.dsb-revealed .dsb-toggle {
opacity: 1;
}
/* Once any pencil has been clicked, every pencil on the page stays visible (dsb-pencils-shown, set in dsb.js). */
body.dsb-pencils-shown .dsb-toggle {
opacity: 1;
}
/* ── Built on the view path ───────────────────────────────────────────
dsb.js creates these before wikt.dsb-panels is fetched, so their styles have to travel with it rather
than with the panels: the "+ Binebeşekê lê zêde bike" link is visible from the start, and the panel
containers are on screen showing "Tê barkirin…" while the module is still on its way. Everything
inside the panels stays in wikt.dsb-panels.css -- that markup does not exist until it has loaded. */
/* Matched to the add-examples gadget's own "Nimûneyeke bikaranînê lê zêde bike" link: a quiet subtle-grey
secondary action at the page's own font and size, not an accent-coloured one. Deliberately no --wikt-sans,
so it inherits the page font the way that link does. */
.dsb-add-subsection {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 8px 0;
font-size: inherit;
font-weight: 400;
color: var( --color-subtle, #54595d );
text-decoration: none;
border-bottom: 0;
cursor: pointer;
}
.dsb-add-subsection:hover,
.dsb-add-subsection:focus {
color: var( --color-base, #202122 );
text-decoration: underline;
}
.dsb-subsection-panel,
.dsb-convert-pos-panel {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
box-sizing: border-box;
max-width: 520px;
margin: 6px 0 14px;
padding: 14px 16px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card-sunken);
border: 1px solid var(--wikt-card-edge);
border-radius: var(--wikt-r-card);
}
/* Mobile: the containers above lose the desktop width cap. */
.skin-minerva .dsb-quick-row,
.skin-minerva .dsb-subsection-panel,
.skin-minerva .dsb-convert-pos-panel {
max-width: 100%;
}
/* ▲ ▼ and the h3/h4/h5 picker after a heading's pencil, shown when the pencil is. */
.dsb-heading-tools {
display: inline-flex;
align-items: center;
gap: 2px;
margin-left: 6px;
vertical-align: middle;
font-family: var(--wikt-sans, sans-serif);
font-size: 12px;
font-weight: normal;
opacity: 0;
transition: opacity 0.15s ease;
}
.dsb-heading-move,
.dsb-heading-depth {
height: 20px;
padding: 0 4px;
font: inherit;
line-height: 18px;
color: var(--color-base, #202122);
background: var(--background-color-base, #fff);
border: 1px solid var(--border-color-base, #a2a9b1);
border-radius: 2px;
cursor: pointer;
}
.dsb-heading-move:hover,
.dsb-heading-depth:hover {
border-color: var(--border-color-progressive, #36c);
}
.dsb-heading-tools:focus-within,
.dsb-toolbar-heading.dsb-revealed .dsb-heading-tools,
body.dsb-pencils-shown .dsb-heading-tools {
opacity: 1;
}
@media (hover: hover) {
.mw-heading:hover .dsb-heading-tools,
h3:hover .dsb-heading-tools,
h4:hover .dsb-heading-tools,
h5:hover .dsb-heading-tools {
opacity: 1;
}
}
/* </nowiki> */
8moqgqbo6m6oyazpf0ejdjqao6jz45z
6865609
6865607
2026-10-23T21:48:00Z
Wikihez
7344
Heading tools hidden until a pencil has been clicked
6865609
css
text/css
/* <nowiki> */
/*
* Page: [[MediaWiki:Gadget-wikt.dsb.css]]
*
* Styles for the view path only: the pencil and its toolbar, which is all wikt.dsb draws before someone
* clicks. Everything the panels need is in wikt.dsb-panels.css, loaded with them.
* Colour and spacing tokens come from wikt.core.css.
*/
/* position:absolute with top/left auto keeps the static position but drops the box out of flow; the negative margin then slides the pencil into the list gutter, in front of the marker (en.wikt's .defEditButton trick). */
/* Anchors the gutter the pencil sits in. Without it the toolbar is placed from its static position, which
moves as soon as anything else is prepended to the line. */
li.dsb-def {
position: relative;
}
/* A sense line is positioned (its pencil hangs off it), so its own background -- the :target highlight
after clicking "mane 1" in a caption, or a queued edit's preview -- would paint over a picture floated
beside it. The pictures sit one layer up instead. */
.mw-parser-output figure,
.mw-parser-output .thumb {
position: relative;
z-index: 1;
}
/* {{bênimûne}}'s own "Nimûneyekê lê zêde bike" line: the sense's "+ Nimûne/jêgirtin" item offers the same.
Here rather than with add-examples, which only loads once a panel is opened. */
.mw-parser-output li:has(> .benimune) {
display: none;
}
.dsb-toolbar {
position: absolute;
left: -40px;
top: 0;
width: 40px;
height: 26px;
font-family: var(--wikt-sans);
}
/* A heading's pencil is inline, so it keeps the flow and needs none of the above. */
/* On an image's caption: inline after the caption text, and always visible -- an entry carries a
picture or two, not a column of headings, so there is nothing here for a hidden icon to spare.
The z-index lifts it over the sense lines: a sense's <li> is positioned and its box runs the full
width of the column, right under a floated picture, so it would otherwise take the click. */
.dsb-toolbar-caption {
position: relative;
left: auto;
top: auto;
z-index: 1;
display: inline-block;
width: auto;
height: auto;
margin-left: 0.35em;
vertical-align: middle;
white-space: nowrap;
}
.dsb-toolbar-caption .dsb-toggle {
display: inline-block;
margin: 0;
vertical-align: middle;
opacity: 0.55;
transition: opacity 0.12s ease;
}
.dsb-toolbar-caption .dsb-toggle:focus,
.dsb-toolbar-caption.dsb-revealed .dsb-toggle {
opacity: 1;
}
/* Beside the picture rather than below it: the thumbnails sit above the definitions, so a panel that
cleared them dropped past every float and took the whole definition list down with it. */
.dsb-panel-after-image {
display: flow-root;
}
.dsb-toolbar-heading {
left: auto;
top: auto;
}
.dsb-toggle {
display: block;
margin-top: 5px; /* sits on the "1." marker; a pencil reads low, so it is lifted off centre */
width: 13px;
height: 13px;
background-image: url(//upload.wikimedia.org/wikipedia/commons/d/d3/InterlanguageLinks-Asset-Pencil.gif);
background-repeat: no-repeat;
background-position: center;
background-size: contain;
text-decoration: none;
cursor: pointer;
opacity: 0;
transition: opacity 0.15s ease;
}
.dsb-toggle:hover,
.dsb-toggle:focus {
background-image: url(//upload.wikimedia.org/wikipedia/commons/d/d1/InterlanguageLinks-Asset-Pencil-Hover.gif);
}
/* Same pencil on a "====" or "===" heading: no list gutter to sit in, so it stays inline after the text. */
.dsb-toolbar-heading {
position: static;
display: inline-block;
width: auto;
height: auto;
margin-left: 0;
vertical-align: middle;
}
.dsb-toolbar-heading .dsb-toggle {
display: inline-block;
margin: 0 0 0 6px;
vertical-align: middle;
/* Hidden until the heading is hovered, like the per-sense pencil: a page with several subsections
otherwise carries an icon on every heading, all competing for the eye at once. */
opacity: 0;
}
/* Revealed by hovering anywhere on the heading, by keyboard focus, and while its own panel is open
(dsb-revealed, set in dsb.js) so it never disappears from under the pointer mid-edit. */
.dsb-toolbar-heading .dsb-toggle:focus,
.dsb-toolbar-heading.dsb-revealed .dsb-toggle {
opacity: 1;
}
/* Reveal rules, in ascending specificity so a later one never loses to an earlier one. */
.dsb-toggle:focus {
opacity: 1;
}
/* Touch has no hover, so a toolbar marks itself revealed on tap instead. A heading's pencil is revealed
by a tap on the heading (see revealHeadingToolbarOnTap in dsb.js), so a page of subsections is not a
column of icons before anything has been asked for. */
@media (hover: none), (pointer: coarse) {
.dsb-toolbar.dsb-revealed .dsb-toggle {
opacity: 1;
}
}
/* The glyph stays 13px -- the asset is only 10px, so scaling it up just blurs it. The tap target grows
invisibly instead, to the 44px minimum. */
@media (max-width: 720px), (pointer: coarse) {
.dsb-toggle {
position: relative;
}
.dsb-toggle::after {
content: "";
position: absolute;
inset: -10px;
}
}
/* Hover reveals only where there is a real pointer. On iOS Safari a tap first plays :hover, and when that
makes something visible the tap is spent on it and the link underneath needs a second tap. Touch gets
the pencils from dsb-revealed instead. */
@media (hover: hover) {
li:hover .dsb-toolbar .dsb-toggle,
.dsb-has-example-toolbar:hover .dsb-example-toolbar .dsb-toggle,
.mw-heading:hover .dsb-toolbar-heading .dsb-toggle,
h2:hover .dsb-toolbar-heading .dsb-toggle,
h3:hover .dsb-toolbar-heading .dsb-toggle,
h4:hover .dsb-toolbar-heading .dsb-toggle,
figure:hover .dsb-toolbar-caption .dsb-toggle,
.thumb:hover .dsb-toolbar-caption .dsb-toggle {
opacity: 1;
}
}
/* The pencil on each example and quotation: at the end of its last line, shown while that example is
hovered. */
.dsb-example-toolbar {
display: inline-block;
margin-left: 6px;
vertical-align: middle;
}
.dsb-example-toolbar .dsb-toggle {
display: inline-block;
margin: 0;
opacity: 0;
transition: opacity 0.12s ease;
}
.dsb-example-toolbar .dsb-toggle:focus,
.dsb-example-toolbar.dsb-revealed .dsb-toggle {
opacity: 1;
}
/* Once any pencil has been clicked, every pencil on the page stays visible (dsb-pencils-shown, set in dsb.js). */
body.dsb-pencils-shown .dsb-toggle {
opacity: 1;
}
/* ── Built on the view path ───────────────────────────────────────────
dsb.js creates these before wikt.dsb-panels is fetched, so their styles have to travel with it rather
than with the panels: the "+ Binebeşekê lê zêde bike" link is visible from the start, and the panel
containers are on screen showing "Tê barkirin…" while the module is still on its way. Everything
inside the panels stays in wikt.dsb-panels.css -- that markup does not exist until it has loaded. */
/* Matched to the add-examples gadget's own "Nimûneyeke bikaranînê lê zêde bike" link: a quiet subtle-grey
secondary action at the page's own font and size, not an accent-coloured one. Deliberately no --wikt-sans,
so it inherits the page font the way that link does. */
.dsb-add-subsection {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 8px 0;
font-size: inherit;
font-weight: 400;
color: var( --color-subtle, #54595d );
text-decoration: none;
border-bottom: 0;
cursor: pointer;
}
.dsb-add-subsection:hover,
.dsb-add-subsection:focus {
color: var( --color-base, #202122 );
text-decoration: underline;
}
.dsb-subsection-panel,
.dsb-convert-pos-panel {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 10px;
box-sizing: border-box;
max-width: 520px;
margin: 6px 0 14px;
padding: 14px 16px;
font-family: var(--wikt-sans);
font-size: 13px;
color: var(--wikt-ink-2);
background: var(--wikt-card-sunken);
border: 1px solid var(--wikt-card-edge);
border-radius: var(--wikt-r-card);
}
/* Mobile: the containers above lose the desktop width cap. */
.skin-minerva .dsb-quick-row,
.skin-minerva .dsb-subsection-panel,
.skin-minerva .dsb-convert-pos-panel {
max-width: 100%;
}
/* ▲ ▼ and the h3/h4/h5 picker after a heading's pencil: left out until a pencil has been clicked
(dsb-pencils-shown), so reading the page never shows them. */
.dsb-heading-tools {
display: none;
align-items: center;
gap: 2px;
margin-left: 6px;
vertical-align: middle;
font-family: var(--wikt-sans, sans-serif);
font-size: 12px;
font-weight: normal;
}
.dsb-heading-move,
.dsb-heading-depth {
height: 20px;
padding: 0 4px;
font: inherit;
line-height: 18px;
color: var(--color-base, #202122);
background: var(--background-color-base, #fff);
border: 1px solid var(--border-color-base, #a2a9b1);
border-radius: 2px;
cursor: pointer;
}
.dsb-heading-move:hover,
.dsb-heading-depth:hover {
border-color: var(--border-color-progressive, #36c);
}
body.dsb-pencils-shown .dsb-heading-tools {
display: inline-flex;
}
/* </nowiki> */
e4h52x7tfuonc47h40jvowxvrk717tz
MediaWiki:Gadget-wikt.dsb-panels.js
8
1359173
6865604
6865472
2026-10-23T21:16:21Z
Wikihez
7344
Queue: reorder a language's === sections and change a heading's depth
6865604
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* Everything DefSideBoxes shows once you click: the per-sense advanced panel, the subsection editors,
* the part-of-speech picker, the template dialogs, the preview and the queued save.
*
* Loaded on demand by wikt.dsb, never on a plain page view. It exports only what that half calls.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var langs = edit.langs;
var escapeRegExp = util.escapeRegExp;
// Local names for the shared wikitext layer, so every call site below reads as it always did.
var langHeaderSource = wt.langHeaderSource;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var splitTemplateParams = wt.splitTemplateParams;
var parseTemplateCalls = wt.parseTemplateCalls;
var splitCallParams = wt.splitCallParams;
var buildTemplateCall = wt.buildTemplateCall;
var tdText = wt.tdText;
var fetchTemplateData = wt.fetchTemplateData;
var parseWordListCall = wt.parseWordListCall;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var sectionNames = sections.sectionNames;
var MANE_SECTION = sections.MANE_SECTION;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var isManeHeading = sections.isManeHeading;
var sectionTypeOf = sections.sectionTypeOf;
var sectionOrdinalWithinLanguage = sections.sectionOrdinalWithinLanguage;
var isFormOfWrapper = sections.isFormOfWrapper;
var eachLeafDefinition = sections.eachLeafDefinition;
var api = new mw.Api();
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from Modul:translations' "trans-top": 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, column-width. A
// box's params are {"1": …, "2": …, name: …}: every named parameter it has is kept, known or not.
// The opening call alone, under the template name the page already uses (a test copy stays one).
function buildWergerTop(params, name) {
var parts = [name || "werger-ser"];
// Positional slots up to the last one filled: 2 can only be reached past 1.
var last = 0;
Object.keys(params).forEach(function (key) {
if (/^\d+$/.test(key) && params[key]) {
last = Math.max(last, +key);
}
});
for (var n = 1; n <= last; n++) {
var value = params[String(n)] || "";
// An "=" would make it a named parameter.
parts.push(value.indexOf("=") !== -1 ? n + "=" + value : value);
}
Object.keys(params).forEach(function (key) {
if (!/^\d+$/.test(key) && params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}";
}
function buildWergerBlock(params) {
return buildWergerTop(params) + "\n{{werger-bin}}";
}
// Every box's opening call in text, in order, as {start, end, name, params}; params is the same
// {1, 2, id, ...} object buildWergerTop() takes. Scans for the matching close rather than the first
// "}}": a gloss can hold a template of its own.
function wergerBoxes(text) {
var out = [];
// {{werger-bnr-h}} is a box too: its head also points to other pages' boxes.
var re = /\{\{\s*(werger-(?:ser|bnr-h)(?:\/ceribandin)?)\s*(?=[|}])/g;
var m;
while ((m = re.exec(text))) {
var depth = 1;
var start = m.index + m[0].length;
var end = -1;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
end = i + 1;
break;
}
}
}
if (end === -1) {
break;
}
var body = text.charAt(start) === "|" ? text.slice(start + 1, end - 2) : "";
out.push({ start: m.index, end: end, name: m[1], params: parseWergerArgs(body) });
re.lastIndex = end;
}
return out;
}
function parseWergerArgs(body) {
var out = {};
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var named = /^\s*([^=|{}\[\]<>]+?)\s*=([\s\S]*)$/.exec(arg);
if (named) {
out[named[1]] = $.trim(named[2]);
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// The section text with the given boxes' opening calls rewritten: rows of {box, params}, where box is
// the box's place among wergerBoxes(text).
function rewriteWergerTops(text, rows) {
var boxes = wergerBoxes(text);
rows.slice().sort(function (a, b) {
return b.box - a.box;
}).forEach(function (row) {
var box = boxes[row.box];
if (!box) {
throw new SubmitAbort("Qutiya tercimeyê ya hejmar " + (row.box + 1) + " nehate dîtin.");
}
text = text.slice(0, box.start) + buildWergerTop(row.params, box.name) + text.slice(box.end);
});
return text;
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
var final = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runFinal ? final : entry.runLast ? last : normal).push(entry);
});
// Stable, so two entries of one kind keep the order they were queued in.
final = final.map(function (entry, i) {
return { entry: entry, i: i };
}).sort(function (a, b) {
return a.entry.runFinal - b.entry.runFinal || a.i - b.i;
}).map(function (x) {
return x.entry;
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last, final);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
// skipKey leaves out the entry queued under that key, for a form reopened on its own queued change.
function queuedContent(pageContent, skipKey) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast && !entry.runFinal && !(skipKey && entry.key === skipKey);
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
// A save on a sense supersedes the one queued for it (they share a key), so it has to write that one's
// fields too. newer over older: a field the newer save touched wins, the rest stays as the older save
// left it. A gloss is only ever present when its box was open, so a missing one means untouched.
function mergeSenseChanges(older, newer) {
if (!older) {
return newer;
}
var merged = {
gloss: newer.gloss || older.gloss,
maneid: newer.changed.maneid ? newer.maneid : older.maneid,
etiket: newer.changed.etiket ? newer.etiket : older.etiket,
kategori: newer.changed.kategori ? newer.kategori : older.kategori,
words: {},
changed: $.extend({}, older.changed, newer.changed)
};
SENSE_GROUPS.forEach(function (group) {
merged.words[group.key] = newer.changed["word:" + group.key]
? newer.words[group.key]
: (older.words[group.key] || []);
});
return merged;
}
// The sense's fields as the page has them, with a queued change written over the top: what the boxes
// prefill from and what collectChanges() diffs against, so reopening a box shows the value that is
// waiting to be saved rather than the one it is about to replace.
function overlaySenseFields(fields, queued) {
if (!queued) {
return fields;
}
var out = {
gloss: queued.gloss || fields.gloss,
maneid: queued.changed.maneid ? queued.maneid : fields.maneid,
etiket: queued.changed.etiket ? queued.etiket : fields.etiket,
kategori: queued.changed.kategori ? queued.kategori : fields.kategori,
words: {}
};
SENSE_GROUPS.forEach(function (group) {
out.words[group.key] = queued.changed["word:" + group.key]
? queued.words[group.key]
: fields.words[group.key];
});
return out;
}
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
// After even the runLast ones, in this order: 1 rewrites the order of a language's "===" sections,
// 2 the depth of one heading. Both find their headings as the other changes leave them.
runFinal: opts.runFinal || 0,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// The sense fields this entry writes, merged with whatever an earlier entry under the same
// key wrote, so superseding it does not drop what it had already collected.
senseChanges: opts.senseChanges || null,
// What the form that queued it held, so the form can be reopened on it.
formState: opts.formState || null,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// ---- heading order and depth ----
var H3 = /^===(?!=)[ ]*(.+?)[ ]*===\s*$/;
// The end of a language: blank lines, "----", categories and magic words stay at the end whatever
// section comes last.
function isTailLine(line) {
return !line.trim() || /^-{4,}\s*$/.test(line) || /^\s*\[\[\s*(Kategorî|Category)\s*:/i.test(line) ||
/^__[A-ZÊÎÛÇŞ]+__\s*$/.test(line);
}
// One "===" section's id within its language: its text, and which of the "===" headings with that text
// it is.
function h3Id(header, ordinal) {
return header + "\u0001" + ordinal;
}
// Rewrites the "===" sections of one language in the order given as ids. Each section runs from its
// heading to the next "===" (or shallower) heading and takes its "====" subsections with it; the blank
// lines between sections stay where they were, so only the sections themselves trade places.
function reorderH3Sections(pageContent, language, order) {
var range = langSectionRange(pageContent, language);
var lines = pageContent.slice(range.start, range.end).split("\n");
var starts = [];
var seen = {};
lines.forEach(function (line, i) {
var m = H3.exec(line);
if (m) {
seen[m[1]] = (seen[m[1]] || 0) + 1;
starts.push({ line: i, id: h3Id(m[1], seen[m[1]]) });
}
});
if (starts.length !== order.length || starts.some(function (st) {
return order.indexOf(st.id) === -1;
})) {
throw new SubmitAbort("Beşên vî zimanî ji dema ku rûpel vebû guherîne; rêza wan nehate guhertin.");
}
var tail = [];
var lastEnd = lines.length;
while (lastEnd > starts[starts.length - 1].line + 1 && isTailLine(lines[lastEnd - 1])) {
lastEnd--;
}
tail = lines.slice(lastEnd);
var bodies = {};
var gaps = [];
starts.forEach(function (st, k) {
var end = k + 1 < starts.length ? starts[k + 1].line : lastEnd;
var block = lines.slice(st.line, end);
var gap = [];
while (block.length > 1 && !block[block.length - 1].trim()) {
gap.unshift(block.pop());
}
bodies[st.id] = block;
gaps.push(gap);
});
var out = lines.slice(0, starts[0].line);
order.forEach(function (id, k) {
out = out.concat(bodies[id], gaps[k]);
});
out = out.concat(tail);
return pageContent.slice(0, range.start) + out.join("\n") + pageContent.slice(range.end);
}
// The same heading at another depth: "=== Çavkanî ===" -> "==== Çavkanî ====", spacing kept.
function setHeadingDepth(pageContent, language, header, ordinal, depth) {
var loc = wt.locateHeadingByText(pageContent, language, header, ordinal);
if (!loc) {
throw new SubmitAbort("Sernavê «" + header + "» nehate dîtin.");
}
var m = /^(={3,6})([ ]*)(.+?)([ ]*)\1\s*$/.exec(loc.lines[loc.headingLineIndex]);
var marks = wt.headingMarks(depth);
loc.lines[loc.headingLineIndex] = marks + m[2] + m[3] + m[4] + marks;
return pageContent.slice(0, loc.langSectionIndex) + loc.lines.join("\n");
}
// The "===" headings of one language on the page, in page order, as ids.
function domH3Order(language) {
var out = [];
var seen = {};
var inside = false;
$(".besazimani[id]").add($("h3")).each(function () {
var $el = $(this);
if ($el.is(".besazimani[id]")) {
inside = this.id === language;
return;
}
if (inside) {
var header = sections.headingText($el);
seen[header] = (seen[header] || 0) + 1;
out.push({ id: h3Id(header, seen[header]), header: header, el: this });
}
});
return out;
}
function dropQueuedKey(key) {
dsbQueue.entries.filter(function (entry) {
return entry.key === key;
}).forEach(dropQueueEntry);
}
// Per language: the "===" sections as the page has them, and the order the queue will give them.
var h3Orders = {};
// Moves an "===" section by one place (by is -1 or 1) in its language's queued order. Returns its new
// place and the number of sections, or null when it is already at that end.
function moveH3(language, $heading, by) {
var state = h3Orders[language];
if (!state) {
var dom = domH3Order(language);
state = h3Orders[language] = {
original: dom.map(function (d) {
return d.id;
}),
headers: {},
current: null
};
dom.forEach(function (d) {
state.headers[d.id] = d.header;
});
state.current = state.original.slice();
}
var mine = domH3Order(language).filter(function (d) {
return d.el === $heading[0];
})[0];
var at = mine ? state.current.indexOf(mine.id) : -1;
var to = at + by;
if (at === -1 || to < 0 || to >= state.current.length) {
return null;
}
state.current.splice(at, 1);
state.current.splice(to, 0, mine.id);
var key = "order:" + language;
if (state.current.join("\u0002") === state.original.join("\u0002")) {
dropQueuedKey(key);
return { index: to, count: state.current.length };
}
var order = state.current.slice();
var names = order.map(function (id) {
return state.headers[id];
}).join(" · ");
queueChange(langLabel(language), "rêza beşan bû: " + names,
function (pageContent) {
return { content: reorderH3Sections(pageContent, language, order), warnings: [] };
},
$(), $(),
function () {
var $mark = $(".besazimani[id]").filter(function () {
return this.id === language;
}).first();
var $h2 = headingOuter($mark.closest("h2").length ? $mark.closest("h2") : $mark);
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("Rêza nû ya beşan: " + names + " — nû, hîn nehatiye qeydkirin");
$h2.after($note);
return function () {
$note.remove();
};
},
{ key: key, runFinal: 1 });
return { index: to, count: state.current.length };
}
// Queues a heading's new depth (3 to 5); its own depth again takes the queued change back.
function setDepth(language, $heading, depth) {
var header = sections.headingText($heading);
var ordinal = sections.headingTextOrdinalWithinLanguage($heading);
var from = sections.headingDepth($heading);
var key = "depth:" + language + "|" + header + "|" + ordinal;
if (depth === from) {
dropQueuedKey(key);
return;
}
queueChange(langLabel(language) + "/" + header, "sernav ji h" + from + " bû h" + depth,
function (pageContent) {
return { content: setHeadingDepth(pageContent, language, header, ordinal, depth), warnings: [] };
},
$(), $(),
function () {
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ h" + depth + " — nû, hîn nehatiye qeydkirin");
headingOuter($heading).after($note);
return function () {
$note.remove();
};
},
{ key: key, runFinal: 2 });
}
// Runs text through the ext.gadget.linkify gadget ($.kuLinkify) and calls back with the linkified result.
// Passes the text through unchanged if the gadget can't be loaded, so it never blocks a save.
var linkifyText = util.linkifyText;
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// The word-list call a "#:" line under a sense is made of, as {name, words}: name as the page spells it,
// words its parameters after the language, each whole even when it holds a template or a piped link.
// Null when the line is not one call of keys (canonical names), or carries anything beside the call.
function senseGroupCall(line, marker, keys) {
var lead = new RegExp("^" + marker + ":\\s*").exec(line);
if (!lead) {
return null;
}
var parsed = parseTemplateCalls(line.slice(lead[0].length));
if (parsed.otherContent || parsed.calls.length !== 1) {
return null;
}
var call = parsed.calls[0];
if (keys.indexOf(canonicalTemplateKey(call.name)) === -1) {
return null;
}
return { name: call.name, words: call.params.slice(1).map(function (p) { return $.trim(p); }) };
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// An existing {{hevwate}} stays {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineStart = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var found = senseGroupCall(lines[i], marker, [tplKey]);
// A line of this list that is more than its one call cannot be rewritten without losing what
// else it holds, nor left beside a second one.
if (!found && tplLineStart.test(lines[i])) {
throw new SubmitAbort("Rêza «" + tplKey + "» ya vê maneyê nayê xwendin; wê bi destan biguhêre.");
}
if (found) {
var foundKey = found.name;
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var existingWords = found.words;
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
// Nothing to write and no line to edit: a call with no words is a module error ("Parametreya 2
// lazim e"), so an empty list leaves the page alone rather than writing one.
if (!words.length) {
return pageContent;
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeys = SENSE_GROUPS.map(function (g) { return g.key; });
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var call = senseGroupCall(lines[i], marker, groupKeys);
if (call) {
words[canonicalTemplateKey(call.name)] = call.words;
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
// Inserting a fragment moves its children into the surface and leaves the fragment itself empty
// and parentless, so the caret follows that last child; setStartAfter on the fragment throws.
var last = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? node.lastChild : node;
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
if (last) {
range.setStartAfter(last);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
} else {
$surface.append(node);
}
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
// pending, when given, means this panel composes a sense that is not on the page yet: it edits no
// line, and its save queues one insertion carrying the definition, its word lists and its
// quotations. Everything the user sees is the panel's own, so a new sense is edited exactly like a
// saved one. See openNewSenseRow().
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense, pending) {
// "#:" lines for a pending sense's quotations, in the order add-examples handed them over.
var pendingQuotes = pending ? pending.state.quotes.slice() : [];
var $pendingQuotes = $("<div class='dsb-new-sense-quotes'></div>");
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
if (pending) {
collectQuotation();
$moreMenu.slideUp();
return;
}
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// The form has to be opened on a sense that exists, so it opens on the one this row hangs off;
// only the line it builds is kept, and the form itself is moved under the row, title and all.
function collectQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
pendingQuotes.push(job.line.replace(/^#+/, ""));
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
});
if (!(wikt.addExamples.openFor && wikt.addExamples.openFor($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// Its title is a list item in the sense's own example list; taken out of that list so it
// does not read as a numbered sense of its own.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$pendingQuotes.after($("<div class='dsb-new-sense-form-title'></div>")
.append($title.contents()));
$title.remove();
}
if ($form.length) {
$quickRow.find(".dsb-new-sense-form-title").first().after($form);
}
}
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
if (pending) {
original = { gloss: pending.state.text || "", maneid: pending.state.maneid || "",
etiket: pending.state.etiket || [], kategori: pending.state.kategori || [],
words: pending.state.lists || {} };
sensePromise = $.Deferred().resolve(original).promise();
} else {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
}
return sensePromise.then(withQueued);
}
// The change already queued for this sense, as the fields it writes. Read back from the queue
// rather than kept in a variable here, so dropping the entry from the queue box also drops this.
function queuedSenseChanges() {
if (pending) {
return null;
}
var key = senseQueueKey();
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === key && entry.senseChanges) {
found = entry.senseChanges;
}
});
return found;
}
// The sense with whatever is queued for it written over the top (nothing queued: as the page has it).
function withQueued(fields) {
return overlaySenseFields(fields, queuedSenseChanges());
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
$.when(fetchTemplateData(target.name), cite.templateAliases(target.name)).then(function (td, aliases) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td, aliases);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
// A pending sense has no line on the page to differ from: whatever is in the box is the sense.
return (pending || (v && v !== originalGloss)) ? (v || null) : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// A sense that is not on the page yet is edited with this same panel, in a row of its own: same
// picker, same dropdown, same buttons. Only the save differs -- it writes an insertion rather
// than editing a line -- and the row renders what the insertion will say.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry: hide it while the box is open, and leave
// the entry alone so cancelling can put it back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $panel = $("<div class='dsb-quick-row dsb-new-sense'></div>");
$row.append($panel);
$li.after($row);
$quickRow.data("dsbNewSenseRow", $row);
buildAdvancedPanel($panel, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel,
isLastSense, {
state: state || { text: "", lists: {}, quotes: [], maneid: "", etiket: [], kategori: [] },
key: newSenseKey,
$row: $row,
close: function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
},
reopen: openNewSenseRow,
queued: queuedNewSense,
drop: dropQueuedNewSense
});
$panel.show();
$panel.find("textarea").first().trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Binebeşekê lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
if (pending) {
// Nothing to remove from the page: the sense only exists in the queue.
pending.drop();
pending.close();
return;
}
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary || (pending && def.key === "newSense")) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !(pending && def.key === "newSense")) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key] && !(pending && def.key === "newSense")) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
// Against what the boxes were filled from, queued change and all -- a box reopened and left
// alone writes nothing, and one the user has just emptied counts as emptied.
var base = withQueued(original || { maneid: "", etiket: [], kategori: [], words: {} });
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// One insertion carrying the whole sense: its leading calls and definition on the "#" line, then
// a "#:" line per word list and per quotation, in the order the wizard writes them.
function newSenseWikitext(changes) {
var leading = LEADING_TPL_ORDER.map(function (key) {
var values = key === "maneid" ? (changes.maneid ? [changes.maneid] : [])
: key === "e" ? changes.etiket
: key === "K" ? changes.kategori : [];
return values && values.length
? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
var lines = [];
SENSE_GROUPS.forEach(function (group) {
var words = changes.words[group.key] || [];
if (words.length) {
lines.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
lines = lines.concat(pendingQuotes);
return { text: (leading ? leading + " " : "") + $.trim(changes.gloss || ""), extras: lines };
}
function pendingState(changes) {
return { text: $.trim(changes.gloss || ""), lists: changes.words, quotes: pendingQuotes.slice(),
maneid: changes.maneid, etiket: changes.etiket, kategori: changes.kategori };
}
function queueNewSense(changes) {
if (!$.trim(changes.gloss || "")) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var built = newSenseWikitext(changes);
var held = pendingState(changes);
var block = ["# " + built.text].concat(built.extras.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
pending.close();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType, sectionOrdinal,
definitionLevel, built.text, built.extras), warnings: [] };
},
$save,
$status,
// Drawn fresh on every call: redrawPreviews() runs this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>").attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
pending.reopen(held);
}));
renderWikitext(block).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $content = $("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html);
var $block = $content.children("dl, ul, ol").first();
if ($block.length) {
$block.before($edit);
} else {
$content.append($edit);
}
$pending.empty().append($content);
announceContent($pending);
}, function () {
$pending.empty().append($("<span></span>").text(built.text), $edit);
});
return function () {
$pending.remove();
};
},
{ key: pending.key, sensePath: definitionLevel, senseLanguage: language }
);
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
if (pending) {
pending.close();
if (pending.queued()) {
redrawPreviews();
}
return;
}
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
if (pending) {
queueNewSense(changes);
return;
}
// This save supersedes any change still queued for the same sense, so it has to write that
// one's fields too: the boxes it was made from are closed by now and collectChanges() cannot
// see them.
var merged = mergeSenseChanges(queuedSenseChanges(), changes);
var parts = senseChangeLabels(merged);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, merged);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel, senseChanges: merged }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
if (pending) {
// The gloss box is what a new sense is: it opens with the row, and the quotations the row
// collects sit above the actions. Done here, once $actions exists.
$quickRow.append($pendingQuotes);
pending.state.quotes.forEach(function (extra) {
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text("#" + extra));
});
$picker.children(".dsb-field-pick").filter(function () {
return $(this).text() === GLOSS_LABEL;
}).trigger("click");
}
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Binebeşekê lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again; onRendered($body) runs once the body is drawn.
function buildSubsectionPreviewDom($h3, s, content, language, onRendered) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
}).then(function () {
if (onRendered && $body.closest("body").length) {
onRendered($body);
}
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded. onRendered($slot) runs once the new content is drawn.
*/
function replaceSubsectionInPlace($h3, header, wikitext, onRendered) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
}).then(function () {
if (onRendered && $slot) {
onRendered($slot);
}
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
// The last node of a subsection under a part of speech (its heading when it is empty), or null.
function subsectionLastNode($h3, header) {
var depth = subsectionDepthFor($h3);
var $last = null;
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break;
}
if (isSubsectionHeading($cursor, depth)) {
if ($last) {
break;
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$last = $cursor;
}
continue;
}
if ($last) {
$last = $cursor;
}
}
return $last;
}
// Queued translation boxes, drawn the way the save leaves the page: a new Werger section with them, or
// the section already there redrawn from the queued text. The translation adder is attached to what
// is drawn, so translations can be added before anything is saved. Returns a function that puts the
// page back.
function drawWergerBoxes($h3, s, newBoxes, language, sectionType, sectionOrdinal) {
function withAdder($body) {
mw.loader.using("ext.gadget.wikt.translation-adder").then(function (require) {
return require("ext.gadget.wikt.translation-adder").attach($body,
translationEditor($h3, language, sectionType));
}).catch(function () { /* without the adder the boxes are still shown */ });
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, newBoxes, language, withAdder);
return function () {
$preview.remove();
};
}
var restore = null;
var dropped = false;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var current = readPageSubsection(queuedContent(pageContent), language, sectionType, sectionOrdinal,
$.extend({}, s, { kind: "text" }));
if (current) {
restore = replaceSubsectionInPlace($h3, s.header, current.text || "", withAdder);
}
});
return function () {
dropped = true;
if (restore) {
restore();
restore = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline, .dsb-werger-add");
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: a section the picker does not offer still opens, as the wikitext
// it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it. Werger carries lang: "ku" --
// it is a Kurdish-entry section, so it is not offered elsewhere.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil that opened the panel, so it is told the panel is closed.
$cancel.on("click", function () {
resetForm();
$panel.hide().trigger("dsbPanelClosed");
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
// Werger holds translation boxes, which no other section can: it can be removed, not renamed.
var renamable = headingEditable && available[0].kind !== "werger";
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (renamable && subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else if (renamable) {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out, and Werger, which holds boxes no other section has.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && s.kind !== "werger" && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return JSON.stringify(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(value ? JSON.parse(value) : []);
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// The translation boxes of a Werger section, one numbered row each like the images' list: a box
// already on the page (box = its place in the section) or a new one (box = null), each with the
// sense it covers. The lines inside are left to the translation adder.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var rows = [];
var $list = $("<ol class='dsb-resim-list'></ol>");
function renumber() {
$list.children().each(function (n) {
$(this).children(".dsb-resim-index").text("Qutî " + (n + 1));
});
}
// {{werger-ser}} also takes stûn and column-width, but they only restyle the box and its defaults
// are what these pages use, so the form does not offer them. A box that sets one keeps it, since
// a row starts from the box's own parameters.
function addRow(params, box) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded.
function field(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "")
.val(params[key] || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
$("<li class='dsb-resim-item'></li>").append(
$("<div class='dsb-resim-index'></div>"),
field("1", "Mane", "ava vexwarinê"),
field("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
field("id", "id", "ne mecbûrî — IDya tercimeyê")
).appendTo($list);
rows.push({ inputs: inputs, params: params, box: box });
renumber();
return inputs["1"];
}
function addNewRow() {
// Empty: the user writes the gloss for the box they are adding.
return addRow({}, null);
}
var $add = $("<a href='#' class='dsb-quick-link'>+ Qutiyeke din zêde bike</a>").on("click", function (e) {
e.preventDefault();
addNewRow().trigger("focus");
});
var $note = $("<p class='dsb-werger-note'></p>").text(
"Tercimeyan piştî lêzêdekirinê di bin qutiyê de binivîse."
);
wergerForm = {
// Each row as {box, params, loaded}: what it holds now and what it was opened with.
read: function () {
return rows.map(function (r) {
var params = $.extend({}, r.params);
$.each(r.inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
params[key] = value;
} else {
delete params[key];
}
});
return { box: r.box, params: params, loaded: r.params };
});
},
// Rows from read() or from the page; an empty list still offers one new box.
set: function (list) {
rows = [];
$list.empty();
(list || []).forEach(function (r) {
addRow(r.loaded || r.params, r.box);
if (r.loaded) {
$.each(rows[rows.length - 1].inputs, function (key, $input) {
$input.val(r.params[key] || "");
});
}
});
if (!rows.length) {
addNewRow();
}
},
addRow: addNewRow
};
// The "+ Qutiyeke din zêde bike" under the boxes on the page adds a row through this.
$panel.data("dsbAddBox", function () {
addNewRow().trigger("focus");
});
$fieldWrap.empty().append($note, $list, $add);
if (!s.existing) {
addNewRow();
}
}
// The boxes already in the section, as rows, with the changes still in the queue (a new gloss from
// the box's ±, say) but not this form's own. A pending "+ Qutiyeke din zêde bike" from the page adds
// a new one after them.
function prefillWerger(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
// Unread, the boxes on the page are unknown, so nothing is offered that a save could get wrong.
fetchPageContent().then(function (pageContent) {
return readPageSubsection(queuedContent(pageContent, wergerKey(s)), language, sectionType,
sectionOrdinal, $.extend({}, s, { kind: "text" }));
}).then(function (current) {
$save.prop("disabled", false);
$status.text("");
var rows = wergerBoxes((current && current.text) || "").map(function (b, n) {
return { box: n, params: b.params };
});
// This form's own queued change, if any, is what it was last saved with: its rows go back in
// over the boxes they edit, and its new boxes after them.
var own = queuedFormState(wergerKey(s));
if (own) {
rows = rows.map(function (row) {
var mine = own.filter(function (r) {
return r.box === row.box;
})[0];
return mine ? { box: row.box, params: mine.params, loaded: row.params } : row;
}).concat(own.filter(function (r) {
return r.box === null;
}).map(function (r) {
return { box: null, params: r.params };
}));
}
wergerForm.set(rows);
if ($panel.data("dsbAddBoxPending")) {
$panel.removeData("dsbAddBoxPending");
wergerForm.addRow().trigger("focus");
}
loadedSnapshot = contentSnapshot();
}, function () {
$panel.removeData("dsbAddBoxPending");
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
});
}
// Queues the new boxes and the changed ones as one change: a new Werger section, or the boxes
// rewritten and appended in the section already there.
function saveWerger(s) {
var rows = wergerForm.read();
var added = rows.filter(function (r) {
return r.box === null && (r.params["1"] || r.params["2"]);
});
var changed = rows.filter(function (r) {
return r.box !== null && JSON.stringify(r.params) !== JSON.stringify(r.loaded);
});
if (!added.length && !changed.length) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
return;
}
var actions = [];
if (added.length) {
actions.push(added.length === 1 ? "qutiyeke tercimeyê hate zêdekirin"
: added.length + " qutiyên tercimeyê hatin zêdekirin");
}
if (changed.length) {
actions.push(changed.length === 1 ? "qutiyeke tercimeyê hate nûkirin"
: changed.length + " qutiyên tercimeyê hatin nûkirin");
}
var asText = $.extend({}, s, { kind: "text" });
var newBoxes = added.map(function (r) {
return buildWergerBlock(r.params);
}).join("\n\n");
$save.prop("disabled", true);
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
actions.join(" û "),
function (pageContent) {
var current = s.existing &&
readPageSubsection(pageContent, language, sectionType, sectionOrdinal, asText);
if (!current) {
return {
content: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, asText, newBoxes),
warnings: []
};
}
var body = rewriteWergerTops($.trim(current.text || ""), changed);
if (newBoxes) {
body = body ? body + "\n\n" + newBoxes : newBoxes;
}
return {
content: updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, asText, body),
warnings: []
};
},
$save,
$status,
function () {
return drawWergerBoxes($h3, s, newBoxes, language, sectionType, sectionOrdinal);
},
{ key: wergerKey(s), formState: rows }
);
}
function wergerKey(s) {
return "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header;
}
function queuedFormState(key) {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === key && entry.formState) {
found = entry.formState;
}
});
return found;
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// What a save writes: the same groups with each word kept once, in the order it first appears. A
// list written made bi made often repeats itself, and {{kol3}} only hides that at render time. The
// same word under two different headings is two different entries, so groups are deduped apart.
function readGroupsForSave() {
return readGroups().map(function (group) {
var seen = {};
return $.extend({}, group, {
// The whole row is the key, modifiers and all, so "dan<t:x>" is not the same word as
// "dan".
words: (group.words || []).filter(function (word) {
var key = $.trim(word);
if (seen[key]) {
return false;
}
seen[key] = true;
return true;
})
});
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
announceBullets(current);
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
announceBullets(current);
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// A list the page writes made bi made is about to become {{kol3}}, which is a change to the
// section even when not one word of it is touched -- so the save is not held back as a no-op.
var loadedFromBullets = false;
function announceBullets(current) {
if (!current.fromBullets) {
return;
}
loadedFromBullets = true;
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroupsForSave()))
: subsectionBodyWikitext(language, s, readGroupsForSave());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
loadedFromBullets = false;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
$save.text(s.existing ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if (s.kind === "werger" && s.existing) {
prefillWerger(s);
} else if (s.existing || s.place) {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
if (s.kind === "werger") {
saveWerger(s);
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroupsForSave();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot
&& !loadedFromBullets;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
"beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// A new subsection gets a block of its own at the position it will land in; one already on the
// page is redrawn where it stands.
function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
// Attaches the "+ Binebeşekê lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
// An editor for [[MediaWiki:Gadget-wikt.translation-adder.js]] whose changes go into this queue, under
// "Kurmancî/Navdêr" like every other change to that section. Each change is tried on the page as the
// queue would save it before it is queued, as the adder's own editor does.
function translationEditor($h3, language, sectionType) {
var place = placeLabel(language, sectionType, $h3);
var $status = $("<span></span>");
var $save = $("<span></span>");
// The entries this editor queued, so "Paşve" takes back the adder's own last change and not one
// made elsewhere after it.
var mine = [];
var adapter = {
withCurrentText: function (callback) {
fetchPageContent().then(function (content) {
callback(queuedContent(content));
}, function () {
adapter.error("Nikare muhtewaya rûpelê bixwîne.");
});
},
// node is the line the change draws; it is highlighted while the change waits in the queue.
addEdit: function (edit, node) {
adapter.withCurrentText(function (text) {
var next;
try {
next = edit.edit(text);
} catch (e) {
adapter.error(String(e));
return;
}
if (typeof next !== "string" || next === text) {
return;
}
queueChange(place, edit.summary, edit.edit, $save, $status, function () {
edit.redo();
if (node) {
node.classList.add("translation-adder-new");
}
return function () {
if (node) {
node.classList.remove("translation-adder-new");
}
edit.undo();
};
});
mine.push(dsbQueue.entries[dsbQueue.entries.length - 1]);
});
},
error: function (message) {
mw.notify(message, { type: "error" });
},
// The adder's "Paşve" takes back its last change still in the queue.
undo: function () {
while (mine.length) {
var last = mine.pop();
if (dsbQueue.entries.indexOf(last) !== -1) {
dropQueueEntry(last);
return;
}
}
}
};
return adapter;
}
module.exports = {
translationEditor: translationEditor,
buildAdvancedPanel: buildAdvancedPanel,
buildSubsectionPanel: buildSubsectionPanel,
buildConvertPosPanel: buildConvertPosPanel,
editExample: editExample,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox,
moveH3: moveH3,
setDepth: setDepth,
reorderH3Sections: reorderH3Sections,
setHeadingDepth: setHeadingDepth
};
}());
// </nowiki>
gkoz14io5r156q9t5dnvq8bh8jwj11c
6865616
6865604
2026-10-23T22:09:21Z
Wikihez
7344
DefSideBoxes: the queue and what the panels share; the panels moved to wikt.dsb-sense, -subsection and -pos
6865616
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-panels.js]]
*
* What every DefSideBoxes panel shares: the save queue and its box, the previews, the wikitext helpers the
* panels have in common, the Werger box editor and the heading tools. Each kind of panel is a module of its
* own, loaded by wikt.dsb only when its kind of pencil is first clicked:
* wikt.dsb-sense the per-sense panel and the example editor
* wikt.dsb-subsection the "====" subsection editors
* wikt.dsb-pos the part-of-speech picker
* They read what they share from module.exports.shared. Each module stays under the 100 KB ResourceLoader
* keeps in the browser's module store, so after the first use a pencil opens without a download.
*
* Loaded on demand by wikt.dsb, never on a plain page view.
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var wt = edit.wt;
var langs = edit.langs;
var headingOuter = sections.headingOuter;
var api = new mw.Api();
// ---- searchable <select> ----------------------------------------------------------------------------
// Same widget start.js gets from its util.js wrapNativeSelect(): the native <select> stays as the value
// holder (so .val() and "change" handlers keep working) but is hidden behind a text input that filters an
// overlay list. Ported rather than required() because util.js is a file inside start.js's package module,
// which another gadget cannot reach; the template-row decoration start.js uses is left out.
// util.js's wrapNativeSelect(): the native <select> stays the value holder behind a filtering text input.
// It reads its own placeholder off data-placeholder, and its wiktku-* styles ship in start.css, so both
// gadgets get the identical widget instead of two lookalikes.
function makeSearchable($select, placeholder) {
$select.attr("data-placeholder", placeholder || "Lê bigere…");
return util.wrapNativeSelect($select);
}
// ---- small shared utilities ----
// The readable half of an mw.Api rejection.
var apiErrorMessage = util.apiErrorMessage;
// Thrown from inside an edit() transform to bail out with a specific message.
var SubmitAbort = util.SubmitAbort;
// Runs transformFn(content) inside mw.Api().edit()'s own transform callback so one fetch covers both the
// edit-conflict check and the content, preventing a stale overwrite of someone else's edit.
//
// No badtoken retry here on purpose: edit() posts through postWithEditToken -> postWithToken, which
// already throws the cached token away and repeats the request once when the wiki says "badtoken".
// A second retry of our own would only double it.
function editPage(summary, transformFn, onSuccess, onError) {
api.edit(mw.config.get("wgPageName"), function (revision) {
return { text: transformFn(revision.content), summary: summary };
}).then(onSuccess, function (codeOrAbort, result) {
if (codeOrAbort instanceof SubmitAbort) {
onError(codeOrAbort.message);
} else {
onError(util.msg.saveFailed + apiErrorMessage(codeOrAbort, result));
}
});
}
// ---- rendered preview: wikitext through the parse API, so a preview shows what the templates
// actually produce rather than a plain list standing in for them ----
// Same text is usually asked for twice (open the preview, close it, open it again), so the promise is
// kept rather than the call repeated.
var renderCache = {};
/**
* Renders wikitext as this page would render it. Resolves with an HTML string.
*
* pst: 1 applies pre-save transform, so ~~~~ and subst: behave as they will once saved. The
* modulestyles/modules the parse reports are loaded before resolving: without them TemplateStyles is
* missing and the preview would show the right markup in the wrong clothes.
*/
function renderWikitext(wikitext) {
if (renderCache[wikitext]) {
return renderCache[wikitext];
}
renderCache[wikitext] = api.post({
action: "parse",
format: "json",
formatversion: 2,
title: mw.config.get("wgPageName"),
text: wikitext,
contentmodel: "wikitext",
prop: "text|modules|jsconfigvars",
pst: 1,
disablelimitreport: 1,
disableeditsection: 1,
disabletoc: 1
}).then(function (data) {
var parsed = (data && data.parse) || {};
if (parsed.modulestyles && parsed.modulestyles.length) {
mw.loader.load(parsed.modulestyles);
}
if (parsed.modules && parsed.modules.length) {
mw.loader.load(parsed.modules);
}
return parsed.text || "";
}, function () {
// A failed render must not be remembered, or the next attempt gets the rejection back.
delete renderCache[wikitext];
return $.Deferred().reject().promise();
});
return renderCache[wikitext];
}
/**
* Puts rendered wikitext into $target, replacing whatever placeholder is there.
*
* The site's own gadgets are told about it through the "wikipage.content" hook, the same way
* MediaWiki announces real content, so collapsible lists and the like behave in the preview too.
*/
// MediaWiki warns when the hook is fired on content that is not in the document, and a gadget that
// listens to it can do nothing useful with nodes nobody can see.
function announceContent($el) {
if ($el.closest("html").length) {
mw.hook("wikipage.content").fire($el);
}
}
function fillWithRendered($target, wikitext, onFail) {
return renderWikitext(wikitext).then(function (html) {
if (!$target.closest("body").length) {
return; // the preview was dropped while the parse was in flight
}
$target.empty().append($("<div class='mw-parser-output'></div>").html(html));
announceContent($target);
}, function () {
if (onFail) onFail();
});
}
// This gadget's own save queue + floating widget, separate from the shared Gadget-Editor queue.
// Each entry keeps its own transformFn so saveQueueNow() can re-run all of them against one fresh fetch.
var dsbQueue = {
// [{key, summary, transformFn, runLast, removePreview}]; transformFn: baseContent -> {content, warnings}.
// removePreview lives on the entry so replacing one entry takes its own live DOM preview with it.
entries: []
};
var $queueBox = null;
function dropPreview(entry) {
if (typeof entry.removePreview === "function") {
try {
entry.removePreview();
} catch (e) { /* a preview failing to clean up should never block the queue itself */ }
entry.removePreview = null;
}
}
function clearAllPreviews() {
dsbQueue.entries.forEach(dropPreview);
}
function clearQueue() {
clearAllPreviews();
dsbQueue.entries = [];
if ($queueBox) {
$queueBox.$list.empty();
$queueBox.$box.hide();
}
}
// Closes every editor UI on the page. Each open box's own "x" clears its getter, restores its picker
// link and updates the counter, so those are triggered rather than repeating that work here; hiding a
// row alone would leave its fields populated and the next pencil click would reopen them half-filled.
function closeAllPanels() {
$(".dsb-open-boxes .dsb-box-remove").trigger("click");
$(".dsb-quick-note").hide().text("");
$(".dsb-more-menu").hide();
$(".dsb-subsection-panel, .dsb-convert-pos-panel").hide();
// dsb.js owns each pencil's pinned state; tell it rather than hiding behind its back, or the next
// click on the pencil would only toggle "pinned" off and appear to do nothing.
$(".dsb-quick-row").trigger("dsbCancel");
}
function ensureQueueBox() {
if ($queueBox) {
return $queueBox;
}
var $box = $("<div class='dsb-queue-box'></div>").hide();
var $close = $("<a href='#' class='dsb-queue-close' title='Veşêre'>×</a>");
var $title = $("<div class='dsb-queue-title'></div>").text("Guhartinên li benda qeydkirinê");
var $list = $("<ul class='dsb-queue-list'></ul>");
var $empty = $("<div class='dsb-queue-empty'></div>").text(
"Guhartinên te dê li vir werin rêzkirin. Heta ku tu pêl «Guhartinan qeyd bike» nekî, "
+ "tiştek li ser rûpelê nayê weşandin."
);
var $status = $("<div class='dsb-queue-status'></div>");
var $save = $("<button type='button' class='dsb-queue-save'>Guhartinan qeyd bike</button>");
var $cancel = $("<button type='button' class='dsb-queue-cancel'></button>").text(util.msg.cancel);
var $actions = $("<div class='dsb-queue-actions'></div>").append($save, $cancel);
$close.on("click", function (e) {
e.preventDefault();
$box.hide();
});
$cancel.on("click", function () {
clearQueue();
closeAllPanels();
});
$save.on("click", function () {
saveQueueNow();
});
$box.append($close, $title, $empty, $list, $actions, $status);
$(document.body).append($box);
$queueBox = { $box: $box, $list: $list, $empty: $empty, $status: $status, $save: $save, $cancel: $cancel };
return $queueBox;
}
// Takes one change out of the queue and puts the page back as it was before it, its in-place preview
// included. The queue is the only record of what "Guhartinan qeyd bike" will write, so every line in it
// has to be removable on its own -- closing the panel that made a change does not unmake it.
// Each preview draws the page the whole queue would save, so one entry leaving changes what all the
// others should show. They are taken down and drawn again from what is left.
function redrawPreviews() {
dsbQueue.entries.forEach(function (entry) {
if (typeof entry.previewFn !== "function") {
return;
}
dropPreview(entry);
try {
var removePreview = entry.previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview never stops the queue */ }
});
}
function dropQueueEntry(entry) {
var at = dsbQueue.entries.indexOf(entry);
if (at === -1) {
return;
}
dropPreview(entry);
dsbQueue.entries.splice(at, 1);
redrawPreviews();
renderQueueBox();
hideQueueIfIdle();
}
function renderQueueBox() {
var box = ensureQueueBox();
box.$list.empty();
dsbQueue.entries.forEach(function (entry) {
var $drop = $("<button type='button' class='dsb-queue-drop'>×</button>")
.attr("title", "Vê guhartinê ji rêzê derxe");
$drop.on("click", function () {
dropQueueEntry(entry);
});
box.$list.append($("<li></li>").text(entry.summary).append($drop));
});
box.$empty.toggle(!dsbQueue.entries.length);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("");
box.$save.prop("disabled", !dsbQueue.entries.length);
box.$box.show();
}
// Shown the moment a panel opens, empty, so it is clear before anything is typed that a change waits
// for "Guhartinan qeyd bike" rather than publishing itself.
function openQueueBox() {
renderQueueBox();
}
// Nothing queued and no form left open means nothing to save, so the box goes away rather than
// lingering on an empty state.
function hideQueueIfIdle() {
if (dsbQueue.entries.length || !$queueBox) {
return;
}
if ($(".dsb-quick-row:visible, .dsb-subsection-panel:visible, .dsb-convert-pos-panel:visible").length) {
return;
}
$queueBox.$box.hide();
}
// Asks core's postEdit module to show its toast on the next load (dsb.js loads it there), then reloads.
// Loaded here rather than declared as a dependency, so a reload happens even if it fails.
function reloadWithPostEditNotice() {
mw.loader.using("mediawiki.action.view.postEdit").then(function (require) {
require("mediawiki.action.view.postEdit").fireHookOnPageReload("saved");
}).always(function () {
location.reload();
});
}
// A transform returns either the new page text or {content, warnings}; both shapes are accepted.
// One that returns neither stops the save, named, rather than writing a broken page.
function transformedContent(entry, pageContent) {
var result = entry.transformFn(pageContent);
var next = typeof result === "string" ? result : (result && result.content);
if (typeof next !== "string") {
throw new SubmitAbort("Guhartin nehate sepandin: " + entry.summary);
}
return next;
}
// Queue order, except that runLast entries move to the end: converting "=== Mane ===" into a real POS
// heading renames the very heading every other queued change locates itself by.
// Those run from the bottom of the page up: a rename changes the ordinals of the sections after it
// ("Mane 2" becomes "Mane 1"), never of the ones before.
// Two sense paths in page order; index 0 of a definitionLevel is unused.
function compareSensePath(a, b) {
var len = Math.max(a.length, b.length);
for (var i = 1; i < len; i++) {
var x = a[i] === undefined ? -1 : a[i];
var y = b[i] === undefined ? -1 : b[i];
if (x !== y) {
return x - y;
}
}
return 0;
}
function orderedEntries() {
var normal = [];
var last = [];
var final = [];
dsbQueue.entries.forEach(function (entry) {
(entry.runFinal ? final : entry.runLast ? last : normal).push(entry);
});
// Stable, so two entries of one kind keep the order they were queued in.
final = final.map(function (entry, i) {
return { entry: entry, i: i };
}).sort(function (a, b) {
return a.entry.runFinal - b.entry.runFinal || a.i - b.i;
}).map(function (x) {
return x.entry;
});
last.sort(function (a, b) {
return b.pagePosition - a.pagePosition;
});
// A sense is found by its number, so removing or adding one moves every sense below it. The sense
// changes therefore run bottom-up, each in the slot it was queued in, and every one of them still
// finds the line it was queued for.
var slots = [];
normal.forEach(function (entry, i) {
if (entry.sensePath) {
slots.push(i);
}
});
var senses = slots.map(function (i) {
return normal[i];
});
senses.sort(function (a, b) {
return compareSensePath(b.sensePath, a.sensePath);
});
slots.forEach(function (slot, k) {
normal[slot] = senses[k];
});
return normal.concat(last, final);
}
/**
* The pending page as previews and example lookups need it: every queued transform, in save order,
* except the runLast section renames. Those callers find their section under the heading it has on
* the page now, which a queued "Mane" -> "Navdêr" would take away, and a rename leaves the senses as
* they are. An entry that cannot apply is skipped -- the save reports that properly, and a preview
* must not shout.
*/
// skipKey leaves out the entry queued under that key, for a form reopened on its own queued change.
function queuedContent(pageContent, skipKey) {
var content = pageContent;
orderedEntries().filter(function (entry) {
return !entry.runLast && !entry.runFinal && !(skipKey && entry.key === skipKey);
}).forEach(function (entry) {
try {
content = transformedContent(entry, content);
} catch (e) {
// Left out of the preview; the save reports it properly. Named, so a change that fails to
// draw is distinguishable from one with nothing to draw.
mw.log.warn("wikt.dsb: pêşdîtin bêyî vê guhartinê hate xêzkirin — " + entry.summary, e);
}
});
return content;
}
// The real save: re-runs every queued transformFn, in order, against one freshly-fetched revision and
// posts the result as a single edit with a combined summary.
function saveQueueNow() {
if (!dsbQueue.entries.length) {
return;
}
var box = ensureQueueBox();
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", true);
box.$status.removeClass("dsb-status-ok dsb-status-error").text("Tê qeydkirin…");
var entries = orderedEntries();
// Grouped by place, in the order the places were first queued: several changes to one section read
// as "Kurmancî/Navdêr 2: a, b, c" rather than repeating the section name in front of each of them.
var order = [];
var byPlace = {};
entries.forEach(function (entry) {
if (!Object.prototype.hasOwnProperty.call(byPlace, entry.place)) {
byPlace[entry.place] = [];
order.push(entry.place);
}
byPlace[entry.place].push(entry.action);
});
var combinedSummary = order.map(function (place) {
var actions = byPlace[place].join(", ");
return place ? place + ": " + actions : actions;
}).join("; ") + " (DefSideBoxes)";
editPage(
combinedSummary,
function (pageContent) {
var content = pageContent;
entries.forEach(function (entry) {
content = transformedContent(entry, content);
});
return content;
},
function (data) {
forgetPageContent();
clearAllPreviews();
dsbQueue.entries = [];
box.$list.empty();
box.$status
.removeClass("dsb-status-error")
.addClass("dsb-status-ok")
.text("✓ Hemû guhartin hatin qeydkirin.");
box.$save.prop("disabled", true);
box.$cancel.prop("disabled", false);
// A real edit reloads the page and gets MediaWiki's own "Guhartina te hat weşandin." toast, the
// one a normal save shows. A null edit changed nothing, so the page stays as it is.
if (data && data.newrevid) {
reloadWithPostEditNotice();
}
},
function (message) {
box.$save.prop("disabled", false);
box.$cancel.prop("disabled", false);
box.$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
}
// "Kurmancî/Navdêr" -- where a queued change lands, for its summary. The language name comes from the
// shipped list; an unknown code is left as the code itself.
// "Kurmancî/Navdêr", or "Kurmancî/Navdêr 2" where the language has more than one section of that
// type. Without the number a summary on a page with two Navdêr sections named neither of them.
// Numbered only when it disambiguates: on the usual page "Navdêr 1" would just be noise.
function placeLabel(language, sectionType, $h3) {
return langLabel(language) + "/" + sectionLabel(sectionType, $h3);
}
function sectionLabel(sectionType, $h3) {
if ($h3 && $h3.length && sections.sectionTypeCountWithinLanguage($h3) > 1) {
return sectionType + " " + sections.sectionOrdinalWithinLanguage($h3);
}
return sectionType;
}
// Capitalised because it opens the summary; Kurdish otherwise writes language names lower-case.
function langLabel(language) {
var names = langs.names();
return util.capitalize(names[language] || language);
}
// Queues transformFn(baseWikitext) -> {content, warnings} for the real save to run later against fresh
// content (see saveQueueNow()); nothing here touches the page yet. Also runs previewFn(), which drops a
// small "not saved yet" preview into the live DOM at the change's own spot on the page. Shared by every
// panel that queues a wikitext change: buildAdvancedPanel, buildSubsectionPanel, buildConvertPosPanel.
// place is what the change is being made to ("Kurmancî/Navdêr 2"), action is what is being done to it.
// Kept apart rather than pre-joined so saveQueueNow() can name a section once and list everything
// queued against it after it, instead of repeating the place before every item.
function queueChange(place, action, transformFn, $save, $status, previewFn, opts) {
opts = opts || {};
var entry = {
key: opts.key || null,
place: place || "",
action: action,
summary: place ? place + ": " + action : action,
transformFn: transformFn,
runLast: !!opts.runLast,
// After even the runLast ones, in this order: 1 rewrites the order of a language's "===" sections,
// 2 the depth of one heading. Both find their headings as the other changes leave them.
runFinal: opts.runFinal || 0,
pagePosition: opts.pagePosition || 0,
// Which sense the change belongs to, so orderedEntries() can apply the senses bottom-up.
sensePath: opts.sensePath || null,
// The language section that path is counted in, and whether this change deletes the sense.
senseLanguage: opts.senseLanguage || null,
removesSense: !!opts.removesSense,
// The sense fields this entry writes, merged with whatever an earlier entry under the same
// key wrote, so superseding it does not drop what it had already collected.
senseChanges: opts.senseChanges || null,
// What the form that queued it held, so the form can be reopened on it.
formState: opts.formState || null,
// Kept, not just run: redrawPreviews() calls it again when another entry leaves the queue.
previewFn: typeof previewFn === "function" ? previewFn : null,
removePreview: null
};
// Queuing the same target twice replaces the pending change rather than stacking a second one --
// two "+Têkildar" entries would both run, and the second would abort on "Ev beş jixwe heye".
var superseded = false;
if (entry.key) {
for (var i = dsbQueue.entries.length - 1; i >= 0; i--) {
if (dsbQueue.entries[i].key === entry.key) {
dropPreview(dsbQueue.entries[i]);
dsbQueue.entries.splice(i, 1);
superseded = true;
}
}
}
dsbQueue.entries.push(entry);
$status.removeClass("dsb-status-error").addClass("dsb-status-ok").text(
"✓ li rêza guhartinan hate zêdekirin — pêl «Guhartinan qeyd bike» bike da ku bi cih bibe."
);
$save.prop("disabled", false);
if (previewFn) {
try {
var removePreview = previewFn();
if (typeof removePreview === "function") {
entry.removePreview = removePreview;
}
} catch (e) { /* a broken preview should never stop the change from being queued */ }
}
// What the superseded entry drew is gone from the queue, so whatever else is drawn from it is stale.
// A queued removal renumbers the senses under it, so every other preview is stale the same way.
if (superseded || entry.removesSense) {
dsbQueue.entries.forEach(function (other) {
if (other !== entry) {
dropPreview(other);
}
});
redrawPreviews();
}
renderQueueBox();
}
// ---- heading order and depth ----
var H3 = /^===(?!=)[ ]*(.+?)[ ]*===\s*$/;
// The end of a language: blank lines, "----", categories and magic words stay at the end whatever
// section comes last.
function isTailLine(line) {
return !line.trim() || /^-{4,}\s*$/.test(line) || /^\s*\[\[\s*(Kategorî|Category)\s*:/i.test(line) ||
/^__[A-ZÊÎÛÇŞ]+__\s*$/.test(line);
}
// One "===" section's id within its language: its text, and which of the "===" headings with that text
// it is.
function h3Id(header, ordinal) {
return header + "\u0001" + ordinal;
}
// Rewrites the "===" sections of one language in the order given as ids. Each section runs from its
// heading to the next "===" (or shallower) heading and takes its "====" subsections with it; the blank
// lines between sections stay where they were, so only the sections themselves trade places.
function reorderH3Sections(pageContent, language, order) {
var range = langSectionRange(pageContent, language);
var lines = pageContent.slice(range.start, range.end).split("\n");
var starts = [];
var seen = {};
lines.forEach(function (line, i) {
var m = H3.exec(line);
if (m) {
seen[m[1]] = (seen[m[1]] || 0) + 1;
starts.push({ line: i, id: h3Id(m[1], seen[m[1]]) });
}
});
if (starts.length !== order.length || starts.some(function (st) {
return order.indexOf(st.id) === -1;
})) {
throw new SubmitAbort("Beşên vî zimanî ji dema ku rûpel vebû guherîne; rêza wan nehate guhertin.");
}
var tail = [];
var lastEnd = lines.length;
while (lastEnd > starts[starts.length - 1].line + 1 && isTailLine(lines[lastEnd - 1])) {
lastEnd--;
}
tail = lines.slice(lastEnd);
var bodies = {};
var gaps = [];
starts.forEach(function (st, k) {
var end = k + 1 < starts.length ? starts[k + 1].line : lastEnd;
var block = lines.slice(st.line, end);
var gap = [];
while (block.length > 1 && !block[block.length - 1].trim()) {
gap.unshift(block.pop());
}
bodies[st.id] = block;
gaps.push(gap);
});
var out = lines.slice(0, starts[0].line);
order.forEach(function (id, k) {
out = out.concat(bodies[id], gaps[k]);
});
out = out.concat(tail);
return pageContent.slice(0, range.start) + out.join("\n") + pageContent.slice(range.end);
}
// The same heading at another depth: "=== Çavkanî ===" -> "==== Çavkanî ====", spacing kept.
function setHeadingDepth(pageContent, language, header, ordinal, depth) {
var loc = wt.locateHeadingByText(pageContent, language, header, ordinal);
if (!loc) {
throw new SubmitAbort("Sernavê «" + header + "» nehate dîtin.");
}
var m = /^(={3,6})([ ]*)(.+?)([ ]*)\1\s*$/.exec(loc.lines[loc.headingLineIndex]);
var marks = wt.headingMarks(depth);
loc.lines[loc.headingLineIndex] = marks + m[2] + m[3] + m[4] + marks;
return pageContent.slice(0, loc.langSectionIndex) + loc.lines.join("\n");
}
// The "===" headings of one language on the page, in page order, as ids.
function domH3Order(language) {
var out = [];
var seen = {};
var inside = false;
$(".besazimani[id]").add($("h3")).each(function () {
var $el = $(this);
if ($el.is(".besazimani[id]")) {
inside = this.id === language;
return;
}
if (inside) {
var header = sections.headingText($el);
seen[header] = (seen[header] || 0) + 1;
out.push({ id: h3Id(header, seen[header]), header: header, el: this });
}
});
return out;
}
function dropQueuedKey(key) {
dsbQueue.entries.filter(function (entry) {
return entry.key === key;
}).forEach(dropQueueEntry);
}
// Per language: the "===" sections as the page has them, and the order the queue will give them.
var h3Orders = {};
// Moves an "===" section by one place (by is -1 or 1) in its language's queued order. Returns its new
// place and the number of sections, or null when it is already at that end.
function moveH3(language, $heading, by) {
var state = h3Orders[language];
if (!state) {
var dom = domH3Order(language);
state = h3Orders[language] = {
original: dom.map(function (d) {
return d.id;
}),
headers: {},
current: null
};
dom.forEach(function (d) {
state.headers[d.id] = d.header;
});
state.current = state.original.slice();
}
var mine = domH3Order(language).filter(function (d) {
return d.el === $heading[0];
})[0];
var at = mine ? state.current.indexOf(mine.id) : -1;
var to = at + by;
if (at === -1 || to < 0 || to >= state.current.length) {
return null;
}
state.current.splice(at, 1);
state.current.splice(to, 0, mine.id);
var key = "order:" + language;
if (state.current.join("\u0002") === state.original.join("\u0002")) {
dropQueuedKey(key);
return { index: to, count: state.current.length };
}
var order = state.current.slice();
var names = order.map(function (id) {
return state.headers[id];
}).join(" · ");
queueChange(langLabel(language), "rêza beşan bû: " + names,
function (pageContent) {
return { content: reorderH3Sections(pageContent, language, order), warnings: [] };
},
$(), $(),
function () {
var $mark = $(".besazimani[id]").filter(function () {
return this.id === language;
}).first();
var $h2 = headingOuter($mark.closest("h2").length ? $mark.closest("h2") : $mark);
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("Rêza nû ya beşan: " + names + " — nû, hîn nehatiye qeydkirin");
$h2.after($note);
return function () {
$note.remove();
};
},
{ key: key, runFinal: 1 });
return { index: to, count: state.current.length };
}
// Queues a heading's new depth (3 to 5); its own depth again takes the queued change back.
function setDepth(language, $heading, depth) {
var header = sections.headingText($heading);
var ordinal = sections.headingTextOrdinalWithinLanguage($heading);
var from = sections.headingDepth($heading);
var key = "depth:" + language + "|" + header + "|" + ordinal;
if (depth === from) {
dropQueuedKey(key);
return;
}
queueChange(langLabel(language) + "/" + header, "sernav ji h" + from + " bû h" + depth,
function (pageContent) {
return { content: setHeadingDepth(pageContent, language, header, ordinal, depth), warnings: [] };
},
$(), $(),
function () {
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ h" + depth + " — nû, hîn nehatiye qeydkirin");
headingOuter($heading).after($note);
return function () {
$note.remove();
};
},
{ key: key, runFinal: 2 });
}
// ---- wikitext location ----
//
// One copy for both editing gadgets, in wikt.edit: add-examples writes into the same places.
var langSectionRange = wt.langSectionRange;
var locateSection = wt.locateSection;
// Fetches the page's current wikitext and pulls out just this sense's gloss text to prefill the "Maneyê
// biguhêre" form. A plain read, not part of the hardened save path -- the actual save still re-fetches
// fresh content, so only the prefilled text itself could be a little stale.
// Current contents of one existing subsection, for prefilling the edit form.
// This page's current wikitext.
// One fetch for the whole queue: every preview reads the same revision, and asking per preview meant
// downloading the page again for each queued change. Dropped after a save, when the page has moved on.
var pageContentPromise = null;
function forgetPageContent() {
pageContentPromise = null;
}
function fetchPageContent() {
if (pageContentPromise) {
return pageContentPromise;
}
pageContentPromise = rawPageContent();
// A failed read must not be remembered as the answer.
pageContentPromise.fail(forgetPageContent);
return pageContentPromise;
}
function rawPageContent() {
return api.get({
action: "query",
prop: "revisions",
rvprop: "content",
titles: mw.config.get("wgPageName"),
formatversion: 2
}).then(function (data) {
var page = data.query && data.query.pages && data.query.pages[0];
var content = page && !page.missing && page.revisions && page.revisions[0] && page.revisions[0].content;
if (!content) {
throw new SubmitAbort("Nikare muhtewaya rûpelê bixwîne.");
}
return content;
});
}
// ---- per-definition UI: a small always-visible "+" toggle (no hover, so it works on mobile/touch too) ----
// A growing list of word rows. The row itself is util.js's, shared with the wizard; this owns the list:
// typing in the last row appends a fresh empty one, so there is always somewhere to type.
var WORDLIST_COLUMN_THRESHOLD = 6;
function buildWordListField(placeholder, opts) {
opts = opts || {};
var $list = $("<ol class='dsb-wordlist'></ol>");
// One column is a long scroll once a list gets past a handful of words. The class only asks for
// two; the stylesheet still gives one on a narrow screen. Two columns inside the panel's usual
// 520px would leave each word about 250px, so the panel drops its width cap for as long as any
// list in it is in two columns -- checked across the whole panel, since a "Herwiha binêre" holds
// several groups and one of them going wide settles it for all.
function updateColumns() {
$list.toggleClass("dsb-wordlist-2col",
$list.children("li").length > WORDLIST_COLUMN_THRESHOLD);
var $panel = $list.closest(".dsb-subsection-panel");
$panel.toggleClass("dsb-panel-wide", !!$panel.find(".dsb-wordlist-2col").length);
}
function makeRow() {
var row = util.makeModifierRow({
prefix: "dsb-",
placeholder: placeholder,
modTitle: util.msg.addModifier,
removeTitle: util.msg.removeWord,
removeModTitle: util.msg.removeModifier,
modifiers: opts.modifiers,
// "bixe qutiyan" puts each comma-separated word in a row of its own, right after the row it
// was typed in, and the caret follows to the last of them. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way --
// a comma left inside a parameter would not be.
onSplit: function (rest) {
var $after = row.$li;
var last = null;
rest.forEach(function (raw) {
var extra = makeRow();
$after.after(extra.$li);
$after = extra.$li;
if (raw) extra.setRaw(raw);
last = extra;
});
// Always an empty row at the end to type the next word into.
var $tail = $list.children("li").last();
if ($.trim($tail.data("row").getRaw())) {
last = makeRow();
$list.append(last.$li);
}
updateColumns();
if (last) last.focus();
},
// raw === null means the row removed itself, so the count has changed either way.
onChange: function (raw) {
if (raw !== null) {
var isLast = row.$li.is($list.children("li").last());
if (isLast && raw) $list.append(makeRow().$li);
}
updateColumns();
}
});
row.$li.data("row", row);
return row;
}
$list.append(makeRow().$li);
return {
$element: $list,
values: function () {
var vals = [];
$list.children("li").each(function () {
var raw = $(this).data("row").getRaw();
if (raw) vals.push(raw);
});
return vals;
},
// Replaces the whole list, keeping one empty trailing row to type into.
setValues: function (raws) {
$list.empty();
raws.forEach(function (raw) {
var row = makeRow();
$list.append(row.$li);
row.setRaw(raw);
});
$list.append(makeRow().$li);
updateColumns();
},
focus: function () {
$list.children("li").first().data("row").focus();
}
};
}
// A heading's place in the page, which orders queued renames and removals bottom-up.
function headingPosition($el) {
var $h = $el.is("h2, h3, h4, h5, h6") ? $el : $el.find("h2, h3, h4, h5, h6").first();
return $("h2, h3, h4, h5, h6").index($h);
}
// Attaches the "+ Binebeşekê lê zêde bike" trigger right after a POS block's own last element.
// Puts the same pencil the definitions use on every existing "====" subsection heading the gadget knows how
// to edit, opening buildSubsectionPanel() pinned to that one subsection.
// An editor for [[MediaWiki:Gadget-wikt.translation-adder.js]] whose changes go into this queue, under
// "Kurmancî/Navdêr" like every other change to that section. Each change is tried on the page as the
// queue would save it before it is queued, as the adder's own editor does.
function translationEditor($h3, language, sectionType) {
var place = placeLabel(language, sectionType, $h3);
var $status = $("<span></span>");
var $save = $("<span></span>");
// The entries this editor queued, so "Paşve" takes back the adder's own last change and not one
// made elsewhere after it.
var mine = [];
var adapter = {
withCurrentText: function (callback) {
fetchPageContent().then(function (content) {
callback(queuedContent(content));
}, function () {
adapter.error("Nikare muhtewaya rûpelê bixwîne.");
});
},
// node is the line the change draws; it is highlighted while the change waits in the queue.
addEdit: function (edit, node) {
adapter.withCurrentText(function (text) {
var next;
try {
next = edit.edit(text);
} catch (e) {
adapter.error(String(e));
return;
}
if (typeof next !== "string" || next === text) {
return;
}
queueChange(place, edit.summary, edit.edit, $save, $status, function () {
edit.redo();
if (node) {
node.classList.add("translation-adder-new");
}
return function () {
if (node) {
node.classList.remove("translation-adder-new");
}
edit.undo();
};
});
mine.push(dsbQueue.entries[dsbQueue.entries.length - 1]);
});
},
error: function (message) {
mw.notify(message, { type: "error" });
},
// The adder's "Paşve" takes back its last change still in the queue.
undo: function () {
while (mine.length) {
var last = mine.pop();
if (dsbQueue.entries.indexOf(last) !== -1) {
dropQueueEntry(last);
return;
}
}
}
};
return adapter;
}
module.exports = {
translationEditor: translationEditor,
clearQueue: clearQueue,
closeAllPanels: closeAllPanels,
openQueueBox: openQueueBox,
moveH3: moveH3,
setDepth: setDepth,
reorderH3Sections: reorderH3Sections,
setHeadingDepth: setHeadingDepth,
// For the panel modules: what they share with this one.
shared: {
SubmitAbort: SubmitAbort,
announceContent: announceContent,
apiErrorMessage: apiErrorMessage,
buildWordListField: buildWordListField,
dropQueueEntry: dropQueueEntry,
dsbQueue: dsbQueue,
fetchPageContent: fetchPageContent,
fillWithRendered: fillWithRendered,
headingPosition: headingPosition,
hideQueueIfIdle: hideQueueIfIdle,
langLabel: langLabel,
locateSection: locateSection,
makeSearchable: makeSearchable,
openQueueBox: openQueueBox,
placeLabel: placeLabel,
queueChange: queueChange,
queuedContent: queuedContent,
redrawPreviews: redrawPreviews,
renderWikitext: renderWikitext,
sectionLabel: sectionLabel,
translationEditor: translationEditor
}
};
}());
// </nowiki>
r4hhezjiqmy6y5qdisd0fvmw3kk0am5
MediaWiki:Gadget-wikt.start-wizard.js
8
1359175
6865630
6531672
2026-10-23T22:38:58Z
Wikihez
7344
Start wizard: step definitions from wikt.start-wizard-steps
6865630
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.start-wizard.js]]
*/
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
// The same gate DefSideBoxes, add-examples and this wizard's own launcher use, so a page one of them
// works on is never a page this one quietly refuses to open on.
var ns = mw.config.get("wgNamespaceNumber");
if (!require("ext.gadget.wikt.core").sections.isEntryPage()) {
return;
}
// Reconstructed entries (ns 108) are unattested proto-forms: there is no recorded usage to give an
// example of or quote, and no dialects to mark, so the sense block leaves out Nimûne, Jêgirtin and
// Dever/Şîwe there.
var isReconstruction = ns === 108;
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data-main.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
// Defaults and storage live in wikt.core/prefs.js, because the launcher reads them on every page view.
var wiktkuPrefsApi = require("ext.gadget.wikt.core").prefs;
function wiktkuLoadPrefs() { return wiktkuPrefsApi.load(); }
function wiktkuSavePrefs(prefs) { return wiktkuPrefsApi.save(prefs); }
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Clears every draft cache and resize height this script writes, for all pages. Tercîh itself is
// left alone: it is the setting being edited when this button is reached.
// The state it touches is assigned by build(), which has always run by the time this is callable.
function wiktkuClearAllScriptCache() {
// Cancel the pending autosave, or it would rewrite the cache from the still-filled form.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps formCache's in-memory copy in step with the localStorage sweep below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// ObjectStorage wraps the draft key as "enwikt/ObjectStorage/<name>?v=<n>&Type=…", so it
// is matched as a substring; the mw.storage keys below are plain prefixes.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: wiktkuUtil.msg.cancel, flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
// Two named groups rather than one "Eyar" heap: which languages the wizard offers, then
// how it behaves. The dialog title already says these are preferences.
var langSet = new OO.ui.FieldsetLayout({ label: "Ziman" });
langSet.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
})
]);
var behaviourSet = new OO.ui.FieldsetLayout({ label: "Wextê xebitînê" });
behaviourSet.addItems([
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$element.addClass("wiktku-prefs-dialog");
this.$body.addClass("wiktku-prefs-dialog-body")
.append(langSet.$element, behaviourSet.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
// The launcher gadget adds the portlet link and decides whether this page wants the wizard before
// loading it at all. Both are repeated here only when this file was loaded on its own.
var wiktkuLaunched = !!(window.wikt && window.wikt.start && window.wikt.start.launched);
if (!wiktkuLaunched && !document.getElementById("ca-wiktku-addlang")) {
wiktkuPrefsApi.addLauncherLink(wiktkuPrefs);
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuEdit = require("ext.gadget.wikt.edit");
var wiktkuUtil = wiktkuEdit.util;
var wiktkuWikitext = wiktkuEdit.wt;
var wiktkuLangs = wiktkuEdit.langs;
// Citations (jêder-*, Kategorî:Çavkanî, Citoid) all go through one module -- see wikt.cite.js.
var wiktkuCite = require("ext.gadget.wikt.cite");
var wiktkuPosparams = wiktkuEdit.posparams;
// Assigned once data-main.json is in: re-merges the language choices, and is called a second time if the
// full language list has to be fetched. Declared here so the step renderer can reach it.
var wiktkuMergeLanguages = null;
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
var langHeaderSource = wiktkuWikitext.langHeaderSource;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
// One delete button per tab bar, acting on the tab in view. The trash glyph
// (oojs-ui.styles.icons-moderation) is fetched here rather than at startup: it is only needed once a
// second Mane or Beş exists. The button is built immediately and the icon appears when the CSS lands.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp("\n?" + langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
// The category's members come from wikt.cite, which also lists each template's redirects so it can be
// found by any of its names. Entries are {label, target}: label is what was typed/shown, target the
// canonical template to transclude.
var catSrcTemplates = null;
function fetchCatSrcTemplates() {
return wiktkuCite.fetchCategoryTemplates().then(function (list) {
catSrcTemplates = list;
return list;
});
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// The mane box holds a Kurdish definition for a Kurdish entry, and a direct translation for every
// other language -- the {{îzah}} box under it carries the explanation.
function manePlaceholder(code) {
return code && code !== "ku" ? wiktkuUtil.msg.tercimePrompt : "Maneya vê kelîmeyê binivîse…";
}
// A Kurmancî example gets an explanation, any other a translation.
function nimuneTranslationPrompt(code) {
return code === "ku" ? "Îzaheke nimûneyê bi kurmancî…" : "Tercimeya nimûneyê bi kurmancî…";
}
// Populated from data-main.json at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices, posChoices)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data-main.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data-main.json's "numberLabels"
var NUMBER_LABELS = {};
var SENSE_GROUPS = [];
var WORDLIST_TEMPLATE = "kol3"; // replaced by data-main.json's first wordlistTemplates entry
// The pills the Dever/Şîwe picker opens with; everything else comes from Modul:labels by typing.
var DEVER_QUICK_PICKS = [];
var LEKTUR_CHOICES = [];
var LEKTUR_FORM_CHOICES = [];
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data-main.json as the fallback for
// templates that have none.
// {desc, params} for the ⓘ popup, from wikt.edit -- the fetch, the shaping and the cache are all
// there, shared with wikt.cite and DefSideBoxes, so a template is asked for once however many pickers
// show it. Null for a template with no TemplateData, and that answer is kept too, so clicking ⓘ on
// an undocumented template twice is one request.
var fetchTemplateData = wiktkuEdit.wt.templateDoc;
var prefetchTemplateData = wiktkuEdit.wt.templateDocBatch;
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data-main.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuEdit.wt.templateDocNow(name);
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
opt.text(name + " — " + wiktkuEdit.wt.shortDesc(desc));
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Description from data-main.json's "desc", for templates with no TemplateData on the wiki. Works for any
* pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
// Under the ⓘ it was opened from, kept inside the viewport, and flipped above it when there is no
// room below. Narrow screens get the width they have rather than a fixed card off the edge.
//
// box is where that ⓘ was when it was clicked. The row it sits in is closed by then, so it can no
// longer be measured; the last known box is kept for the re-placements after the text arrives.
function positionTplInfo(panel, anchor, box) {
var el = $(anchor).get(0);
var rect = box || (el && el.getBoundingClientRect ? el.getBoundingClientRect() : null);
if (rect && !rect.width && !rect.height) {
rect = null;
}
if (rect) {
panel.data("wiktkuAnchorBox", rect);
} else {
rect = panel.data("wiktkuAnchorBox") || null;
}
var margin = 8;
var arrow = 8;
var width = Math.min(360, $(window).width() - margin * 2);
panel.css({ width: width + "px", top: "0px", left: "0px" });
var height = panel.outerHeight();
// Above the dropdown, pointing down at it, the way MediaWiki's own filter tips sit; below it only
// when there is no room above.
var above = !rect || rect.top - height - arrow >= margin;
var top = !rect ? margin : (above ? rect.top - height - arrow : rect.bottom + arrow);
var left = rect ? Math.min(Math.max(margin, rect.left), $(window).width() - width - margin) : margin;
// The arrow points at the start of the box it explains, kept clear of the tip's corners.
var arrowX = rect ? Math.min(Math.max(16, rect.left + 24 - left), width - 16) : 16;
panel.toggleClass("wiktku-tpl-info-above", above).toggleClass("wiktku-tpl-info-below", !above);
panel.css({ top: top + "px", left: left + "px" });
panel.get(0).style.setProperty("--wiktku-arrow-x", arrowX + "px");
}
/** Opens the explanation for `name` as a tip above the dropdown it was asked from. */
function showTemplateInfo(name, anchorWrap, anchorEl, anchorBox) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text(wiktkuUtil.msg.loading);
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text(wiktkuUtil.msg.openTemplatePage + " →"));
function closePanel() {
panel.remove();
$(document).off(".wiktkuTplInfo");
$(window).off(".wiktkuTplInfo");
}
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", closePanel);
panel.prepend(close);
$(document).on("keydown.wiktkuTplInfo", function (e) {
if (e.which === 27) {
closePanel();
}
});
$(document).on("mousedown.wiktkuTplInfo", function (e) {
if (!$(e.target).closest(".wiktku-tpl-info").length) {
closePanel();
}
});
// A panel that stayed put while the page moved under it would point at nothing.
$(window).on("resize.wiktkuTplInfo scroll.wiktkuTplInfo", function () {
positionTplInfo(panel, anchorWrap);
});
// Over the form, not inside it: as a block in the flow it pushed every field below it down the
// page for as long as it was open, and the picker it explains moved with them.
$(document.body).append(panel);
positionTplInfo(panel, anchorWrap);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.key + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
// The panel is as tall as its text, which only arrives now.
positionTplInfo(panel, anchorWrap);
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data-main.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.maneid) return true;
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
var anyWords = false;
$.each(wordGroups(), function (_, g) {
if ((s[g.field] || []).length) anyWords = true;
});
if (anyWords || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? wiktkuCite.nlToBr(text) : text;
}
// Line breaks in a field save as "<br>"; this marks them so that is visible while typing.
var highlightLinebreak = wiktkuUtil.makeLinebreakHighlighter({ prefix: "wiktku" });
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
// A sense with no example is left as it is, with no "{{nimûne?}}" placeholder.
return "";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
// The builder DefSideBoxes and add-examples use: the translation in slot 3, rêz=1 for a short pair.
return "\n" + wiktkuCite.buildCitation({
mode: "plain",
marker: "#:",
language: lang,
title: pageName,
quote: text,
translation: typeof n === "object" ? n.t : "",
transcription: typeof n === "object" ? n.tr : ""
});
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var mode = jeder.raw ? "raw"
: jeder.jederTpl ? "jeder"
: jeder.jederCatTpl ? "category"
: "plain";
return "\n" + wiktkuCite.buildCitation({
mode: mode,
marker: "#*",
language: lang,
template: jeder.jederTpl || jeder.jederCatTpl,
fields: jeder.jederTpl ? (jeder.jêderFields || {}) : (jeder.jederCatFields || {}),
langParamKey: jeder.jederCatLangParam,
quote: jeder.quote,
transcription: jeder.quoteTr,
translation: jeder.quoteT,
source: jeder.jederManual || (mode === "plain" && !jeder.url ? "çavkanî?" : ""),
sourceURL: jeder.url
});
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîwe values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîwe
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// "{{maneid|lang|x}}", the anchor id a sense can be linked by. First of the leading calls, which is
// data-main.json's leadingTplOrder.
function buildManeidTpl(sense, lang) {
var id = $.trim(sense.maneid || "");
return id ? "{{maneid|" + lang + "|" + id + "}}" : "";
}
// Quick-pick suggestions for the Dever/Şîwe, Etîket and Kategorî chip pickers on each sense, taken
// from [[Modul:labels]] rather than kept by hand here. Free text is still always accepted; this only
// saves typing, and keeps the lists honest as the module changes.
//
// Fetched on the first time one of the three panels is opened, never on load: the wizard opens on
// pages that never touch these fields. Once it lands, the open panel re-renders itself.
var wiktkuLabelSets = null;
var wiktkuLabelsPending = null;
var wiktkuLabelListeners = [];
function wiktkuEnsureLabels() {
if (wiktkuLabelSets || wiktkuLabelsPending) return;
wiktkuLabelsPending = wiktkuEdit.labels.load().then(function (sets) {
wiktkuLabelSets = sets;
wiktkuLabelsPending = null;
$.each(wiktkuLabelListeners, function (_, fn) { fn(); });
}, function () {
wiktkuLabelsPending = null;
});
}
// Which list each picker shows. Dever/Şîwe is the regions of the language being edited; Etîket adds
// the qualifiers and subject labels on top; Kategorî is the subject labels alone.
function wiktkuLabelSuggestions(which) {
if (!wiktkuLabelSets) return [];
var lang = (getValues().lang || "ku");
if (which === "dever") return wiktkuEdit.labels.regionalFor(wiktkuLabelSets, lang);
if (which === "etiket") return wiktkuEdit.labels.etiketFor(wiktkuLabelSets, lang);
return wiktkuEdit.labels.kategoriNames(wiktkuLabelSets);
}
// Lower-cased alias -> canonical label, so typing a name the module lists as an alias still finds it.
// Built once per label load rather than per keystroke.
var wiktkuAliasCache = null;
function wiktkuLabelAliases() {
if (!wiktkuLabelSets) return {};
if (!wiktkuAliasCache) {
wiktkuAliasCache = wiktkuEdit.labels.aliasIndex(wiktkuLabelSets);
}
return wiktkuAliasCache;
}
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
/**
* The word lists (Hevmane, Dijmane, Formên alternatîv, Termên koordîne, Jornav, Jêrnav), taken from
* data-main.json's senseGroups so the wizard, DefSideBoxes and the wikitext writer all name them once.
* That array's own order is the order the "#:" lines are written to the page in; "order" is the
* order they are shown in, which is not the same. cls is how each one's box is found in the DOM.
*/
function wordGroups() {
return SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return { cls: "wiktku-dfn-" + g.field, label: g.label, field: g.field, izah: g.hint || "" };
});
}
/** True for the six word lists, which get the ⊕-modifier row; Nimûne and Jêgirtin do not. */
function isWordGroupCls(cls) {
var found = false;
$.each(SENSE_GROUPS, function (_, g) {
if ("wiktku-dfn-" + g.field === cls) found = true;
});
return found;
}
/**
* Wraps a textarea in a <li> with a ⊕ modifier button and returns the <li>.
*
* `ta` stays hidden and holds the raw "word<key:value>…" text every reader and writer expects. What
* is typed into is a plain word box, with one pill per modifier above it; clicking a pill edits its
* value, ⊕ adds another.
*/
function makeModifierLi(ta) {
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Not ta's own classes: code elsewhere counts elements by those, so a second matching textarea
// would skew the counts. wiktku-word-input carries the same look.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared code that has no reason to know
// wordInput exists -- mirror them instead of touching every call site.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
// ta stays the source of truth: the row writes back to it, and the caller's growth listener is
// bound to ta's "input" event.
var row = wiktkuUtil.makeModifierRow({
prefix: "wiktku-",
wordInput: wordInput,
modTitle: "Modifiyerê lê zêde bike",
removeTitle: wiktkuUtil.msg.removeWord,
removeModTitle: wiktkuUtil.msg.removeModifier,
onChange: function (raw) {
if (raw === null) return; // the row removed itself; ta goes with it
ta.val(raw);
ta.trigger("input");
},
// "bixe qutiyan" puts each comma-separated word in a row of its own. The list grows off the
// hidden field's own "input" event, so filling the trailing row is what makes the next one
// appear; the caret ends up in the empty row at the bottom. One word per row is also one
// word per template parameter, which both {{hevmane}} and {{kol3}} read the same way -- a
// comma left inside a parameter would not be.
onSplit: function (rest) {
// A tick later, not now: the row writes its own value to the hidden field after this
// call returns, and it is that field's "input" that grows the list -- so the empty row
// to put the next word in does not exist yet.
setTimeout(function () {
var $list = row.$li.closest("ol, ul");
rest.forEach(function (raw) {
if (!raw) return;
var setRaw = $list.children("li").last().data("setRawValue");
if (setRaw) setRaw(raw);
});
$list.children("li").last().find(".wiktku-word-input").focus();
}, 0);
}
});
row.setRaw(ta.val());
var li = row.$li;
// restoreSubList's "fill an existing row" path sets a raw "word<key:value>" value here.
li.data("setRawValue", function (raw) {
row.setRaw(raw);
ta.val(row.getRaw());
ta.trigger("input");
});
li.prepend(ta);
return li;
}
/**
* A "chip + toggle" field: values render as removable chips, and a "+ <label>" toggle opens a panel
* with quick-pick pills plus free text. panel is returned apart from wrap so callers can group all
* toggles in one row and all panels below, keeping toggles from shifting when one opens.
*
* Pills carry a × as well: hiding a built-in one, or typing a new value, is remembered per wrapCls
* in mw.storage.
*
* opts: { wrapCls, toggleLabel, hint, suggestions (array or function), placeholder, onOpen }
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
// One document listener for every sense's "Hevmane û Bêhtir" menu, not one per sense: a long entry can hold a
// dozen senses and each would otherwise bind its own.
var moreMenuCloserBound = false;
function bindMoreMenuCloser() {
if (moreMenuCloserBound) return;
moreMenuCloserBound = true;
$(document).on("click.wiktkuMore", function (e) {
if ($(e.target).closest(".wiktku-dfn-more-wrap").length) return;
$(".wiktku-dfn-more-menu").hide();
$(".wiktku-dfn-more-arrow").html("▸");
});
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
// opts.suggestions is either a fixed array or a function returning one -- the label-driven
// pickers depend on the language being edited, and on data that arrives from Modul:labels after
// the field is built, so they cannot be a snapshot taken here.
function baseSuggestions() {
var list = typeof opts.suggestions === "function" ? opts.suggestions() : opts.suggestions;
return list || [];
}
function allSuggestions() {
return $.grep(baseSuggestions(), function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
// Label-driven pickers are built before Modul:labels has been read, so re-render once it lands.
if (typeof opts.suggestions === "function") {
wiktkuLabelListeners.push(function () {
if (panel.is(":visible")) renderSuggest();
});
}
// At most this many pills at once. Modul:labels offers 87 regions, 362 etîket and 209 kategorî;
// drawing them all was a wall of pills nobody reads and the field it belonged to scrolled away.
var MAX_PILLS = 24;
// What shows with an empty box: the short hand-picked list if the caller gave one, otherwise the
// full set (capped) as before, for pickers whose lists are small enough to browse.
function restingSuggestions() {
var quick = typeof opts.quickPicks === "function" ? opts.quickPicks() : opts.quickPicks;
if (!quick || !quick.length) {
return allSuggestions();
}
// A value the person added themselves outranks the hand-picked list -- it is theirs.
return quick.concat($.grep(addedSuggestions, function (s) { return quick.indexOf(s) === -1; }));
}
// Typed text searches the WHOLE set, not just the pills on screen, and matches the module's own
// aliases too, so "Behdînanî" finds Badînî and "USA" finds DYA.
function matching(query) {
var q = $.trim(query).toLowerCase();
if (!q) {
return restingSuggestions();
}
var aliases = wiktkuLabelAliases();
var hits = $.grep(allSuggestions(), function (name) {
return name.toLowerCase().indexOf(q) !== -1;
});
$.each(aliases, function (alias, canonical) {
if (alias.indexOf(q) !== -1 && hits.indexOf(canonical) === -1 &&
allSuggestions().indexOf(canonical) !== -1) {
hits.push(canonical);
}
});
return hits;
}
function renderSuggest(query) {
var taken = values();
suggestRow.empty();
var shown = 0;
$.each(matching(query || ""), function (_, name) {
if (taken.indexOf(name) !== -1 || shown >= MAX_PILLS) return;
shown++;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val(""); renderSuggest(""); input.focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Typing searches the whole list, so a value that is not among the pills is still one keystroke
// from being found rather than something you have to know by heart and type out in full.
input.on("input", function () {
renderSuggest(input.val());
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
// Box first, pills under it. The other way round, every keystroke re-filtered the pills above the
// box and the box itself jumped up and down the page as the list grew and shrank.
panel.append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
).append(suggestRow);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîwe starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
},
/** Opens the panel from somewhere other than the toggle — the
* "Hevmane û Bêhtir" menu, which is where Etîket and Kategorî are asked
* for now. Counts as a manual toggle for the same reason close()
* does. */
open: function () {
userToggled = true;
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
}
};
}
/**
* One page-level word list (Formên alternatîv, Hevmane, … Herwiha binêre): a "====" section holding
* the words for the whole entry rather than for one sense.
*
* Only the question is passed in. The heading and the step's own name come from data-sections.json's
* pageSubsections, filled in below once the data lands — the same table DefSideBoxes reads to find
* these sections, so the wizard cannot write a heading the panels then fail to recognise.
*/
function makeWordListStep(id, question_fmt) {
return {
id: id,
name: id,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
if (!words.length) return "";
var header = steps[id].header || capitalize(steps[id].name);
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{" + WORDLIST_TEMPLATE + "|" + lang + "|" + params + "\n}}\n";
} };
}
// The helpers and settings the step definitions use, for wikt.start-wizard-steps. DEVER_QUICK_PICKS,
// SENSE_GROUPS, catSrcTemplates, pageName and wiktkuLabelSets change after loading, so they are passed as
// getters and setters on this same closure rather than as copies.
function wizardContext() {
var ctx = {
GENDER_BASE_LABELS: GENDER_BASE_LABELS,
ZARAVA_LANG_CODES: ZARAVA_LANG_CODES,
addLangMode: addLangMode,
applyTplDescriptions: applyTplDescriptions,
bindMoreMenuCloser: bindMoreMenuCloser,
buildChipField: buildChipField,
buildCite: buildCite,
buildDeverTpl: buildDeverTpl,
buildEtiketTpl: buildEtiketTpl,
buildManeidTpl: buildManeidTpl,
buildSenseTpl: buildSenseTpl,
capitalize: capitalize,
decapitalize: decapitalize,
fetchCatSrcTemplates: fetchCatSrcTemplates,
findLangHeader: findLangHeader,
getLangLabel: getLangLabel,
getValues: getValues,
highlightLinebreak: highlightLinebreak,
initSelects: initSelects,
insertAtCursor: insertAtCursor,
insertLineSnippet: insertLineSnippet,
isReconstruction: isReconstruction,
isWordGroupCls: isWordGroupCls,
langHasMultipleScripts: langHasMultipleScripts,
langsByCode: langsByCode,
makeModifierLi: makeModifierLi,
makeTrashButton: makeTrashButton,
makeWordListStep: makeWordListStep,
manePlaceholder: manePlaceholder,
markTemplateOptions: markTemplateOptions,
needsTransliteration: needsTransliteration,
nimuneTranslationPrompt: nimuneTranslationPrompt,
populateMenus: populateMenus,
refresh: refresh,
senseHasExtraContent: senseHasExtraContent,
splitTercimeIzah: splitTercimeIzah,
stripWikilinks: stripWikilinks,
tplNameFromSnippet: tplNameFromSnippet,
wiktkuCite: wiktkuCite,
wiktkuEdit: wiktkuEdit,
wiktkuEnsureLabels: wiktkuEnsureLabels,
wiktkuLabelListeners: wiktkuLabelListeners,
wiktkuLabelSuggestions: wiktkuLabelSuggestions,
wiktkuPosparams: wiktkuPosparams,
wiktkuUtil: wiktkuUtil,
wiktkuWikitext: wiktkuWikitext,
wordGroups: wordGroups
};
Object.defineProperties(ctx, {
DEVER_QUICK_PICKS: { get: function () { return DEVER_QUICK_PICKS; }, set: function (value) { DEVER_QUICK_PICKS = value; } },
SENSE_GROUPS: { get: function () { return SENSE_GROUPS; }, set: function (value) { SENSE_GROUPS = value; } },
catSrcTemplates: { get: function () { return catSrcTemplates; }, set: function (value) { catSrcTemplates = value; } },
pageName: { get: function () { return pageName; }, set: function (value) { pageName = value; } },
wiktkuLabelSets: { get: function () { return wiktkuLabelSets; }, set: function (value) { wiktkuLabelSets = value; } }
});
return ctx;
}
// What each step asks, offers and writes: [[MediaWiki:Gadget-wikt.start-wizard-steps.js]].
var steps = require("ext.gadget.wikt.start-wizard-steps")(wizardContext());
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Reading order: the senses, then what belongs to the entry as a whole. Etîmolojî follows
// "Bi alfabeyên din" and keeps a permanent slot; dunde and the three "eleqedar" lists are
// reached through the Beşên din picker like every other optional section.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.etym, steps.zarava, steps.trans,
steps.dunde, steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Sections ─────────────────────────────────────────────────────────────
// One group, no tab bar. What is always on screen: the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin, alfabeyendin, zarava, trans
// and Etîmolojî. Everything else is added from the "Beşên din" picker, which sits at the top of the
// form; a newly-picked section is inserted directly under it (see renderBesenDinPicker()), so the
// most recently added one is always the one nearest the picker.
//
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT absorb the Cins/zayend form
// parameters (m=/n=/p=), which stay on the headword template.
// Behind the "Beşên din" picker: shown only after an explicit open, the way DefSideBoxes' own
// "+ Beşek lê zêde bike" works -- nothing is on screen until it is asked for.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin",
"cavkani", "xwendin", "bnrh", "dunde", "je", "tekildar", "binereherwiha"];
// One group, so no tab bar: the senses, the fields that go with them, and Etîmolojî, which is common
// enough to keep a permanent slot. Everything else is reached through the picker above.
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans", "etym"] }
];
// Step id -> index into STEP_GROUPS; undefined for the always-visible ungrouped steps.
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls the given tab bar to the top of the viewport, below the sticky offset, so a switched-to tab
// does not open off-screen. Deferred one frame: switching can shorten the page, and the browser may
// clamp the scroll position before the measurement runs.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
// One group is nothing to switch between. Done here rather than only where the bar is built,
// because the form is rebuilt on language/part-of-speech changes and a bar left over from an
// earlier build would otherwise sit there until refresh() next got round to hiding it.
if (STEP_GROUPS.length < 2) {
groupTabsDiv.remove();
return;
}
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// The picker sits at the top of the form, above the senses, and is on screen whenever the form is.
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
// Every section it offers belongs to a part-of-speech section, so there is nothing to add until
// one is chosen. Checked here rather than only in refresh(): this function is also called on form
// build and on section changes, and each of those would otherwise show the picker on its own.
if (!pickerValues.pos) {
wrap.hide();
return;
}
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) {
wrap.hide(); // everything's open — nothing left to pick
return;
}
// Same voice and styling as a step's own question line, not a section heading -- this picker sits
// between cards rather than being one.
wrap.append($("<label class='wiktku-start-step-question'></label>").text("Binebeşek lê zêde bike"));
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
wrap.show();
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find("textarea.wiktku-dfn-mane").val(maneParts ? $.trim(maneParts.tercime) : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? $.trim(maneParts.izah) : "");
var setIzahOpen = block.data("setIzahOpen");
if (setIzahOpen) setIzahOpen(!!(maneParts && $.trim(maneParts.izah)));
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = isWordGroupCls(cls);
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Populate the word lists that the draft has something for. senseGroups' "field" is both the
// property getSenses() read them into and the tail of the box's own class.
$.each(wordGroups(), function (_, g) {
var vals = sense[g.field];
if (vals && vals.length) restoreSubList(g.cls, vals);
});
// Dever/Şîwe, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
block.find("input.wiktku-dfn-maneid-input").val(sense.maneid || "");
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.url) li.find(".wiktku-dfn-quote-url").val(j.url);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.raw) {
li.find(".wiktku-dfn-quote-src-sel").val("__raw__").trigger("change");
} else if (j.jederCatTpl) {
// Reopen the category picker on the saved template and put its fields back. The
// fields arrive asynchronously (TemplateData), so the saved values are applied
// once they exist rather than straight away.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__category__").trigger("change");
li.data("catTemplate", j.jederCatTpl);
if (j.jederCatLangParam) li.data("catLangParam", j.jederCatLangParam);
li.find(".wiktku-dfn-quote-src-cat-input").val(j.jederCatTpl);
// Kept on the row from the moment the restore starts. Submitting walks every section
// -- restore, then read -- and the read is synchronous while the boxes below only
// exist once TemplateData comes back, so without this the values are read from boxes
// that are not there yet and every parameter but the quote is lost. readJederList()
// falls back to these until the boxes catch up.
li.data("catFields", j.jederCatFields || {});
wiktkuCite.fieldSpecs(j.jederCatTpl).then(function (spec) {
var srcFields = li.find(".wiktku-dfn-quote-src-fields");
li.data("catLangParam", j.jederCatLangParam || spec.langParamKey || null);
$.each(spec.fields, function (_, field) {
srcFields.append(li.data("buildSrcFieldRow")(field));
});
$.each(j.jederCatFields || {}, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
// The boxes hold them now, so they are the truth again -- clearing this keeps a
// field the user empties from being refilled from the stale copy.
li.removeData("catFields");
});
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Everything above filled boxes that are still behind the chip row, so each block is asked to
// show whichever of its sub-fields the draft actually had something in.
list.find("li.wiktku-dfn-sense").each(function () {
var syncPicks = $(this).data("syncSensePicks");
if (syncPicks) syncPicks();
});
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-step").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
// Read with the shared parser, so a nested "c=" is never taken for the call's own, and a
// saved "z=" restores the gender the same as "c=".
var savedC = wiktkuWikitext.callParamValue(savedTa, "c");
var savedJ = wiktkuWikitext.callParamValue(savedTa, "j");
var savedTr = wiktkuWikitext.callParamValue(savedTa, "tr");
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (savedC) $("#wiktku-start-cinsiyet-field").val(savedC);
if (savedJ) $("#wiktku-start-jimar-field").val(savedJ);
if (savedTr) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(savedTr);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (savedC && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(savedC);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = wiktkuPosparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
// setValues() keeps a blank trailing row of its own, same as Nimûne/Dûnde.
if (rows.length && steps.resim.field) steps.resim.field.setValues(rows);
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
// Two sections of the same part of speech would otherwise both be "=== Rengdêr ===", which makes them
// indistinguishable on the page, in the table of contents and in every link that targets one. Run over
// the finished sections rather than guessed from the pos alone, so "Rengdêr" and "Formeke rengdêrê"
// -- different headings from the same pos -- are correctly left alone.
function numberRepeatedHeadings(results) {
var headingRE = /^(===\s*)([^=\n]+?)(\s*===)$/m;
var counts = {};
$.each(results, function (_, text) {
var m = headingRE.exec(text || "");
if (m) counts[m[2]] = (counts[m[2]] || 0) + 1;
});
var seen = {};
$.each(results, function (i, text) {
var m = headingRE.exec(text || "");
if (!m || counts[m[2]] < 2) {
return;
}
seen[m[2]] = (seen[m[2]] || 0) + 1;
results[i] = text.replace(headingRE, m[1] + m[2] + " " + seen[m[2]] + m[3]);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
numberRepeatedHeadings(results);
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter the pronunciation template dropdown when language changes -- etym/cavkani's lists don't depend on lang, so they're only built once at data load instead of on every refresh().
steps.pron.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîwe's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and
// reveals any sub-field group that has ended up with content in it
// while still behind the chip row (a draft, a restore).
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncPicks = $(this).data("syncSensePicks");
if (syncPicks) syncPicks();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t once a language is chosen.
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = !!lang;
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
tTa.attr("placeholder", nimuneTranslationPrompt(lang));
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// One group means no bar to show; it only appears again if STEP_GROUPS ever grows back.
$("#wiktku-group-tabs").toggle(STEP_GROUPS.length > 1 && !!values.pos && !viewingPron);
// Bilêvkirin hides the picker outright; otherwise renderBesenDinPicker() decides, showing itself
// only once a part of speech is chosen and there are sections left to offer. The decision lives
// there rather than here because that function has other callers, which would each need the same
// test.
var besenDinActive = !viewingPron;
if (!besenDinActive) {
$("#wiktku-besendin-picker").hide();
}
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
// Ziman and Cureyê rêzimanî are all it takes: an entry with a heading and a template but no
// definition yet is a skeleton somebody means to come back to, not a mistake to be blocked.
continueBtn.setDisabled(!canContinue);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^\n?" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = "\n?" + langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky. group-tabs sits below section-tabs' rendered height, which varies
// when the bar wraps, so it is measured and published as a CSS custom property.
// Vector 2022's own sticky header would cover section-tabs, so its occupied height goes into
// --wiktku-siteheader-h. It slides with scroll, hence the re-measure on scroll as well as resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — the search box for adding a section, at the top of the form;
// renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(\n?" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
var merged = before + sep + newContent + after;
// The section just added may repeat a part of speech the page already had. Both
// are renumbered across the whole language section, so a lone "Rengdêr" that was
// there before becomes "Rengdêr 1" and the new one "Rengdêr 2" -- leaving the old
// one unnumbered would make the pair unreadable on the page.
var secStart = existingHeader.index;
var nextLang = merged.indexOf("\n== ", secStart + existingHeader.match.length);
var secEnd = nextLang === -1 ? merged.length : nextLang;
textbox.val(
merged.slice(0, secStart) +
wiktkuEdit.wt.numberRepeatedPosHeadings(merged.slice(secStart, secEnd)) +
merged.slice(secEnd)
);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// The step the "Beşên din" picker is inserted directly above.
var PICKER_BEFORE_STEP = "tewandin";
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
// The language step ships ~300 languages; if a search comes up empty, fetch the other ~3400
// and refill this select before telling the user there is no such language.
if (step.id === "lang") {
input.data("wiktkuOnNoMatch", function (query, refresh) {
if (!wiktkuMergeLanguages || wiktkuLangs.isFull()) {
return;
}
wiktkuLangs.ensureFull().then(function (all) {
populateMenus(input, wiktkuMergeLanguages(all));
refresh();
});
});
}
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
// With a single group there is nothing to switch between, so the bar is never built --
// rather than built and then hidden, which left it on screen whenever the form was
// rebuilt before refresh() next ran.
if (STEP_GROUPS.length > 1) {
startForm.append(groupTabsDiv);
}
groupTabsInserted = true;
}
// The picker sits between the senses and Tewandin: sections are added underneath it, so a
// newly picked one lands right here and pushes the rest of the form down.
if (step.id === PICKER_BEFORE_STEP) {
startForm.append(besenDinPickerDiv);
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-step").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
// Numbered while there is more than one section of this part of speech, so two tabs are
// never both "Rengdêr" -- the same numbering the headings get on save.
var samePos = $.grep(sections, function (s) { return (s.pos || "") === posVal; });
var nth = 0;
$.each(sections, function (j, s) {
if ((s.pos || "") === posVal && j <= i) nth++;
});
var tabLabel = posVal
? capitalize(posVal) + (samePos.length > 1 ? " " + nth : "")
: ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a growing "multiple" field list (bnrh, je, tekildar, …) from a saved array.
* Values are set with .val() plus an "input" event so refreshForTextField() grows the rows,
* exactly as typing into the last row does.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
continueBtn.$element.addClass("wiktku-footer-primary");
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
sourceEditorBtn.$element.addClass("wiktku-footer-primary");
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel the scheduled autosave, or it would rewrite the cache from the still-filled form.
// The form is left clean, so autosave resumes only once something is edited again.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// Everything sits on the bar: no overflow menu, nothing a click away.
// Settings button — opens the Tercîh dialog. Plain OOUI icon names depend on which icon-pack
// module happens to be loaded, so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne",
"aria-label": "Tercîh"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
clearCacheBtn.$element.addClass("wiktku-btn-danger");
saveCacheBtn.$element.addClass("wiktku-footer-cache");
clearCacheBtn.$element.addClass("wiktku-footer-cache");
footer.append(saveCacheBtn.$element)
.append(clearCacheBtn.$element)
.append(prefsBtn)
.append($("<span class='wiktku-cache-note'></span>").append(lastSavedEl));
startForm.append(footer);
// Moved off-screen rather than display:none. WikiEditor and the mobile editor measure
// #wpTextbox1 while the wizard is up, and a hidden ancestor reports zero, which they then keep.
// Off-screen keeps it laid out and measurable.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
if (d.senseGroups) SENSE_GROUPS = d.senseGroups;
// Headings for the page-level word lists, from the same table DefSideBoxes reads.
$.each(d.pageSubsections || [], function (_, sub) {
if (sub.kind !== "wordlist" || !steps[sub.id]) return;
steps[sub.id].header = sub.header;
steps[sub.id].name = decapitalize(sub.header);
});
if (d.wordlistTemplates && d.wordlistTemplates.length) WORDLIST_TEMPLATE = d.wordlistTemplates[0];
if (d.deverQuickPicks) DEVER_QUICK_PICKS = d.deverQuickPicks;
if (d.lekturChoices) LEKTUR_CHOICES = d.lekturChoices;
if (d.lekturFormChoices) LEKTUR_FORM_CHOICES = d.lekturFormChoices;
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
// Only the ~300 languages of ziman-core.json are merged in at load; the other ~3400 arrive
// through wiktkuMergeLanguages() the first time somebody searches for one of them. The pinned
// choices are kept aside so a second merge rebuilds the list instead of appending to it.
var wiktkuPinnedChoices = steps.lang.choices;
wiktkuMergeLanguages = function (zimanNames) {
var pinnedCodes = {};
$.each(wiktkuPinnedChoices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(zimanNames || {}, function (code, name) {
// A proto-language is never the language of an ordinary entry, but it is the whole point
// of a reconstruction page, so ns 108 keeps them in the list.
if (!isReconstruction && /-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// One Intl.Collator built once and reused for the whole sort, instead of localeCompare() rebuilding a collator on every pair -- same order, far faster over ~3700 entries.
var langCollator = new Intl.Collator(userLang);
extraChoices.sort(function (a, b) {
return langCollator.compare(a.sortKey, b.sortKey);
});
steps.lang.choices = wiktkuPinnedChoices.concat(extraChoices);
// The translation/dialect steps reference any language, so they follow the same list.
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
return steps.lang.choices;
};
wiktkuMergeLanguages(wiktkuLangs.names());
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data-main.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object, from data-sections.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// The data ships inside wikt.edit, so it is already parsed and in memory.
wiktkuProceedWithData(wiktkuEdit.data);
});
});
//</nowiki>
k5jc9nw43xpzpsts3y55c02kfmvvkhy
MediaWiki:Gadget-wikt.cite/etimon.js
8
1359266
6865536
6865401
2026-10-23T19:43:11Z
Wikihez
7344
Language fields: the translation adder's dropdown (names and codes searched) instead of a datalist
6865536
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.cite/etimon.js]]
*
* The builder behind {{etîmon}}: one call is read into steps of "kelîmeya mifteyî + kelîmeyên jêder",
* every source word carrying its own <guherîner>s and, when its page has no etymology of its own, an
* inline <etimon:…> chain of further steps.
*
* Keywords, modifiers, the metn= values and the named parameters all come from the "etimon" block of
* data-main.json, so a new one is added there rather than here. cite.openTemplateDialog() hands every
* {{etîmon}} call to openDialog(), which is what DefSideBoxes and the wizard both call.
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var langs = edit.langs;
var wt = edit.wt;
var data = edit.data.etimon || {};
var NAME = "etîmon";
// Every name the template answers to: a call written under one of its redirects still opens the builder.
var NAMES = ["etîmon", "etimon", "etymon"];
// <etimon:…> is spelled without "î": modifier names inside <> are ASCII only.
var INLINE_KEYS = ["etimon", "ety"];
function isEtimonName(name) {
return NAMES.indexOf($.trim(name || "").toLowerCase().replace(/^şablon:\s*/i, "")) !== -1;
}
// ---- the tables ---------------------------------------------------------------------------------
function keywords() {
return data.keywords || [];
}
function wordMods() {
return data.wordMods || [];
}
function keywordMods() {
return data.keywordMods || [];
}
// The entry for a key or for one of its documented aliases, so ":inh" is understood as ":mîr".
function defFor(defs, key) {
var found = null;
defs.forEach(function (def) {
if (found) {
return;
}
if (def.key === key || (def.aliases || []).indexOf(key) !== -1) {
found = def;
}
});
return found;
}
function isKeywordModName(key) {
return !!defFor(keywordMods(), key);
}
// ---- reading a call ----------------------------------------------------------------------------
// The ">" closing the "<" at start, skipping the nested ones an <etimon:…> or an <id:…> inside it has.
function matchAngle(text, start) {
var depth = 0;
for (var i = start; i < text.length; i++) {
var ch = text.charAt(i);
if (ch === "<") {
depth++;
} else if (ch === ">") {
depth--;
if (depth === 0) {
return i;
}
}
}
return -1;
}
// Splits on the "|" that separate parameters, and only those: a <ref:{{ş|R:X}}> carries pipes of its own.
function splitParams(text) {
var out = [];
var buf = "";
var braces = 0;
var angles = 0;
for (var i = 0; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === "{{" || two === "}}") {
braces += two === "{{" ? 1 : -1;
buf += two;
i++;
continue;
}
var ch = text.charAt(i);
if (ch === "<") {
angles++;
} else if (ch === ">") {
angles = Math.max(0, angles - 1);
}
if (ch === "|" && braces <= 0 && angles === 0) {
out.push(buf);
buf = "";
continue;
}
buf += ch;
}
out.push(buf);
return out;
}
function parseMod(inner) {
var at = inner.indexOf(":");
if (at === -1) {
return { key: $.trim(inner), flag: true };
}
return { key: $.trim(inner.slice(0, at)), value: inner.slice(at + 1) };
}
// "fa:خانه<tr:xâne><t:mal>" -> { head: "fa:خانه", mods: [...] }
function splitMods(text) {
var head = "";
var mods = [];
var i = 0;
while (i < text.length) {
if (text.charAt(i) === "<") {
var end = matchAngle(text, i);
if (end === -1) {
head += text.slice(i);
break;
}
mods.push(parseMod(text.slice(i + 1, end)));
i = end + 1;
continue;
}
head += text.charAt(i);
i++;
}
return { head: head, mods: mods };
}
function emptyWord() {
return { lang: "", word: "", mods: [], steps: [] };
}
function emptyStep() {
return { keyword: "", mods: [], words: [] };
}
function parseWord(text) {
var split = splitMods($.trim(text));
var word = emptyWord();
var head = split.head;
// A language code, or a language family ("ira:-"); anything else is the word itself.
var m = /^([a-zA-Z][a-zA-Z0-9._-]*):([\s\S]*)$/.exec(head);
if (m) {
word.lang = m[1];
head = m[2];
}
word.word = head;
split.mods.forEach(function (mo) {
if (INLINE_KEYS.indexOf(mo.key) !== -1) {
word.steps = parseChain(mo.value || "");
return;
}
word.mods.push(mo);
});
return word;
}
// The inside of an <etimon:…>: ":kelîmeyeke mifteyî" followed by each source word in its own <…>.
// A group named after a keyword modifier belongs to the keyword, which is how Modul:etymon reads it too.
function parseChain(text) {
var steps = [];
var i = 0;
// "<etimon:" already ends in a colon, so the first keyword of an inline chain carries none of
// its own. Both spellings are read, since the module accepts either.
var lead = /^([^:<]+)/.exec(text);
if (lead) {
steps.push($.extend(emptyStep(), { keyword: $.trim(lead[1]) }));
i = lead[1].length;
}
function current() {
if (!steps.length) {
steps.push(emptyStep());
}
return steps[steps.length - 1];
}
while (i < text.length) {
var ch = text.charAt(i);
if (ch === ":") {
var j = i + 1;
while (j < text.length && text.charAt(j) !== ":" && text.charAt(j) !== "<") {
j++;
}
var step = emptyStep();
step.keyword = $.trim(text.slice(i + 1, j));
steps.push(step);
i = j;
continue;
}
if (ch === "<") {
var end = matchAngle(text, i);
if (end === -1) {
break;
}
var inner = text.slice(i + 1, end);
i = end + 1;
var name = /^([^:>]+)/.exec(inner);
var step2 = current();
if (!step2.words.length && name && isKeywordModName($.trim(name[1]))) {
step2.mods.push(parseMod(inner));
} else {
step2.words.push(parseWord(inner));
}
continue;
}
i++;
}
return steps;
}
// A named parameter; a name cannot hold "<": that is a modifier, so "fa:x<t:a=b>" stays positional.
var NAMED = /^\s*([^|={}<>\n]+?)\s*=([\s\S]*)$/;
// One {{etîmon}} call -> the model the form edits, or null for anything else. A named parameter written
// under another name (text= for metn=, nocat= for katna=, see data's paramAliases) fills the form's own
// field, and spelling keeps the page's name for writing it back.
function parseCall(callWikitext) {
var call = wt.parseTemplateCalls(callWikitext || "").calls[0];
if (!call || !isEtimonName(call.name)) {
return null;
}
var model = { lang: "", steps: [], named: {}, namedOrder: [], spelling: {} };
var positional = 0;
var args = splitParams(call.params.join("|"));
// The names the call itself uses, so an alias is read as the form's own name only when that name is
// not also given: a call with both keeps both, as the template will complain about it.
var given = args.map(function (raw) {
var m = NAMED.exec(raw);
return m && m[1];
});
args.forEach(function (raw) {
var m = NAMED.exec(raw);
if (m) {
var key = (data.paramAliases || {})[m[1]];
if (!key || given.indexOf(key) !== -1 || model.spelling[key]) {
key = m[1];
} else {
model.spelling[key] = m[1];
}
if (model.named[key] === undefined) {
model.namedOrder.push(key);
}
model.named[key] = $.trim(m[2]);
return;
}
positional++;
var value = $.trim(raw);
if (positional === 1) {
model.lang = value;
return;
}
if (value.charAt(0) === ":") {
var split = splitMods(value);
var step = emptyStep();
step.keyword = $.trim(split.head.slice(1));
step.mods = split.mods;
model.steps.push(step);
return;
}
if (!model.steps.length) {
model.steps.push(emptyStep());
}
model.steps[model.steps.length - 1].words.push(parseWord(value));
});
return model;
}
// ---- writing it back ---------------------------------------------------------------------------
function joinMods(mods) {
return (mods || []).map(function (mo) {
return mo.flag ? "<" + mo.key + ">" : "<" + mo.key + ":" + (mo.value || "") + ">";
}).join("");
}
// A row nobody filled in: no word, no language, nothing hanging off it.
function wordIsEmpty(word) {
return !$.trim(word.word || "") && !$.trim(word.lang || "") &&
!(word.mods || []).length && !(word.steps || []).length;
}
function joinWord(word) {
var lang = $.trim(word.lang || "");
var out = (lang ? lang + ":" : "") + $.trim(word.word || "") + joinMods(word.mods);
if ((word.steps || []).length) {
out += "<etimon:" + joinChain(word.steps, true) + ">";
}
return out;
}
// inline: the chain sits inside "<etimon:…>", whose colon is the first keyword's own.
function joinChain(steps, inline) {
return (steps || []).map(function (step, index) {
var keyword = $.trim(step.keyword || "");
return (inline && index === 0 && keyword ? "" : ":") + keyword + joinMods(step.mods) +
(step.words || []).filter(function (word) {
return !wordIsEmpty(word);
}).map(function (word) {
return "<" + joinWord(word) + ">";
}).join("");
}).join("");
}
function buildCall(model) {
var parts = [NAME, $.trim(model.lang || "")];
(model.steps || []).forEach(function (step) {
var keyword = $.trim(step.keyword || "");
if (keyword || (step.mods || []).length) {
parts.push(":" + keyword + joinMods(step.mods));
}
(step.words || []).forEach(function (word) {
if (!wordIsEmpty(word)) {
parts.push(joinWord(word));
}
});
});
(model.namedOrder || []).forEach(function (key) {
var value = model.named[key];
if (value !== undefined && value !== null && value !== "") {
parts.push(((model.spelling || {})[key] || key) + "=" + value);
}
});
return "{{" + parts.join("|") + "}}";
}
// Which metn= choice a written value belongs to, and what is left over for that choice's own box:
// "3" belongs to the number choice, ":pal*" to the ":kod*" one.
function splitMetn(value) {
var current = $.trim(value || "");
var out = { choice: "", rest: "" };
if (!current) {
return out;
}
(data.metnChoices || []).forEach(function (choice) {
if (out.choice) {
return;
}
if (!choice.free) {
if (choice.value === current) {
out.choice = choice.value;
}
return;
}
if (choice.value === "1" && /^\d+$/.test(current)) {
out.choice = choice.value;
out.rest = current;
} else if (choice.value === ":*" && /^:.+\*$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1, -1);
} else if (choice.value === ":" && /^:[^*]+$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1);
}
});
return out;
}
// The inverse: what the dropdown and its box together mean as a metn= value.
function joinMetn(choiceValue, rest) {
var choice = null;
(data.metnChoices || []).forEach(function (c) {
if (c.value === choiceValue) {
choice = c;
}
});
if (!choice) {
return "";
}
if (!choice.free) {
return choice.value;
}
var typed = $.trim(rest || "");
if (!typed) {
return "";
}
if (choice.value === "1") {
return typed;
}
return ":" + typed + (choice.value === ":*" ? "*" : "");
}
function setNamed(model, key, value) {
if (model.namedOrder.indexOf(key) === -1) {
model.namedOrder.push(key);
}
model.named[key] = value;
}
// ---- the form ----------------------------------------------------------------------------------
// Chips for one list of modifiers, plus the ⊕ that adds another. The pills, the value box and the
// chips are the same widgetry a sense word row uses, so they look and behave the same here.
// Whether a modifier applies under the keyword it is being offered for: an "only" list in the data
// (aftype under :qf, maneid under :wergirtin) keeps it off every other keyword's picker.
function modApplies(def, keyword) {
if (!(def.only || []).length) {
return true;
}
var canonical = defFor(keywords(), keyword);
return def.only.indexOf(canonical ? canonical.key : keyword) !== -1;
}
function modifierEditor(mods, defs, onChange, keywordOf) {
var $wrap = $("<div class='dsb-etimon-mods'></div>");
var $chips = $("<div class='dsb-word-mods'></div>");
var $btn = $("<button type='button' class='dsb-mod-btn'>⊕</button>")
.attr("title", util.msg.addModifier);
var $picker = $("<div class='dsb-mod-picker'></div>").hide();
function label(def) {
return def ? def.key + " — " + def.desc : "";
}
// Same box for adding and for editing: a modifier with a fixed set of values gets a dropdown.
function openValueEditor(def, index) {
$picker.hide();
$wrap.children(".dsb-mod-val-wrap").remove();
var $box = $("<div class='dsb-mod-val-wrap'></div>");
var $label = $("<span class='dsb-mod-val-label'></span>").text(def.key);
var current = index === -1 ? "" : (mods[index].value || "");
var $field;
if ((def.values || []).length) {
$field = $("<select class='dsb-mod-val-input'></select>");
$field.append($("<option value=''></option>"));
def.values.forEach(function (value) {
$field.append($("<option></option>").val(value).text(value));
});
$field.val(current);
} else {
$field = $("<input type='text' class='dsb-mod-val-input'>")
.attr("size", 24).attr("placeholder", def.desc).val(current);
}
var $ok = $("<button type='button' class='dsb-mod-confirm'>✓</button>");
var $no = $("<button type='button' class='dsb-mod-cancel'>✕</button>");
function apply() {
var value = $.trim(String($field.val() || ""));
if (value) {
if (index === -1) {
mods.push({ key: def.key, value: value });
} else {
mods[index].value = value;
}
onChange();
render();
}
$box.remove();
}
$ok.on("click", apply);
$no.on("click", function () {
$box.remove();
});
$field.on("keydown", function (e) {
if (e.which === 13) {
e.preventDefault();
apply();
}
if (e.which === 27) {
$box.remove();
}
});
$box.append($label, $field, $ok, $no);
$wrap.append($box);
$field.focus();
}
function render() {
$chips.empty();
mods.forEach(function (mo, index) {
var def = defFor(defs, mo.key);
var $chip = $("<span class='dsb-word-mod-chip' role='button' tabindex='0'></span>")
.attr("title", def ? def.desc : mo.key)
.text(mo.flag ? mo.key : mo.key + ": " + (mo.value || ""));
var $x = $("<button type='button' class='dsb-word-mod-chip-remove'>×</button>")
.attr("title", util.msg.removeModifier);
$x.on("click", function (e) {
e.stopPropagation();
mods.splice(index, 1);
onChange();
render();
});
$chip.on("click", function () {
if (def && !mo.flag) {
openValueEditor(def, index);
}
});
$chip.append($x);
$chips.append($chip);
});
$chips.toggle(!!mods.length);
}
// Rebuilt per open rather than once: the keyword can change while the row is on screen, and with
// it which modifiers are on offer.
$btn.on("click", function (e) {
e.preventDefault();
var keyword = keywordOf ? keywordOf() : "";
$picker.empty();
defs.forEach(function (def) {
// <etimon:…> has the "+ Etîmona vê kelîmeyê" button of its own; as a pill it would be a
// box asking for a whole chain in one line.
if (INLINE_KEYS.indexOf(def.key) !== -1 || !modApplies(def, keyword)) {
return;
}
var $pill = $("<button type='button' class='dsb-mod-pill'></button>")
.text(def.key).attr("title", label(def)).data("mod-key", def.key);
$pill.on("click", function (ev) {
ev.preventDefault();
if (def.flag) {
mods.push({ key: def.key, flag: true });
$picker.hide();
onChange();
render();
return;
}
openValueEditor(def, -1);
});
$picker.append($pill);
});
var used = {};
mods.forEach(function (mo) {
used[mo.key] = true;
});
$picker.children(".dsb-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("dsb-mod-pill-used", isUsed);
});
$picker.toggle();
});
render();
$wrap.append($chips, $btn, $picker);
return $wrap;
}
function languageInput(value, onChange) {
var $input = $("<input type='text' class='dsb-input dsb-etimon-lang'>")
.attr("placeholder", "koda zimanî…")
.val(value || "");
$input.on("input change", function () {
onChange($.trim($input.val()));
});
langs.picker($input, function (code) {
onChange(code);
});
return $input;
}
// The whole form for one call. Structural changes (a step or a word added or dropped) rebuild it from
// the model, which every box writes into as it is typed in.
function buildForm(model, onResize) {
var $element = $("<div class='dsb-etimon'></div>");
var $preview = $("<code class='dsb-etimon-preview'></code>");
function changed() {
$preview.text(buildCall(model));
}
function structureChanged() {
render();
if (onResize) {
onResize();
}
}
// What a keyword constrains, in the two words that matter when picking one.
function keywordBadges(def) {
var out = [];
if (def && def.min > 1) {
out.push(def.min + "+ kelîme");
}
if (def && def.invisible) {
out.push("nayê çapkirin");
}
return out;
}
// An option carries the code, a short label and those badges. The description is long enough to
// be cut off mid-word in a select, so it goes in a line of its own under the control.
function keywordOptionText(def) {
return [":" + def.key].concat(def.short ? [def.short] : [])
.concat(keywordBadges(def)).join(" · ");
}
function keywordSelect(step) {
var $wrap = $("<div class='dsb-etimon-kw'></div>");
var $select = $("<select class='dsb-etimon-keyword'></select>");
$select.append($("<option value=''>— kelîmeya mifteyî —</option>"));
keywords().forEach(function (def) {
$select.append($("<option></option>").val(def.key).text(keywordOptionText(def)));
});
// A keyword written under an alias, or one the table does not know, keeps its own option.
var known = defFor(keywords(), step.keyword);
if (step.keyword && (!known || known.key !== step.keyword)) {
$select.append($("<option></option>").val(step.keyword)
.text(known ? keywordOptionText($.extend({}, known, { key: step.keyword }))
: ":" + step.keyword + " · (ne di belgeyê de)"));
}
$select.val(step.keyword);
var $desc = $("<div class='dsb-etimon-kw-desc dsb-etimon-hint'></div>");
function showDesc() {
var def = defFor(keywords(), $select.val());
$desc.text(def ? def.desc : "").toggle(!!(def && def.desc));
}
$select.on("change", function () {
step.keyword = $select.val();
showDesc();
changed();
structureChanged(); // the hints under it (how many words, what they mean) follow the keyword
});
$select.attr("data-placeholder", "Kelîmeya mifteyî");
$wrap.append(util.wrapNativeSelect($select), $desc);
showDesc();
// The searchable box reads the selection only when told to, so a keyword already chosen shows.
$select.trigger("wiktku:refresh");
return $wrap;
}
function wordRow(step, word, words, nested) {
var $li = $("<li class='dsb-etimon-word'></li>");
var $row = $("<div class='dsb-etimon-word-row'></div>");
var $lang = languageInput(word.lang, function (value) {
word.lang = value;
changed();
});
var $word = $("<input type='text' class='dsb-input dsb-etimon-word-input'>")
.attr("placeholder", "kelîme ( - = tenê navê zimên )")
.val(word.word || "");
$word.on("input", function () {
word.word = $word.val();
changed();
});
var $remove = $("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê kelîmeyê jê bibe");
$remove.on("click", function () {
words.splice(words.indexOf(word), 1);
if (!words.length && !nested) {
words.push(emptyWord());
}
changed();
structureChanged();
});
$row.append($lang, $word, modifierEditor(word.mods, wordMods(), changed, function () {
return step.keyword;
}), $remove);
$li.append($row);
// The way on to the next step when the source word's own page carries no {{etîmon}}.
if ((word.steps || []).length) {
var $nested = $("<div class='dsb-etimon-nested'></div>");
$nested.append($("<div class='dsb-etimon-nested-head'></div>").append(
$("<span class='dsb-adv-label'>Etîmona vê kelîmeyê</span>"),
$("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Etîmona vê kelîmeyê jê bibe")
.on("click", function () {
word.steps = [];
changed();
structureChanged();
})
));
renderSteps($nested, word.steps, true);
$li.append($nested);
} else {
$li.append($("<button type='button' class='dsb-etimon-add dsb-etimon-add-word'></button>")
.text("+ Etîmona vê kelîmeyê binivîse")
.attr("title", "Zincîrê li vir bidomîne, eger rûpela vê kelîmeyê tune be")
.on("click", function () {
word.steps = [$.extend(emptyStep(), { words: [emptyWord()] })];
changed();
structureChanged();
}));
}
return $li;
}
// A step is one array element, so its keyword, its words and anything nested under them move
// together. by is -1 or 1.
function moveStep(steps, step, by) {
var at = steps.indexOf(step);
var to = at + by;
if (at === -1 || to < 0 || to >= steps.length) {
return;
}
steps.splice(at, 1);
steps.splice(to, 0, step);
changed();
structureChanged();
}
function moveButton(steps, step, by) {
var at = steps.indexOf(step);
var up = by < 0;
return $("<button type='button' class='dsb-etimon-move'></button>")
.html(up ? "↑" : "↓")
.attr("title", up ? "Vê gavê jor bibe" : "Vê gavê jêr bibe")
.prop("disabled", up ? at === 0 : at === steps.length - 1)
.on("click", function () {
moveStep(steps, step, by);
});
}
function stepBlock(steps, step, nested) {
var $block = $("<div class='dsb-etimon-step'></div>");
var $head = $("<div class='dsb-etimon-step-head'></div>");
$head.append(keywordSelect(step));
// Inside an <etimon:…> the module reads every <…> group after the keyword as a term, so a
// keyword modifier written there would print as a source word. No picker, then.
if (!nested) {
$head.append(modifierEditor(step.mods, keywordMods(), changed, function () {
return step.keyword;
}));
}
if (steps.length > 1) {
$head.append(moveButton(steps, step, -1), moveButton(steps, step, 1));
}
if (steps.length > 1 || nested) {
$head.append($("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê gavê jê bibe")
.on("click", function () {
steps.splice(steps.indexOf(step), 1);
changed();
structureChanged();
}));
}
$block.append($head);
var def = defFor(keywords(), step.keyword);
if (def && def.min > 1) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Ev kelîmeya mifteyî bi kêmî " + def.min + " kelîmeyan dixwaze."));
}
if (def && def.invisible) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Di metnê de xuya nabe; tenê kategoriyan çêdike."));
}
if (!step.words.length) {
step.words.push(emptyWord());
}
var $words = $("<ul class='dsb-etimon-words'></ul>");
step.words.forEach(function (word) {
$words.append(wordRow(step, word, step.words, nested));
});
$block.append($words);
$block.append($("<button type='button' class='dsb-etimon-add'>+ Kelîme</button>")
.on("click", function () {
step.words.push(emptyWord());
structureChanged();
}));
return $block;
}
function renderSteps($host, steps, nested) {
if (!steps.length) {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
}
steps.forEach(function (step) {
$host.append(stepBlock(steps, step, nested));
});
$host.append($("<button type='button' class='dsb-etimon-add'></button>")
.text("+ Gaveke etîmolojiyê")
.attr("title", "Gaveke din a eynî bangkirinê")
.on("click", function () {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
structureChanged();
}));
}
// metn= picks how much of the chain is written out; the values that need a number or a language
// code ("3", ":pal") open a box of their own. The whole chain is the template's default, so it writes
// nothing; "no text" writes metnna=1.
function metnControl() {
var $wrap = $("<div class='dsb-etimon-metn'></div>");
var choices = (data.metnChoices || []).filter(function (choice) {
return choice.value !== "++";
});
var $select = $("<select class='dsb-etimon-metn-select'></select>");
$select.append($("<option value=''>++ — Tevahiya zincîrê</option>"));
$select.append($("<option value='na'>— metn nayê nîşandan —</option>"));
choices.forEach(function (choice) {
$select.append($("<option></option>").val(choice.value).text(choice.value + " — " + choice.desc));
});
var split = splitMetn(model.named.metn === "++" ? "" : model.named.metn);
$select.val(model.named.metnna ? "na" : split.choice);
var $free = $("<input type='text' class='dsb-input dsb-etimon-metn-free'>").val(split.rest);
function choiceFor(value) {
var found = null;
choices.forEach(function (choice) {
if (choice.value === value) {
found = choice;
}
});
return found;
}
function apply() {
var choice = choiceFor($select.val());
$free.toggle(!!(choice && choice.free));
if (choice && choice.free) {
$free.attr("placeholder", choice.value === "1" ? "hejmar…" : "koda zimanî…");
}
var none = $select.val() === "na";
setNamed(model, "metnna", none ? "1" : "");
setNamed(model, "metn", none ? "" : joinMetn($select.val(), $free.val()));
changed();
}
$select.on("change", apply);
$free.on("input", apply);
langs.picker($free, apply);
apply();
$select.attr("data-placeholder", "metn");
$wrap.append($("<span class='dsb-adv-label'>Metn</span>"), util.wrapNativeSelect($select), $free);
$select.trigger("wiktku:refresh");
return $wrap;
}
// The parameters marked "primary" in the data sit in the open next to Metn; the rest are behind
// "Parametreyên din".
function namedParams(primary) {
var $wrap = $("<div class='dsb-etimon-named'></div>");
(data.params || []).forEach(function (param) {
if (!!param.primary !== !!primary) {
return;
}
var value = model.named[param.key] || "";
var $row = $("<div class='dsb-etimon-param'></div>");
if (param.type === "flag") {
$row.addClass("dsb-etimon-param-flag");
var id = "dsb-etimon-" + param.key.replace(/[^A-Za-z0-9]/g, "");
var $box = $("<input type='checkbox'>").attr("id", id).prop("checked", value === "1");
$box.on("change", function () {
setNamed(model, param.key, $box.prop("checked") ? "1" : "");
changed();
});
$row.append($box, $("<label class='dsb-adv-label'></label>").attr("for", id)
.text(param.key + " — " + param.desc));
} else {
var $field;
if (param.type === "select") {
$field = $("<select class='dsb-etimon-param-select'></select>");
$field.append($("<option value=''></option>"));
(param.values || []).forEach(function (v) {
$field.append($("<option></option>").val(v).text(v));
});
$field.val(value);
} else {
$field = $("<input type='text' class='dsb-input'>").val(value);
}
$field.on("input change", function () {
setNamed(model, param.key, $.trim(String($field.val() || "")));
changed();
});
$row.append($("<label class='dsb-adv-label'></label>").text(param.key), $field,
$("<div class='dsb-tpl-help'></div>").text(param.desc));
}
$wrap.append($row);
});
return $wrap;
}
function render() {
$element.empty();
$element.append($("<div class='dsb-tpl-name'></div>").append(
$("<a></a>").attr("href", mw.util.getUrl("Şablon:" + NAME))
.attr("target", "_blank").text("{{" + NAME + "}}")
));
var $lang = $("<div class='dsb-etimon-pagelang'></div>");
$lang.append($("<span class='dsb-adv-label'>Zimanê rûpelê</span>"),
languageInput(model.lang, function (value) {
model.lang = value;
changed();
}));
// The two decisions that apply to the whole call sit together, above the steps: metn decides
// how much of everything under it is printed at all.
var $settings = $("<div class='dsb-etimon-settings'></div>");
$settings.append($lang, metnControl());
$element.append($settings);
var $steps = $("<div class='dsb-etimon-steps'></div>");
renderSteps($steps, model.steps, false);
$element.append($steps);
$element.append(namedParams(true));
var $more = $("<details class='dsb-etimon-more'></details>");
$more.append($("<summary>Parametreyên din</summary>"), namedParams(false));
$more.on("toggle", function () {
if (onResize) {
onResize();
}
});
$element.append($more);
$element.append($("<div class='dsb-etimon-preview-wrap'></div>").append($preview));
changed();
}
render();
return {
$element: $element,
values: function () {
return buildCall(model);
}
};
}
// A call for a page in this language with one empty step; the template writes the text and draws the
// tree by default.
function emptyModel(lang) {
return { lang: lang || "", steps: [$.extend(emptyStep(), { words: [emptyWord()] })],
named: {}, namedOrder: [], spelling: {} };
}
// ---- the dialog --------------------------------------------------------------------------------
var manager = null;
function windowManager() {
if (!manager) {
manager = new OO.ui.WindowManager();
$(document.body).append(manager.$element);
}
return manager;
}
// Opens one {{etîmon}} call in the builder. Resolves with the edited wikitext, "" for "Jê bibe", or
// null when it is cancelled -- the contract cite.openTemplateDialog() has, since it is called in its
// place. One at a time, for the same reason: a second window the manager does not track is dead.
var busy = false;
function openDialog(callWikitext) {
var done = $.Deferred();
if (busy) {
return done.resolve(null).promise();
}
var parsed = parseCall(callWikitext);
if (!parsed) {
return done.resolve(null).promise();
}
var model = parsed.steps.length ? parsed : emptyModel(parsed.lang);
busy = true;
done.always(function () {
busy = false;
});
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"]).then(function () {
var result = null;
var ParentDialog = OO.ui.ProcessDialog;
function EtimonDialog(config) {
ParentDialog.call(this, config);
}
OO.inheritClass(EtimonDialog, ParentDialog);
EtimonDialog.static.name = "etimonDialog";
EtimonDialog.static.title = "{{" + NAME + "}}";
EtimonDialog.static.actions = [
{ action: "save", label: "Biguhêre", flags: ["primary", "progressive"] },
{ action: "remove", label: "Jê bibe", flags: "destructive" },
{ label: util.msg.cancel, flags: "safe" }
];
EtimonDialog.prototype.initialize = function () {
var dialog = this;
ParentDialog.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.form = buildForm(model, function () {
dialog.updateSize();
});
this.panel.$element.append(this.form.$element);
this.$body.append(this.panel.$element);
};
EtimonDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
EtimonDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save" || action === "remove") {
return new OO.ui.Process(function () {
result = action === "remove" ? "" : dialog.form.values();
dialog.close();
});
}
return ParentDialog.prototype.getActionProcess.call(this, action);
};
// Marks the page while this is showing, so a floating box of ours (DefSideBoxes' queue) does
// not sit over a dialog the page's stacking order puts underneath it.
$(document.body).addClass("wikt-dialog-open");
done.always(function () {
$(document.body).removeClass("wikt-dialog-open");
});
var windows = windowManager();
var dialog = new EtimonDialog({ size: "large" });
windows.addWindows([dialog]);
var instance = windows.openWindow(dialog);
instance.closed.then(function () {
windows.clearWindows();
done.resolve(result);
});
instance.opening.fail(function () {
windows.clearWindows();
done.resolve(null);
});
}, function () {
done.resolve(null);
});
return done.promise();
}
module.exports = {
NAME: NAME,
splitMetn: splitMetn,
joinMetn: joinMetn,
isEtimonName: isEtimonName,
parseCall: parseCall,
buildCall: buildCall,
parseChain: parseChain,
joinChain: joinChain,
openDialog: openDialog
};
}());
// </nowiki>
2npmwd0drkhocc5qer8tx7o9byxybme
6865546
6865536
2026-10-23T19:58:05Z
Wikihez
7344
Template dialogs: move focus off the clicked chip before the modal hides the page (aria-hidden on a focused ancestor), return it on close
6865546
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.cite/etimon.js]]
*
* The builder behind {{etîmon}}: one call is read into steps of "kelîmeya mifteyî + kelîmeyên jêder",
* every source word carrying its own <guherîner>s and, when its page has no etymology of its own, an
* inline <etimon:…> chain of further steps.
*
* Keywords, modifiers, the metn= values and the named parameters all come from the "etimon" block of
* data-main.json, so a new one is added there rather than here. cite.openTemplateDialog() hands every
* {{etîmon}} call to openDialog(), which is what DefSideBoxes and the wizard both call.
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var langs = edit.langs;
var wt = edit.wt;
var data = edit.data.etimon || {};
var NAME = "etîmon";
// Every name the template answers to: a call written under one of its redirects still opens the builder.
var NAMES = ["etîmon", "etimon", "etymon"];
// <etimon:…> is spelled without "î": modifier names inside <> are ASCII only.
var INLINE_KEYS = ["etimon", "ety"];
function isEtimonName(name) {
return NAMES.indexOf($.trim(name || "").toLowerCase().replace(/^şablon:\s*/i, "")) !== -1;
}
// ---- the tables ---------------------------------------------------------------------------------
function keywords() {
return data.keywords || [];
}
function wordMods() {
return data.wordMods || [];
}
function keywordMods() {
return data.keywordMods || [];
}
// The entry for a key or for one of its documented aliases, so ":inh" is understood as ":mîr".
function defFor(defs, key) {
var found = null;
defs.forEach(function (def) {
if (found) {
return;
}
if (def.key === key || (def.aliases || []).indexOf(key) !== -1) {
found = def;
}
});
return found;
}
function isKeywordModName(key) {
return !!defFor(keywordMods(), key);
}
// ---- reading a call ----------------------------------------------------------------------------
// The ">" closing the "<" at start, skipping the nested ones an <etimon:…> or an <id:…> inside it has.
function matchAngle(text, start) {
var depth = 0;
for (var i = start; i < text.length; i++) {
var ch = text.charAt(i);
if (ch === "<") {
depth++;
} else if (ch === ">") {
depth--;
if (depth === 0) {
return i;
}
}
}
return -1;
}
// Splits on the "|" that separate parameters, and only those: a <ref:{{ş|R:X}}> carries pipes of its own.
function splitParams(text) {
var out = [];
var buf = "";
var braces = 0;
var angles = 0;
for (var i = 0; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === "{{" || two === "}}") {
braces += two === "{{" ? 1 : -1;
buf += two;
i++;
continue;
}
var ch = text.charAt(i);
if (ch === "<") {
angles++;
} else if (ch === ">") {
angles = Math.max(0, angles - 1);
}
if (ch === "|" && braces <= 0 && angles === 0) {
out.push(buf);
buf = "";
continue;
}
buf += ch;
}
out.push(buf);
return out;
}
function parseMod(inner) {
var at = inner.indexOf(":");
if (at === -1) {
return { key: $.trim(inner), flag: true };
}
return { key: $.trim(inner.slice(0, at)), value: inner.slice(at + 1) };
}
// "fa:خانه<tr:xâne><t:mal>" -> { head: "fa:خانه", mods: [...] }
function splitMods(text) {
var head = "";
var mods = [];
var i = 0;
while (i < text.length) {
if (text.charAt(i) === "<") {
var end = matchAngle(text, i);
if (end === -1) {
head += text.slice(i);
break;
}
mods.push(parseMod(text.slice(i + 1, end)));
i = end + 1;
continue;
}
head += text.charAt(i);
i++;
}
return { head: head, mods: mods };
}
function emptyWord() {
return { lang: "", word: "", mods: [], steps: [] };
}
function emptyStep() {
return { keyword: "", mods: [], words: [] };
}
function parseWord(text) {
var split = splitMods($.trim(text));
var word = emptyWord();
var head = split.head;
// A language code, or a language family ("ira:-"); anything else is the word itself.
var m = /^([a-zA-Z][a-zA-Z0-9._-]*):([\s\S]*)$/.exec(head);
if (m) {
word.lang = m[1];
head = m[2];
}
word.word = head;
split.mods.forEach(function (mo) {
if (INLINE_KEYS.indexOf(mo.key) !== -1) {
word.steps = parseChain(mo.value || "");
return;
}
word.mods.push(mo);
});
return word;
}
// The inside of an <etimon:…>: ":kelîmeyeke mifteyî" followed by each source word in its own <…>.
// A group named after a keyword modifier belongs to the keyword, which is how Modul:etymon reads it too.
function parseChain(text) {
var steps = [];
var i = 0;
// "<etimon:" already ends in a colon, so the first keyword of an inline chain carries none of
// its own. Both spellings are read, since the module accepts either.
var lead = /^([^:<]+)/.exec(text);
if (lead) {
steps.push($.extend(emptyStep(), { keyword: $.trim(lead[1]) }));
i = lead[1].length;
}
function current() {
if (!steps.length) {
steps.push(emptyStep());
}
return steps[steps.length - 1];
}
while (i < text.length) {
var ch = text.charAt(i);
if (ch === ":") {
var j = i + 1;
while (j < text.length && text.charAt(j) !== ":" && text.charAt(j) !== "<") {
j++;
}
var step = emptyStep();
step.keyword = $.trim(text.slice(i + 1, j));
steps.push(step);
i = j;
continue;
}
if (ch === "<") {
var end = matchAngle(text, i);
if (end === -1) {
break;
}
var inner = text.slice(i + 1, end);
i = end + 1;
var name = /^([^:>]+)/.exec(inner);
var step2 = current();
if (!step2.words.length && name && isKeywordModName($.trim(name[1]))) {
step2.mods.push(parseMod(inner));
} else {
step2.words.push(parseWord(inner));
}
continue;
}
i++;
}
return steps;
}
// A named parameter; a name cannot hold "<": that is a modifier, so "fa:x<t:a=b>" stays positional.
var NAMED = /^\s*([^|={}<>\n]+?)\s*=([\s\S]*)$/;
// One {{etîmon}} call -> the model the form edits, or null for anything else. A named parameter written
// under another name (text= for metn=, nocat= for katna=, see data's paramAliases) fills the form's own
// field, and spelling keeps the page's name for writing it back.
function parseCall(callWikitext) {
var call = wt.parseTemplateCalls(callWikitext || "").calls[0];
if (!call || !isEtimonName(call.name)) {
return null;
}
var model = { lang: "", steps: [], named: {}, namedOrder: [], spelling: {} };
var positional = 0;
var args = splitParams(call.params.join("|"));
// The names the call itself uses, so an alias is read as the form's own name only when that name is
// not also given: a call with both keeps both, as the template will complain about it.
var given = args.map(function (raw) {
var m = NAMED.exec(raw);
return m && m[1];
});
args.forEach(function (raw) {
var m = NAMED.exec(raw);
if (m) {
var key = (data.paramAliases || {})[m[1]];
if (!key || given.indexOf(key) !== -1 || model.spelling[key]) {
key = m[1];
} else {
model.spelling[key] = m[1];
}
if (model.named[key] === undefined) {
model.namedOrder.push(key);
}
model.named[key] = $.trim(m[2]);
return;
}
positional++;
var value = $.trim(raw);
if (positional === 1) {
model.lang = value;
return;
}
if (value.charAt(0) === ":") {
var split = splitMods(value);
var step = emptyStep();
step.keyword = $.trim(split.head.slice(1));
step.mods = split.mods;
model.steps.push(step);
return;
}
if (!model.steps.length) {
model.steps.push(emptyStep());
}
model.steps[model.steps.length - 1].words.push(parseWord(value));
});
return model;
}
// ---- writing it back ---------------------------------------------------------------------------
function joinMods(mods) {
return (mods || []).map(function (mo) {
return mo.flag ? "<" + mo.key + ">" : "<" + mo.key + ":" + (mo.value || "") + ">";
}).join("");
}
// A row nobody filled in: no word, no language, nothing hanging off it.
function wordIsEmpty(word) {
return !$.trim(word.word || "") && !$.trim(word.lang || "") &&
!(word.mods || []).length && !(word.steps || []).length;
}
function joinWord(word) {
var lang = $.trim(word.lang || "");
var out = (lang ? lang + ":" : "") + $.trim(word.word || "") + joinMods(word.mods);
if ((word.steps || []).length) {
out += "<etimon:" + joinChain(word.steps, true) + ">";
}
return out;
}
// inline: the chain sits inside "<etimon:…>", whose colon is the first keyword's own.
function joinChain(steps, inline) {
return (steps || []).map(function (step, index) {
var keyword = $.trim(step.keyword || "");
return (inline && index === 0 && keyword ? "" : ":") + keyword + joinMods(step.mods) +
(step.words || []).filter(function (word) {
return !wordIsEmpty(word);
}).map(function (word) {
return "<" + joinWord(word) + ">";
}).join("");
}).join("");
}
function buildCall(model) {
var parts = [NAME, $.trim(model.lang || "")];
(model.steps || []).forEach(function (step) {
var keyword = $.trim(step.keyword || "");
if (keyword || (step.mods || []).length) {
parts.push(":" + keyword + joinMods(step.mods));
}
(step.words || []).forEach(function (word) {
if (!wordIsEmpty(word)) {
parts.push(joinWord(word));
}
});
});
(model.namedOrder || []).forEach(function (key) {
var value = model.named[key];
if (value !== undefined && value !== null && value !== "") {
parts.push(((model.spelling || {})[key] || key) + "=" + value);
}
});
return "{{" + parts.join("|") + "}}";
}
// Which metn= choice a written value belongs to, and what is left over for that choice's own box:
// "3" belongs to the number choice, ":pal*" to the ":kod*" one.
function splitMetn(value) {
var current = $.trim(value || "");
var out = { choice: "", rest: "" };
if (!current) {
return out;
}
(data.metnChoices || []).forEach(function (choice) {
if (out.choice) {
return;
}
if (!choice.free) {
if (choice.value === current) {
out.choice = choice.value;
}
return;
}
if (choice.value === "1" && /^\d+$/.test(current)) {
out.choice = choice.value;
out.rest = current;
} else if (choice.value === ":*" && /^:.+\*$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1, -1);
} else if (choice.value === ":" && /^:[^*]+$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1);
}
});
return out;
}
// The inverse: what the dropdown and its box together mean as a metn= value.
function joinMetn(choiceValue, rest) {
var choice = null;
(data.metnChoices || []).forEach(function (c) {
if (c.value === choiceValue) {
choice = c;
}
});
if (!choice) {
return "";
}
if (!choice.free) {
return choice.value;
}
var typed = $.trim(rest || "");
if (!typed) {
return "";
}
if (choice.value === "1") {
return typed;
}
return ":" + typed + (choice.value === ":*" ? "*" : "");
}
function setNamed(model, key, value) {
if (model.namedOrder.indexOf(key) === -1) {
model.namedOrder.push(key);
}
model.named[key] = value;
}
// ---- the form ----------------------------------------------------------------------------------
// Chips for one list of modifiers, plus the ⊕ that adds another. The pills, the value box and the
// chips are the same widgetry a sense word row uses, so they look and behave the same here.
// Whether a modifier applies under the keyword it is being offered for: an "only" list in the data
// (aftype under :qf, maneid under :wergirtin) keeps it off every other keyword's picker.
function modApplies(def, keyword) {
if (!(def.only || []).length) {
return true;
}
var canonical = defFor(keywords(), keyword);
return def.only.indexOf(canonical ? canonical.key : keyword) !== -1;
}
function modifierEditor(mods, defs, onChange, keywordOf) {
var $wrap = $("<div class='dsb-etimon-mods'></div>");
var $chips = $("<div class='dsb-word-mods'></div>");
var $btn = $("<button type='button' class='dsb-mod-btn'>⊕</button>")
.attr("title", util.msg.addModifier);
var $picker = $("<div class='dsb-mod-picker'></div>").hide();
function label(def) {
return def ? def.key + " — " + def.desc : "";
}
// Same box for adding and for editing: a modifier with a fixed set of values gets a dropdown.
function openValueEditor(def, index) {
$picker.hide();
$wrap.children(".dsb-mod-val-wrap").remove();
var $box = $("<div class='dsb-mod-val-wrap'></div>");
var $label = $("<span class='dsb-mod-val-label'></span>").text(def.key);
var current = index === -1 ? "" : (mods[index].value || "");
var $field;
if ((def.values || []).length) {
$field = $("<select class='dsb-mod-val-input'></select>");
$field.append($("<option value=''></option>"));
def.values.forEach(function (value) {
$field.append($("<option></option>").val(value).text(value));
});
$field.val(current);
} else {
$field = $("<input type='text' class='dsb-mod-val-input'>")
.attr("size", 24).attr("placeholder", def.desc).val(current);
}
var $ok = $("<button type='button' class='dsb-mod-confirm'>✓</button>");
var $no = $("<button type='button' class='dsb-mod-cancel'>✕</button>");
function apply() {
var value = $.trim(String($field.val() || ""));
if (value) {
if (index === -1) {
mods.push({ key: def.key, value: value });
} else {
mods[index].value = value;
}
onChange();
render();
}
$box.remove();
}
$ok.on("click", apply);
$no.on("click", function () {
$box.remove();
});
$field.on("keydown", function (e) {
if (e.which === 13) {
e.preventDefault();
apply();
}
if (e.which === 27) {
$box.remove();
}
});
$box.append($label, $field, $ok, $no);
$wrap.append($box);
$field.focus();
}
function render() {
$chips.empty();
mods.forEach(function (mo, index) {
var def = defFor(defs, mo.key);
var $chip = $("<span class='dsb-word-mod-chip' role='button' tabindex='0'></span>")
.attr("title", def ? def.desc : mo.key)
.text(mo.flag ? mo.key : mo.key + ": " + (mo.value || ""));
var $x = $("<button type='button' class='dsb-word-mod-chip-remove'>×</button>")
.attr("title", util.msg.removeModifier);
$x.on("click", function (e) {
e.stopPropagation();
mods.splice(index, 1);
onChange();
render();
});
$chip.on("click", function () {
if (def && !mo.flag) {
openValueEditor(def, index);
}
});
$chip.append($x);
$chips.append($chip);
});
$chips.toggle(!!mods.length);
}
// Rebuilt per open rather than once: the keyword can change while the row is on screen, and with
// it which modifiers are on offer.
$btn.on("click", function (e) {
e.preventDefault();
var keyword = keywordOf ? keywordOf() : "";
$picker.empty();
defs.forEach(function (def) {
// <etimon:…> has the "+ Etîmona vê kelîmeyê" button of its own; as a pill it would be a
// box asking for a whole chain in one line.
if (INLINE_KEYS.indexOf(def.key) !== -1 || !modApplies(def, keyword)) {
return;
}
var $pill = $("<button type='button' class='dsb-mod-pill'></button>")
.text(def.key).attr("title", label(def)).data("mod-key", def.key);
$pill.on("click", function (ev) {
ev.preventDefault();
if (def.flag) {
mods.push({ key: def.key, flag: true });
$picker.hide();
onChange();
render();
return;
}
openValueEditor(def, -1);
});
$picker.append($pill);
});
var used = {};
mods.forEach(function (mo) {
used[mo.key] = true;
});
$picker.children(".dsb-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("dsb-mod-pill-used", isUsed);
});
$picker.toggle();
});
render();
$wrap.append($chips, $btn, $picker);
return $wrap;
}
function languageInput(value, onChange) {
var $input = $("<input type='text' class='dsb-input dsb-etimon-lang'>")
.attr("placeholder", "koda zimanî…")
.val(value || "");
$input.on("input change", function () {
onChange($.trim($input.val()));
});
langs.picker($input, function (code) {
onChange(code);
});
return $input;
}
// The whole form for one call. Structural changes (a step or a word added or dropped) rebuild it from
// the model, which every box writes into as it is typed in.
function buildForm(model, onResize) {
var $element = $("<div class='dsb-etimon'></div>");
var $preview = $("<code class='dsb-etimon-preview'></code>");
function changed() {
$preview.text(buildCall(model));
}
function structureChanged() {
render();
if (onResize) {
onResize();
}
}
// What a keyword constrains, in the two words that matter when picking one.
function keywordBadges(def) {
var out = [];
if (def && def.min > 1) {
out.push(def.min + "+ kelîme");
}
if (def && def.invisible) {
out.push("nayê çapkirin");
}
return out;
}
// An option carries the code, a short label and those badges. The description is long enough to
// be cut off mid-word in a select, so it goes in a line of its own under the control.
function keywordOptionText(def) {
return [":" + def.key].concat(def.short ? [def.short] : [])
.concat(keywordBadges(def)).join(" · ");
}
function keywordSelect(step) {
var $wrap = $("<div class='dsb-etimon-kw'></div>");
var $select = $("<select class='dsb-etimon-keyword'></select>");
$select.append($("<option value=''>— kelîmeya mifteyî —</option>"));
keywords().forEach(function (def) {
$select.append($("<option></option>").val(def.key).text(keywordOptionText(def)));
});
// A keyword written under an alias, or one the table does not know, keeps its own option.
var known = defFor(keywords(), step.keyword);
if (step.keyword && (!known || known.key !== step.keyword)) {
$select.append($("<option></option>").val(step.keyword)
.text(known ? keywordOptionText($.extend({}, known, { key: step.keyword }))
: ":" + step.keyword + " · (ne di belgeyê de)"));
}
$select.val(step.keyword);
var $desc = $("<div class='dsb-etimon-kw-desc dsb-etimon-hint'></div>");
function showDesc() {
var def = defFor(keywords(), $select.val());
$desc.text(def ? def.desc : "").toggle(!!(def && def.desc));
}
$select.on("change", function () {
step.keyword = $select.val();
showDesc();
changed();
structureChanged(); // the hints under it (how many words, what they mean) follow the keyword
});
$select.attr("data-placeholder", "Kelîmeya mifteyî");
$wrap.append(util.wrapNativeSelect($select), $desc);
showDesc();
// The searchable box reads the selection only when told to, so a keyword already chosen shows.
$select.trigger("wiktku:refresh");
return $wrap;
}
function wordRow(step, word, words, nested) {
var $li = $("<li class='dsb-etimon-word'></li>");
var $row = $("<div class='dsb-etimon-word-row'></div>");
var $lang = languageInput(word.lang, function (value) {
word.lang = value;
changed();
});
var $word = $("<input type='text' class='dsb-input dsb-etimon-word-input'>")
.attr("placeholder", "kelîme ( - = tenê navê zimên )")
.val(word.word || "");
$word.on("input", function () {
word.word = $word.val();
changed();
});
var $remove = $("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê kelîmeyê jê bibe");
$remove.on("click", function () {
words.splice(words.indexOf(word), 1);
if (!words.length && !nested) {
words.push(emptyWord());
}
changed();
structureChanged();
});
$row.append($lang, $word, modifierEditor(word.mods, wordMods(), changed, function () {
return step.keyword;
}), $remove);
$li.append($row);
// The way on to the next step when the source word's own page carries no {{etîmon}}.
if ((word.steps || []).length) {
var $nested = $("<div class='dsb-etimon-nested'></div>");
$nested.append($("<div class='dsb-etimon-nested-head'></div>").append(
$("<span class='dsb-adv-label'>Etîmona vê kelîmeyê</span>"),
$("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Etîmona vê kelîmeyê jê bibe")
.on("click", function () {
word.steps = [];
changed();
structureChanged();
})
));
renderSteps($nested, word.steps, true);
$li.append($nested);
} else {
$li.append($("<button type='button' class='dsb-etimon-add dsb-etimon-add-word'></button>")
.text("+ Etîmona vê kelîmeyê binivîse")
.attr("title", "Zincîrê li vir bidomîne, eger rûpela vê kelîmeyê tune be")
.on("click", function () {
word.steps = [$.extend(emptyStep(), { words: [emptyWord()] })];
changed();
structureChanged();
}));
}
return $li;
}
// A step is one array element, so its keyword, its words and anything nested under them move
// together. by is -1 or 1.
function moveStep(steps, step, by) {
var at = steps.indexOf(step);
var to = at + by;
if (at === -1 || to < 0 || to >= steps.length) {
return;
}
steps.splice(at, 1);
steps.splice(to, 0, step);
changed();
structureChanged();
}
function moveButton(steps, step, by) {
var at = steps.indexOf(step);
var up = by < 0;
return $("<button type='button' class='dsb-etimon-move'></button>")
.html(up ? "↑" : "↓")
.attr("title", up ? "Vê gavê jor bibe" : "Vê gavê jêr bibe")
.prop("disabled", up ? at === 0 : at === steps.length - 1)
.on("click", function () {
moveStep(steps, step, by);
});
}
function stepBlock(steps, step, nested) {
var $block = $("<div class='dsb-etimon-step'></div>");
var $head = $("<div class='dsb-etimon-step-head'></div>");
$head.append(keywordSelect(step));
// Inside an <etimon:…> the module reads every <…> group after the keyword as a term, so a
// keyword modifier written there would print as a source word. No picker, then.
if (!nested) {
$head.append(modifierEditor(step.mods, keywordMods(), changed, function () {
return step.keyword;
}));
}
if (steps.length > 1) {
$head.append(moveButton(steps, step, -1), moveButton(steps, step, 1));
}
if (steps.length > 1 || nested) {
$head.append($("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê gavê jê bibe")
.on("click", function () {
steps.splice(steps.indexOf(step), 1);
changed();
structureChanged();
}));
}
$block.append($head);
var def = defFor(keywords(), step.keyword);
if (def && def.min > 1) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Ev kelîmeya mifteyî bi kêmî " + def.min + " kelîmeyan dixwaze."));
}
if (def && def.invisible) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Di metnê de xuya nabe; tenê kategoriyan çêdike."));
}
if (!step.words.length) {
step.words.push(emptyWord());
}
var $words = $("<ul class='dsb-etimon-words'></ul>");
step.words.forEach(function (word) {
$words.append(wordRow(step, word, step.words, nested));
});
$block.append($words);
$block.append($("<button type='button' class='dsb-etimon-add'>+ Kelîme</button>")
.on("click", function () {
step.words.push(emptyWord());
structureChanged();
}));
return $block;
}
function renderSteps($host, steps, nested) {
if (!steps.length) {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
}
steps.forEach(function (step) {
$host.append(stepBlock(steps, step, nested));
});
$host.append($("<button type='button' class='dsb-etimon-add'></button>")
.text("+ Gaveke etîmolojiyê")
.attr("title", "Gaveke din a eynî bangkirinê")
.on("click", function () {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
structureChanged();
}));
}
// metn= picks how much of the chain is written out; the values that need a number or a language
// code ("3", ":pal") open a box of their own. The whole chain is the template's default, so it writes
// nothing; "no text" writes metnna=1.
function metnControl() {
var $wrap = $("<div class='dsb-etimon-metn'></div>");
var choices = (data.metnChoices || []).filter(function (choice) {
return choice.value !== "++";
});
var $select = $("<select class='dsb-etimon-metn-select'></select>");
$select.append($("<option value=''>++ — Tevahiya zincîrê</option>"));
$select.append($("<option value='na'>— metn nayê nîşandan —</option>"));
choices.forEach(function (choice) {
$select.append($("<option></option>").val(choice.value).text(choice.value + " — " + choice.desc));
});
var split = splitMetn(model.named.metn === "++" ? "" : model.named.metn);
$select.val(model.named.metnna ? "na" : split.choice);
var $free = $("<input type='text' class='dsb-input dsb-etimon-metn-free'>").val(split.rest);
function choiceFor(value) {
var found = null;
choices.forEach(function (choice) {
if (choice.value === value) {
found = choice;
}
});
return found;
}
function apply() {
var choice = choiceFor($select.val());
$free.toggle(!!(choice && choice.free));
if (choice && choice.free) {
$free.attr("placeholder", choice.value === "1" ? "hejmar…" : "koda zimanî…");
}
var none = $select.val() === "na";
setNamed(model, "metnna", none ? "1" : "");
setNamed(model, "metn", none ? "" : joinMetn($select.val(), $free.val()));
changed();
}
$select.on("change", apply);
$free.on("input", apply);
langs.picker($free, apply);
apply();
$select.attr("data-placeholder", "metn");
$wrap.append($("<span class='dsb-adv-label'>Metn</span>"), util.wrapNativeSelect($select), $free);
$select.trigger("wiktku:refresh");
return $wrap;
}
// The parameters marked "primary" in the data sit in the open next to Metn; the rest are behind
// "Parametreyên din".
function namedParams(primary) {
var $wrap = $("<div class='dsb-etimon-named'></div>");
(data.params || []).forEach(function (param) {
if (!!param.primary !== !!primary) {
return;
}
var value = model.named[param.key] || "";
var $row = $("<div class='dsb-etimon-param'></div>");
if (param.type === "flag") {
$row.addClass("dsb-etimon-param-flag");
var id = "dsb-etimon-" + param.key.replace(/[^A-Za-z0-9]/g, "");
var $box = $("<input type='checkbox'>").attr("id", id).prop("checked", value === "1");
$box.on("change", function () {
setNamed(model, param.key, $box.prop("checked") ? "1" : "");
changed();
});
$row.append($box, $("<label class='dsb-adv-label'></label>").attr("for", id)
.text(param.key + " — " + param.desc));
} else {
var $field;
if (param.type === "select") {
$field = $("<select class='dsb-etimon-param-select'></select>");
$field.append($("<option value=''></option>"));
(param.values || []).forEach(function (v) {
$field.append($("<option></option>").val(v).text(v));
});
$field.val(value);
} else {
$field = $("<input type='text' class='dsb-input'>").val(value);
}
$field.on("input change", function () {
setNamed(model, param.key, $.trim(String($field.val() || "")));
changed();
});
$row.append($("<label class='dsb-adv-label'></label>").text(param.key), $field,
$("<div class='dsb-tpl-help'></div>").text(param.desc));
}
$wrap.append($row);
});
return $wrap;
}
function render() {
$element.empty();
$element.append($("<div class='dsb-tpl-name'></div>").append(
$("<a></a>").attr("href", mw.util.getUrl("Şablon:" + NAME))
.attr("target", "_blank").text("{{" + NAME + "}}")
));
var $lang = $("<div class='dsb-etimon-pagelang'></div>");
$lang.append($("<span class='dsb-adv-label'>Zimanê rûpelê</span>"),
languageInput(model.lang, function (value) {
model.lang = value;
changed();
}));
// The two decisions that apply to the whole call sit together, above the steps: metn decides
// how much of everything under it is printed at all.
var $settings = $("<div class='dsb-etimon-settings'></div>");
$settings.append($lang, metnControl());
$element.append($settings);
var $steps = $("<div class='dsb-etimon-steps'></div>");
renderSteps($steps, model.steps, false);
$element.append($steps);
$element.append(namedParams(true));
var $more = $("<details class='dsb-etimon-more'></details>");
$more.append($("<summary>Parametreyên din</summary>"), namedParams(false));
$more.on("toggle", function () {
if (onResize) {
onResize();
}
});
$element.append($more);
$element.append($("<div class='dsb-etimon-preview-wrap'></div>").append($preview));
changed();
}
render();
return {
$element: $element,
values: function () {
return buildCall(model);
}
};
}
// A call for a page in this language with one empty step; the template writes the text and draws the
// tree by default.
function emptyModel(lang) {
return { lang: lang || "", steps: [$.extend(emptyStep(), { words: [emptyWord()] })],
named: {}, namedOrder: [], spelling: {} };
}
// ---- the dialog --------------------------------------------------------------------------------
var manager = null;
function windowManager() {
if (!manager) {
manager = new OO.ui.WindowManager();
$(document.body).append(manager.$element);
}
return manager;
}
// Opens one {{etîmon}} call in the builder. Resolves with the edited wikitext, "" for "Jê bibe", or
// null when it is cancelled -- the contract cite.openTemplateDialog() has, since it is called in its
// place. One at a time, for the same reason: a second window the manager does not track is dead.
var busy = false;
function openDialog(callWikitext) {
var done = $.Deferred();
if (busy) {
return done.resolve(null).promise();
}
var parsed = parseCall(callWikitext);
if (!parsed) {
return done.resolve(null).promise();
}
var model = parsed.steps.length ? parsed : emptyModel(parsed.lang);
busy = true;
done.always(function () {
busy = false;
});
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"]).then(function () {
var result = null;
var ParentDialog = OO.ui.ProcessDialog;
function EtimonDialog(config) {
ParentDialog.call(this, config);
}
OO.inheritClass(EtimonDialog, ParentDialog);
EtimonDialog.static.name = "etimonDialog";
EtimonDialog.static.title = "{{" + NAME + "}}";
EtimonDialog.static.actions = [
{ action: "save", label: "Biguhêre", flags: ["primary", "progressive"] },
{ action: "remove", label: "Jê bibe", flags: "destructive" },
{ label: util.msg.cancel, flags: "safe" }
];
EtimonDialog.prototype.initialize = function () {
var dialog = this;
ParentDialog.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.form = buildForm(model, function () {
dialog.updateSize();
});
this.panel.$element.append(this.form.$element);
this.$body.append(this.panel.$element);
};
EtimonDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
EtimonDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save" || action === "remove") {
return new OO.ui.Process(function () {
result = action === "remove" ? "" : dialog.form.values();
dialog.close();
});
}
return ParentDialog.prototype.getActionProcess.call(this, action);
};
// Marks the page while this is showing, so a floating box of ours (DefSideBoxes' queue) does
// not sit over a dialog the page's stacking order puts underneath it.
$(document.body).addClass("wikt-dialog-open");
done.always(function () {
$(document.body).removeClass("wikt-dialog-open");
});
var windows = windowManager();
var dialog = new EtimonDialog({ size: "large" });
windows.addWindows([dialog]);
// The modal hides the page from screen readers, which is refused while the clicked chip still
// has focus; so the focus leaves it first, and OOUI hands it back when the dialog closes.
var $opener = $(document.activeElement);
$opener.trigger("blur");
var instance = windows.openWindow(dialog, { $returnFocusTo: $opener });
instance.closed.then(function () {
windows.clearWindows();
done.resolve(result);
});
instance.opening.fail(function () {
windows.clearWindows();
done.resolve(null);
});
}, function () {
done.resolve(null);
});
return done.promise();
}
module.exports = {
NAME: NAME,
splitMetn: splitMetn,
joinMetn: joinMetn,
isEtimonName: isEtimonName,
parseCall: parseCall,
buildCall: buildCall,
parseChain: parseChain,
joinChain: joinChain,
openDialog: openDialog
};
}());
// </nowiki>
k6p54exj078px3l4q0q4wi6owwxc13t
6865573
6865546
2026-10-23T20:36:07Z
Wikihez
7344
Etimon form: select values may carry a label; no empty help line
6865573
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.cite/etimon.js]]
*
* The builder behind {{etîmon}}: one call is read into steps of "kelîmeya mifteyî + kelîmeyên jêder",
* every source word carrying its own <guherîner>s and, when its page has no etymology of its own, an
* inline <etimon:…> chain of further steps.
*
* Keywords, modifiers, the metn= values and the named parameters all come from the "etimon" block of
* data-main.json, so a new one is added there rather than here. cite.openTemplateDialog() hands every
* {{etîmon}} call to openDialog(), which is what DefSideBoxes and the wizard both call.
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var langs = edit.langs;
var wt = edit.wt;
var data = edit.data.etimon || {};
var NAME = "etîmon";
// Every name the template answers to: a call written under one of its redirects still opens the builder.
var NAMES = ["etîmon", "etimon", "etymon"];
// <etimon:…> is spelled without "î": modifier names inside <> are ASCII only.
var INLINE_KEYS = ["etimon", "ety"];
function isEtimonName(name) {
return NAMES.indexOf($.trim(name || "").toLowerCase().replace(/^şablon:\s*/i, "")) !== -1;
}
// ---- the tables ---------------------------------------------------------------------------------
function keywords() {
return data.keywords || [];
}
function wordMods() {
return data.wordMods || [];
}
function keywordMods() {
return data.keywordMods || [];
}
// The entry for a key or for one of its documented aliases, so ":inh" is understood as ":mîr".
function defFor(defs, key) {
var found = null;
defs.forEach(function (def) {
if (found) {
return;
}
if (def.key === key || (def.aliases || []).indexOf(key) !== -1) {
found = def;
}
});
return found;
}
function isKeywordModName(key) {
return !!defFor(keywordMods(), key);
}
// ---- reading a call ----------------------------------------------------------------------------
// The ">" closing the "<" at start, skipping the nested ones an <etimon:…> or an <id:…> inside it has.
function matchAngle(text, start) {
var depth = 0;
for (var i = start; i < text.length; i++) {
var ch = text.charAt(i);
if (ch === "<") {
depth++;
} else if (ch === ">") {
depth--;
if (depth === 0) {
return i;
}
}
}
return -1;
}
// Splits on the "|" that separate parameters, and only those: a <ref:{{ş|R:X}}> carries pipes of its own.
function splitParams(text) {
var out = [];
var buf = "";
var braces = 0;
var angles = 0;
for (var i = 0; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === "{{" || two === "}}") {
braces += two === "{{" ? 1 : -1;
buf += two;
i++;
continue;
}
var ch = text.charAt(i);
if (ch === "<") {
angles++;
} else if (ch === ">") {
angles = Math.max(0, angles - 1);
}
if (ch === "|" && braces <= 0 && angles === 0) {
out.push(buf);
buf = "";
continue;
}
buf += ch;
}
out.push(buf);
return out;
}
function parseMod(inner) {
var at = inner.indexOf(":");
if (at === -1) {
return { key: $.trim(inner), flag: true };
}
return { key: $.trim(inner.slice(0, at)), value: inner.slice(at + 1) };
}
// "fa:خانه<tr:xâne><t:mal>" -> { head: "fa:خانه", mods: [...] }
function splitMods(text) {
var head = "";
var mods = [];
var i = 0;
while (i < text.length) {
if (text.charAt(i) === "<") {
var end = matchAngle(text, i);
if (end === -1) {
head += text.slice(i);
break;
}
mods.push(parseMod(text.slice(i + 1, end)));
i = end + 1;
continue;
}
head += text.charAt(i);
i++;
}
return { head: head, mods: mods };
}
function emptyWord() {
return { lang: "", word: "", mods: [], steps: [] };
}
function emptyStep() {
return { keyword: "", mods: [], words: [] };
}
function parseWord(text) {
var split = splitMods($.trim(text));
var word = emptyWord();
var head = split.head;
// A language code, or a language family ("ira:-"); anything else is the word itself.
var m = /^([a-zA-Z][a-zA-Z0-9._-]*):([\s\S]*)$/.exec(head);
if (m) {
word.lang = m[1];
head = m[2];
}
word.word = head;
split.mods.forEach(function (mo) {
if (INLINE_KEYS.indexOf(mo.key) !== -1) {
word.steps = parseChain(mo.value || "");
return;
}
word.mods.push(mo);
});
return word;
}
// The inside of an <etimon:…>: ":kelîmeyeke mifteyî" followed by each source word in its own <…>.
// A group named after a keyword modifier belongs to the keyword, which is how Modul:etymon reads it too.
function parseChain(text) {
var steps = [];
var i = 0;
// "<etimon:" already ends in a colon, so the first keyword of an inline chain carries none of
// its own. Both spellings are read, since the module accepts either.
var lead = /^([^:<]+)/.exec(text);
if (lead) {
steps.push($.extend(emptyStep(), { keyword: $.trim(lead[1]) }));
i = lead[1].length;
}
function current() {
if (!steps.length) {
steps.push(emptyStep());
}
return steps[steps.length - 1];
}
while (i < text.length) {
var ch = text.charAt(i);
if (ch === ":") {
var j = i + 1;
while (j < text.length && text.charAt(j) !== ":" && text.charAt(j) !== "<") {
j++;
}
var step = emptyStep();
step.keyword = $.trim(text.slice(i + 1, j));
steps.push(step);
i = j;
continue;
}
if (ch === "<") {
var end = matchAngle(text, i);
if (end === -1) {
break;
}
var inner = text.slice(i + 1, end);
i = end + 1;
var name = /^([^:>]+)/.exec(inner);
var step2 = current();
if (!step2.words.length && name && isKeywordModName($.trim(name[1]))) {
step2.mods.push(parseMod(inner));
} else {
step2.words.push(parseWord(inner));
}
continue;
}
i++;
}
return steps;
}
// A named parameter; a name cannot hold "<": that is a modifier, so "fa:x<t:a=b>" stays positional.
var NAMED = /^\s*([^|={}<>\n]+?)\s*=([\s\S]*)$/;
// One {{etîmon}} call -> the model the form edits, or null for anything else. A named parameter written
// under another name (text= for metn=, nocat= for katna=, see data's paramAliases) fills the form's own
// field, and spelling keeps the page's name for writing it back.
function parseCall(callWikitext) {
var call = wt.parseTemplateCalls(callWikitext || "").calls[0];
if (!call || !isEtimonName(call.name)) {
return null;
}
var model = { lang: "", steps: [], named: {}, namedOrder: [], spelling: {} };
var positional = 0;
var args = splitParams(call.params.join("|"));
// The names the call itself uses, so an alias is read as the form's own name only when that name is
// not also given: a call with both keeps both, as the template will complain about it.
var given = args.map(function (raw) {
var m = NAMED.exec(raw);
return m && m[1];
});
args.forEach(function (raw) {
var m = NAMED.exec(raw);
if (m) {
var key = (data.paramAliases || {})[m[1]];
if (!key || given.indexOf(key) !== -1 || model.spelling[key]) {
key = m[1];
} else {
model.spelling[key] = m[1];
}
if (model.named[key] === undefined) {
model.namedOrder.push(key);
}
model.named[key] = $.trim(m[2]);
return;
}
positional++;
var value = $.trim(raw);
if (positional === 1) {
model.lang = value;
return;
}
if (value.charAt(0) === ":") {
var split = splitMods(value);
var step = emptyStep();
step.keyword = $.trim(split.head.slice(1));
step.mods = split.mods;
model.steps.push(step);
return;
}
if (!model.steps.length) {
model.steps.push(emptyStep());
}
model.steps[model.steps.length - 1].words.push(parseWord(value));
});
return model;
}
// ---- writing it back ---------------------------------------------------------------------------
function joinMods(mods) {
return (mods || []).map(function (mo) {
return mo.flag ? "<" + mo.key + ">" : "<" + mo.key + ":" + (mo.value || "") + ">";
}).join("");
}
// A row nobody filled in: no word, no language, nothing hanging off it.
function wordIsEmpty(word) {
return !$.trim(word.word || "") && !$.trim(word.lang || "") &&
!(word.mods || []).length && !(word.steps || []).length;
}
function joinWord(word) {
var lang = $.trim(word.lang || "");
var out = (lang ? lang + ":" : "") + $.trim(word.word || "") + joinMods(word.mods);
if ((word.steps || []).length) {
out += "<etimon:" + joinChain(word.steps, true) + ">";
}
return out;
}
// inline: the chain sits inside "<etimon:…>", whose colon is the first keyword's own.
function joinChain(steps, inline) {
return (steps || []).map(function (step, index) {
var keyword = $.trim(step.keyword || "");
return (inline && index === 0 && keyword ? "" : ":") + keyword + joinMods(step.mods) +
(step.words || []).filter(function (word) {
return !wordIsEmpty(word);
}).map(function (word) {
return "<" + joinWord(word) + ">";
}).join("");
}).join("");
}
function buildCall(model) {
var parts = [NAME, $.trim(model.lang || "")];
(model.steps || []).forEach(function (step) {
var keyword = $.trim(step.keyword || "");
if (keyword || (step.mods || []).length) {
parts.push(":" + keyword + joinMods(step.mods));
}
(step.words || []).forEach(function (word) {
if (!wordIsEmpty(word)) {
parts.push(joinWord(word));
}
});
});
(model.namedOrder || []).forEach(function (key) {
var value = model.named[key];
if (value !== undefined && value !== null && value !== "") {
parts.push(((model.spelling || {})[key] || key) + "=" + value);
}
});
return "{{" + parts.join("|") + "}}";
}
// Which metn= choice a written value belongs to, and what is left over for that choice's own box:
// "3" belongs to the number choice, ":pal*" to the ":kod*" one.
function splitMetn(value) {
var current = $.trim(value || "");
var out = { choice: "", rest: "" };
if (!current) {
return out;
}
(data.metnChoices || []).forEach(function (choice) {
if (out.choice) {
return;
}
if (!choice.free) {
if (choice.value === current) {
out.choice = choice.value;
}
return;
}
if (choice.value === "1" && /^\d+$/.test(current)) {
out.choice = choice.value;
out.rest = current;
} else if (choice.value === ":*" && /^:.+\*$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1, -1);
} else if (choice.value === ":" && /^:[^*]+$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1);
}
});
return out;
}
// The inverse: what the dropdown and its box together mean as a metn= value.
function joinMetn(choiceValue, rest) {
var choice = null;
(data.metnChoices || []).forEach(function (c) {
if (c.value === choiceValue) {
choice = c;
}
});
if (!choice) {
return "";
}
if (!choice.free) {
return choice.value;
}
var typed = $.trim(rest || "");
if (!typed) {
return "";
}
if (choice.value === "1") {
return typed;
}
return ":" + typed + (choice.value === ":*" ? "*" : "");
}
function setNamed(model, key, value) {
if (model.namedOrder.indexOf(key) === -1) {
model.namedOrder.push(key);
}
model.named[key] = value;
}
// ---- the form ----------------------------------------------------------------------------------
// Chips for one list of modifiers, plus the ⊕ that adds another. The pills, the value box and the
// chips are the same widgetry a sense word row uses, so they look and behave the same here.
// Whether a modifier applies under the keyword it is being offered for: an "only" list in the data
// (aftype under :qf, maneid under :wergirtin) keeps it off every other keyword's picker.
function modApplies(def, keyword) {
if (!(def.only || []).length) {
return true;
}
var canonical = defFor(keywords(), keyword);
return def.only.indexOf(canonical ? canonical.key : keyword) !== -1;
}
function modifierEditor(mods, defs, onChange, keywordOf) {
var $wrap = $("<div class='dsb-etimon-mods'></div>");
var $chips = $("<div class='dsb-word-mods'></div>");
var $btn = $("<button type='button' class='dsb-mod-btn'>⊕</button>")
.attr("title", util.msg.addModifier);
var $picker = $("<div class='dsb-mod-picker'></div>").hide();
function label(def) {
return def ? def.key + " — " + def.desc : "";
}
// Same box for adding and for editing: a modifier with a fixed set of values gets a dropdown.
function openValueEditor(def, index) {
$picker.hide();
$wrap.children(".dsb-mod-val-wrap").remove();
var $box = $("<div class='dsb-mod-val-wrap'></div>");
var $label = $("<span class='dsb-mod-val-label'></span>").text(def.key);
var current = index === -1 ? "" : (mods[index].value || "");
var $field;
if ((def.values || []).length) {
$field = $("<select class='dsb-mod-val-input'></select>");
$field.append($("<option value=''></option>"));
def.values.forEach(function (value) {
$field.append($("<option></option>").val(value).text(value));
});
$field.val(current);
} else {
$field = $("<input type='text' class='dsb-mod-val-input'>")
.attr("size", 24).attr("placeholder", def.desc).val(current);
}
var $ok = $("<button type='button' class='dsb-mod-confirm'>✓</button>");
var $no = $("<button type='button' class='dsb-mod-cancel'>✕</button>");
function apply() {
var value = $.trim(String($field.val() || ""));
if (value) {
if (index === -1) {
mods.push({ key: def.key, value: value });
} else {
mods[index].value = value;
}
onChange();
render();
}
$box.remove();
}
$ok.on("click", apply);
$no.on("click", function () {
$box.remove();
});
$field.on("keydown", function (e) {
if (e.which === 13) {
e.preventDefault();
apply();
}
if (e.which === 27) {
$box.remove();
}
});
$box.append($label, $field, $ok, $no);
$wrap.append($box);
$field.focus();
}
function render() {
$chips.empty();
mods.forEach(function (mo, index) {
var def = defFor(defs, mo.key);
var $chip = $("<span class='dsb-word-mod-chip' role='button' tabindex='0'></span>")
.attr("title", def ? def.desc : mo.key)
.text(mo.flag ? mo.key : mo.key + ": " + (mo.value || ""));
var $x = $("<button type='button' class='dsb-word-mod-chip-remove'>×</button>")
.attr("title", util.msg.removeModifier);
$x.on("click", function (e) {
e.stopPropagation();
mods.splice(index, 1);
onChange();
render();
});
$chip.on("click", function () {
if (def && !mo.flag) {
openValueEditor(def, index);
}
});
$chip.append($x);
$chips.append($chip);
});
$chips.toggle(!!mods.length);
}
// Rebuilt per open rather than once: the keyword can change while the row is on screen, and with
// it which modifiers are on offer.
$btn.on("click", function (e) {
e.preventDefault();
var keyword = keywordOf ? keywordOf() : "";
$picker.empty();
defs.forEach(function (def) {
// <etimon:…> has the "+ Etîmona vê kelîmeyê" button of its own; as a pill it would be a
// box asking for a whole chain in one line.
if (INLINE_KEYS.indexOf(def.key) !== -1 || !modApplies(def, keyword)) {
return;
}
var $pill = $("<button type='button' class='dsb-mod-pill'></button>")
.text(def.key).attr("title", label(def)).data("mod-key", def.key);
$pill.on("click", function (ev) {
ev.preventDefault();
if (def.flag) {
mods.push({ key: def.key, flag: true });
$picker.hide();
onChange();
render();
return;
}
openValueEditor(def, -1);
});
$picker.append($pill);
});
var used = {};
mods.forEach(function (mo) {
used[mo.key] = true;
});
$picker.children(".dsb-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("dsb-mod-pill-used", isUsed);
});
$picker.toggle();
});
render();
$wrap.append($chips, $btn, $picker);
return $wrap;
}
function languageInput(value, onChange) {
var $input = $("<input type='text' class='dsb-input dsb-etimon-lang'>")
.attr("placeholder", "koda zimanî…")
.val(value || "");
$input.on("input change", function () {
onChange($.trim($input.val()));
});
langs.picker($input, function (code) {
onChange(code);
});
return $input;
}
// The whole form for one call. Structural changes (a step or a word added or dropped) rebuild it from
// the model, which every box writes into as it is typed in.
function buildForm(model, onResize) {
var $element = $("<div class='dsb-etimon'></div>");
var $preview = $("<code class='dsb-etimon-preview'></code>");
function changed() {
$preview.text(buildCall(model));
}
function structureChanged() {
render();
if (onResize) {
onResize();
}
}
// What a keyword constrains, in the two words that matter when picking one.
function keywordBadges(def) {
var out = [];
if (def && def.min > 1) {
out.push(def.min + "+ kelîme");
}
if (def && def.invisible) {
out.push("nayê çapkirin");
}
return out;
}
// An option carries the code, a short label and those badges. The description is long enough to
// be cut off mid-word in a select, so it goes in a line of its own under the control.
function keywordOptionText(def) {
return [":" + def.key].concat(def.short ? [def.short] : [])
.concat(keywordBadges(def)).join(" · ");
}
function keywordSelect(step) {
var $wrap = $("<div class='dsb-etimon-kw'></div>");
var $select = $("<select class='dsb-etimon-keyword'></select>");
$select.append($("<option value=''>— kelîmeya mifteyî —</option>"));
keywords().forEach(function (def) {
$select.append($("<option></option>").val(def.key).text(keywordOptionText(def)));
});
// A keyword written under an alias, or one the table does not know, keeps its own option.
var known = defFor(keywords(), step.keyword);
if (step.keyword && (!known || known.key !== step.keyword)) {
$select.append($("<option></option>").val(step.keyword)
.text(known ? keywordOptionText($.extend({}, known, { key: step.keyword }))
: ":" + step.keyword + " · (ne di belgeyê de)"));
}
$select.val(step.keyword);
var $desc = $("<div class='dsb-etimon-kw-desc dsb-etimon-hint'></div>");
function showDesc() {
var def = defFor(keywords(), $select.val());
$desc.text(def ? def.desc : "").toggle(!!(def && def.desc));
}
$select.on("change", function () {
step.keyword = $select.val();
showDesc();
changed();
structureChanged(); // the hints under it (how many words, what they mean) follow the keyword
});
$select.attr("data-placeholder", "Kelîmeya mifteyî");
$wrap.append(util.wrapNativeSelect($select), $desc);
showDesc();
// The searchable box reads the selection only when told to, so a keyword already chosen shows.
$select.trigger("wiktku:refresh");
return $wrap;
}
function wordRow(step, word, words, nested) {
var $li = $("<li class='dsb-etimon-word'></li>");
var $row = $("<div class='dsb-etimon-word-row'></div>");
var $lang = languageInput(word.lang, function (value) {
word.lang = value;
changed();
});
var $word = $("<input type='text' class='dsb-input dsb-etimon-word-input'>")
.attr("placeholder", "kelîme ( - = tenê navê zimên )")
.val(word.word || "");
$word.on("input", function () {
word.word = $word.val();
changed();
});
var $remove = $("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê kelîmeyê jê bibe");
$remove.on("click", function () {
words.splice(words.indexOf(word), 1);
if (!words.length && !nested) {
words.push(emptyWord());
}
changed();
structureChanged();
});
$row.append($lang, $word, modifierEditor(word.mods, wordMods(), changed, function () {
return step.keyword;
}), $remove);
$li.append($row);
// The way on to the next step when the source word's own page carries no {{etîmon}}.
if ((word.steps || []).length) {
var $nested = $("<div class='dsb-etimon-nested'></div>");
$nested.append($("<div class='dsb-etimon-nested-head'></div>").append(
$("<span class='dsb-adv-label'>Etîmona vê kelîmeyê</span>"),
$("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Etîmona vê kelîmeyê jê bibe")
.on("click", function () {
word.steps = [];
changed();
structureChanged();
})
));
renderSteps($nested, word.steps, true);
$li.append($nested);
} else {
$li.append($("<button type='button' class='dsb-etimon-add dsb-etimon-add-word'></button>")
.text("+ Etîmona vê kelîmeyê binivîse")
.attr("title", "Zincîrê li vir bidomîne, eger rûpela vê kelîmeyê tune be")
.on("click", function () {
word.steps = [$.extend(emptyStep(), { words: [emptyWord()] })];
changed();
structureChanged();
}));
}
return $li;
}
// A step is one array element, so its keyword, its words and anything nested under them move
// together. by is -1 or 1.
function moveStep(steps, step, by) {
var at = steps.indexOf(step);
var to = at + by;
if (at === -1 || to < 0 || to >= steps.length) {
return;
}
steps.splice(at, 1);
steps.splice(to, 0, step);
changed();
structureChanged();
}
function moveButton(steps, step, by) {
var at = steps.indexOf(step);
var up = by < 0;
return $("<button type='button' class='dsb-etimon-move'></button>")
.html(up ? "↑" : "↓")
.attr("title", up ? "Vê gavê jor bibe" : "Vê gavê jêr bibe")
.prop("disabled", up ? at === 0 : at === steps.length - 1)
.on("click", function () {
moveStep(steps, step, by);
});
}
function stepBlock(steps, step, nested) {
var $block = $("<div class='dsb-etimon-step'></div>");
var $head = $("<div class='dsb-etimon-step-head'></div>");
$head.append(keywordSelect(step));
// Inside an <etimon:…> the module reads every <…> group after the keyword as a term, so a
// keyword modifier written there would print as a source word. No picker, then.
if (!nested) {
$head.append(modifierEditor(step.mods, keywordMods(), changed, function () {
return step.keyword;
}));
}
if (steps.length > 1) {
$head.append(moveButton(steps, step, -1), moveButton(steps, step, 1));
}
if (steps.length > 1 || nested) {
$head.append($("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê gavê jê bibe")
.on("click", function () {
steps.splice(steps.indexOf(step), 1);
changed();
structureChanged();
}));
}
$block.append($head);
var def = defFor(keywords(), step.keyword);
if (def && def.min > 1) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Ev kelîmeya mifteyî bi kêmî " + def.min + " kelîmeyan dixwaze."));
}
if (def && def.invisible) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Di metnê de xuya nabe; tenê kategoriyan çêdike."));
}
if (!step.words.length) {
step.words.push(emptyWord());
}
var $words = $("<ul class='dsb-etimon-words'></ul>");
step.words.forEach(function (word) {
$words.append(wordRow(step, word, step.words, nested));
});
$block.append($words);
$block.append($("<button type='button' class='dsb-etimon-add'>+ Kelîme</button>")
.on("click", function () {
step.words.push(emptyWord());
structureChanged();
}));
return $block;
}
function renderSteps($host, steps, nested) {
if (!steps.length) {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
}
steps.forEach(function (step) {
$host.append(stepBlock(steps, step, nested));
});
$host.append($("<button type='button' class='dsb-etimon-add'></button>")
.text("+ Gaveke etîmolojiyê")
.attr("title", "Gaveke din a eynî bangkirinê")
.on("click", function () {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
structureChanged();
}));
}
// metn= picks how much of the chain is written out; the values that need a number or a language
// code ("3", ":pal") open a box of their own. The whole chain is the template's default, so it writes
// nothing; "no text" writes metnna=1.
function metnControl() {
var $wrap = $("<div class='dsb-etimon-metn'></div>");
var choices = (data.metnChoices || []).filter(function (choice) {
return choice.value !== "++";
});
var $select = $("<select class='dsb-etimon-metn-select'></select>");
$select.append($("<option value=''>++ — Tevahiya zincîrê</option>"));
$select.append($("<option value='na'>— metn nayê nîşandan —</option>"));
choices.forEach(function (choice) {
$select.append($("<option></option>").val(choice.value).text(choice.value + " — " + choice.desc));
});
var split = splitMetn(model.named.metn === "++" ? "" : model.named.metn);
$select.val(model.named.metnna ? "na" : split.choice);
var $free = $("<input type='text' class='dsb-input dsb-etimon-metn-free'>").val(split.rest);
function choiceFor(value) {
var found = null;
choices.forEach(function (choice) {
if (choice.value === value) {
found = choice;
}
});
return found;
}
function apply() {
var choice = choiceFor($select.val());
$free.toggle(!!(choice && choice.free));
if (choice && choice.free) {
$free.attr("placeholder", choice.value === "1" ? "hejmar…" : "koda zimanî…");
}
var none = $select.val() === "na";
setNamed(model, "metnna", none ? "1" : "");
setNamed(model, "metn", none ? "" : joinMetn($select.val(), $free.val()));
changed();
}
$select.on("change", apply);
$free.on("input", apply);
langs.picker($free, apply);
apply();
$select.attr("data-placeholder", "metn");
$wrap.append($("<span class='dsb-adv-label'>Metn</span>"), util.wrapNativeSelect($select), $free);
$select.trigger("wiktku:refresh");
return $wrap;
}
// The parameters marked "primary" in the data sit in the open next to Metn; the rest are behind
// "Parametreyên din".
function namedParams(primary) {
var $wrap = $("<div class='dsb-etimon-named'></div>");
(data.params || []).forEach(function (param) {
if (!!param.primary !== !!primary) {
return;
}
var value = model.named[param.key] || "";
var $row = $("<div class='dsb-etimon-param'></div>");
if (param.type === "flag") {
$row.addClass("dsb-etimon-param-flag");
var id = "dsb-etimon-" + param.key.replace(/[^A-Za-z0-9]/g, "");
var $box = $("<input type='checkbox'>").attr("id", id).prop("checked", value === "1");
$box.on("change", function () {
setNamed(model, param.key, $box.prop("checked") ? "1" : "");
changed();
});
$row.append($box, $("<label class='dsb-adv-label'></label>").attr("for", id)
.text(param.key + " — " + param.desc));
} else {
var $field;
if (param.type === "select") {
$field = $("<select class='dsb-etimon-param-select'></select>");
$field.append($("<option value=''></option>"));
// A value is a bare string, or {value, label} to say what it does in the option itself.
(param.values || []).forEach(function (v) {
$field.append($("<option></option>").val(v.value || v).text(v.label || v));
});
$field.val(value);
} else {
$field = $("<input type='text' class='dsb-input'>").val(value);
}
$field.on("input change", function () {
setNamed(model, param.key, $.trim(String($field.val() || "")));
changed();
});
$row.append($("<label class='dsb-adv-label'></label>").text(param.key), $field);
if (param.desc) {
$row.append($("<div class='dsb-tpl-help'></div>").text(param.desc));
}
}
$wrap.append($row);
});
return $wrap;
}
function render() {
$element.empty();
$element.append($("<div class='dsb-tpl-name'></div>").append(
$("<a></a>").attr("href", mw.util.getUrl("Şablon:" + NAME))
.attr("target", "_blank").text("{{" + NAME + "}}")
));
var $lang = $("<div class='dsb-etimon-pagelang'></div>");
$lang.append($("<span class='dsb-adv-label'>Zimanê rûpelê</span>"),
languageInput(model.lang, function (value) {
model.lang = value;
changed();
}));
// The two decisions that apply to the whole call sit together, above the steps: metn decides
// how much of everything under it is printed at all.
var $settings = $("<div class='dsb-etimon-settings'></div>");
$settings.append($lang, metnControl());
$element.append($settings);
var $steps = $("<div class='dsb-etimon-steps'></div>");
renderSteps($steps, model.steps, false);
$element.append($steps);
$element.append(namedParams(true));
var $more = $("<details class='dsb-etimon-more'></details>");
$more.append($("<summary>Parametreyên din</summary>"), namedParams(false));
$more.on("toggle", function () {
if (onResize) {
onResize();
}
});
$element.append($more);
$element.append($("<div class='dsb-etimon-preview-wrap'></div>").append($preview));
changed();
}
render();
return {
$element: $element,
values: function () {
return buildCall(model);
}
};
}
// A call for a page in this language with one empty step; the template writes the text and draws the
// tree by default.
function emptyModel(lang) {
return { lang: lang || "", steps: [$.extend(emptyStep(), { words: [emptyWord()] })],
named: {}, namedOrder: [], spelling: {} };
}
// ---- the dialog --------------------------------------------------------------------------------
var manager = null;
function windowManager() {
if (!manager) {
manager = new OO.ui.WindowManager();
$(document.body).append(manager.$element);
}
return manager;
}
// Opens one {{etîmon}} call in the builder. Resolves with the edited wikitext, "" for "Jê bibe", or
// null when it is cancelled -- the contract cite.openTemplateDialog() has, since it is called in its
// place. One at a time, for the same reason: a second window the manager does not track is dead.
var busy = false;
function openDialog(callWikitext) {
var done = $.Deferred();
if (busy) {
return done.resolve(null).promise();
}
var parsed = parseCall(callWikitext);
if (!parsed) {
return done.resolve(null).promise();
}
var model = parsed.steps.length ? parsed : emptyModel(parsed.lang);
busy = true;
done.always(function () {
busy = false;
});
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"]).then(function () {
var result = null;
var ParentDialog = OO.ui.ProcessDialog;
function EtimonDialog(config) {
ParentDialog.call(this, config);
}
OO.inheritClass(EtimonDialog, ParentDialog);
EtimonDialog.static.name = "etimonDialog";
EtimonDialog.static.title = "{{" + NAME + "}}";
EtimonDialog.static.actions = [
{ action: "save", label: "Biguhêre", flags: ["primary", "progressive"] },
{ action: "remove", label: "Jê bibe", flags: "destructive" },
{ label: util.msg.cancel, flags: "safe" }
];
EtimonDialog.prototype.initialize = function () {
var dialog = this;
ParentDialog.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.form = buildForm(model, function () {
dialog.updateSize();
});
this.panel.$element.append(this.form.$element);
this.$body.append(this.panel.$element);
};
EtimonDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
EtimonDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save" || action === "remove") {
return new OO.ui.Process(function () {
result = action === "remove" ? "" : dialog.form.values();
dialog.close();
});
}
return ParentDialog.prototype.getActionProcess.call(this, action);
};
// Marks the page while this is showing, so a floating box of ours (DefSideBoxes' queue) does
// not sit over a dialog the page's stacking order puts underneath it.
$(document.body).addClass("wikt-dialog-open");
done.always(function () {
$(document.body).removeClass("wikt-dialog-open");
});
var windows = windowManager();
var dialog = new EtimonDialog({ size: "large" });
windows.addWindows([dialog]);
// The modal hides the page from screen readers, which is refused while the clicked chip still
// has focus; so the focus leaves it first, and OOUI hands it back when the dialog closes.
var $opener = $(document.activeElement);
$opener.trigger("blur");
var instance = windows.openWindow(dialog, { $returnFocusTo: $opener });
instance.closed.then(function () {
windows.clearWindows();
done.resolve(result);
});
instance.opening.fail(function () {
windows.clearWindows();
done.resolve(null);
});
}, function () {
done.resolve(null);
});
return done.promise();
}
module.exports = {
NAME: NAME,
splitMetn: splitMetn,
joinMetn: joinMetn,
isEtimonName: isEtimonName,
parseCall: parseCall,
buildCall: buildCall,
parseChain: parseChain,
joinChain: joinChain,
openDialog: openDialog
};
}());
// </nowiki>
tqu4f51iy07psb1yrj5zxokwq9ppggi
6865597
6865573
2026-10-23T20:56:00Z
Wikihez
7344
Etimon form: ⊕ and × lead each word row, modifier chips after the word; Metn after id
6865597
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.cite/etimon.js]]
*
* The builder behind {{etîmon}}: one call is read into steps of "kelîmeya mifteyî + kelîmeyên jêder",
* every source word carrying its own <guherîner>s and, when its page has no etymology of its own, an
* inline <etimon:…> chain of further steps.
*
* Keywords, modifiers, the metn= values and the named parameters all come from the "etimon" block of
* data-main.json, so a new one is added there rather than here. cite.openTemplateDialog() hands every
* {{etîmon}} call to openDialog(), which is what DefSideBoxes and the wizard both call.
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var langs = edit.langs;
var wt = edit.wt;
var data = edit.data.etimon || {};
var NAME = "etîmon";
// Every name the template answers to: a call written under one of its redirects still opens the builder.
var NAMES = ["etîmon", "etimon", "etymon"];
// <etimon:…> is spelled without "î": modifier names inside <> are ASCII only.
var INLINE_KEYS = ["etimon", "ety"];
function isEtimonName(name) {
return NAMES.indexOf($.trim(name || "").toLowerCase().replace(/^şablon:\s*/i, "")) !== -1;
}
// ---- the tables ---------------------------------------------------------------------------------
function keywords() {
return data.keywords || [];
}
function wordMods() {
return data.wordMods || [];
}
function keywordMods() {
return data.keywordMods || [];
}
// The entry for a key or for one of its documented aliases, so ":inh" is understood as ":mîr".
function defFor(defs, key) {
var found = null;
defs.forEach(function (def) {
if (found) {
return;
}
if (def.key === key || (def.aliases || []).indexOf(key) !== -1) {
found = def;
}
});
return found;
}
function isKeywordModName(key) {
return !!defFor(keywordMods(), key);
}
// ---- reading a call ----------------------------------------------------------------------------
// The ">" closing the "<" at start, skipping the nested ones an <etimon:…> or an <id:…> inside it has.
function matchAngle(text, start) {
var depth = 0;
for (var i = start; i < text.length; i++) {
var ch = text.charAt(i);
if (ch === "<") {
depth++;
} else if (ch === ">") {
depth--;
if (depth === 0) {
return i;
}
}
}
return -1;
}
// Splits on the "|" that separate parameters, and only those: a <ref:{{ş|R:X}}> carries pipes of its own.
function splitParams(text) {
var out = [];
var buf = "";
var braces = 0;
var angles = 0;
for (var i = 0; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === "{{" || two === "}}") {
braces += two === "{{" ? 1 : -1;
buf += two;
i++;
continue;
}
var ch = text.charAt(i);
if (ch === "<") {
angles++;
} else if (ch === ">") {
angles = Math.max(0, angles - 1);
}
if (ch === "|" && braces <= 0 && angles === 0) {
out.push(buf);
buf = "";
continue;
}
buf += ch;
}
out.push(buf);
return out;
}
function parseMod(inner) {
var at = inner.indexOf(":");
if (at === -1) {
return { key: $.trim(inner), flag: true };
}
return { key: $.trim(inner.slice(0, at)), value: inner.slice(at + 1) };
}
// "fa:خانه<tr:xâne><t:mal>" -> { head: "fa:خانه", mods: [...] }
function splitMods(text) {
var head = "";
var mods = [];
var i = 0;
while (i < text.length) {
if (text.charAt(i) === "<") {
var end = matchAngle(text, i);
if (end === -1) {
head += text.slice(i);
break;
}
mods.push(parseMod(text.slice(i + 1, end)));
i = end + 1;
continue;
}
head += text.charAt(i);
i++;
}
return { head: head, mods: mods };
}
function emptyWord() {
return { lang: "", word: "", mods: [], steps: [] };
}
function emptyStep() {
return { keyword: "", mods: [], words: [] };
}
function parseWord(text) {
var split = splitMods($.trim(text));
var word = emptyWord();
var head = split.head;
// A language code, or a language family ("ira:-"); anything else is the word itself.
var m = /^([a-zA-Z][a-zA-Z0-9._-]*):([\s\S]*)$/.exec(head);
if (m) {
word.lang = m[1];
head = m[2];
}
word.word = head;
split.mods.forEach(function (mo) {
if (INLINE_KEYS.indexOf(mo.key) !== -1) {
word.steps = parseChain(mo.value || "");
return;
}
word.mods.push(mo);
});
return word;
}
// The inside of an <etimon:…>: ":kelîmeyeke mifteyî" followed by each source word in its own <…>.
// A group named after a keyword modifier belongs to the keyword, which is how Modul:etymon reads it too.
function parseChain(text) {
var steps = [];
var i = 0;
// "<etimon:" already ends in a colon, so the first keyword of an inline chain carries none of
// its own. Both spellings are read, since the module accepts either.
var lead = /^([^:<]+)/.exec(text);
if (lead) {
steps.push($.extend(emptyStep(), { keyword: $.trim(lead[1]) }));
i = lead[1].length;
}
function current() {
if (!steps.length) {
steps.push(emptyStep());
}
return steps[steps.length - 1];
}
while (i < text.length) {
var ch = text.charAt(i);
if (ch === ":") {
var j = i + 1;
while (j < text.length && text.charAt(j) !== ":" && text.charAt(j) !== "<") {
j++;
}
var step = emptyStep();
step.keyword = $.trim(text.slice(i + 1, j));
steps.push(step);
i = j;
continue;
}
if (ch === "<") {
var end = matchAngle(text, i);
if (end === -1) {
break;
}
var inner = text.slice(i + 1, end);
i = end + 1;
var name = /^([^:>]+)/.exec(inner);
var step2 = current();
if (!step2.words.length && name && isKeywordModName($.trim(name[1]))) {
step2.mods.push(parseMod(inner));
} else {
step2.words.push(parseWord(inner));
}
continue;
}
i++;
}
return steps;
}
// A named parameter; a name cannot hold "<": that is a modifier, so "fa:x<t:a=b>" stays positional.
var NAMED = /^\s*([^|={}<>\n]+?)\s*=([\s\S]*)$/;
// One {{etîmon}} call -> the model the form edits, or null for anything else. A named parameter written
// under another name (text= for metn=, nocat= for katna=, see data's paramAliases) fills the form's own
// field, and spelling keeps the page's name for writing it back.
function parseCall(callWikitext) {
var call = wt.parseTemplateCalls(callWikitext || "").calls[0];
if (!call || !isEtimonName(call.name)) {
return null;
}
var model = { lang: "", steps: [], named: {}, namedOrder: [], spelling: {} };
var positional = 0;
var args = splitParams(call.params.join("|"));
// The names the call itself uses, so an alias is read as the form's own name only when that name is
// not also given: a call with both keeps both, as the template will complain about it.
var given = args.map(function (raw) {
var m = NAMED.exec(raw);
return m && m[1];
});
args.forEach(function (raw) {
var m = NAMED.exec(raw);
if (m) {
var key = (data.paramAliases || {})[m[1]];
if (!key || given.indexOf(key) !== -1 || model.spelling[key]) {
key = m[1];
} else {
model.spelling[key] = m[1];
}
if (model.named[key] === undefined) {
model.namedOrder.push(key);
}
model.named[key] = $.trim(m[2]);
return;
}
positional++;
var value = $.trim(raw);
if (positional === 1) {
model.lang = value;
return;
}
if (value.charAt(0) === ":") {
var split = splitMods(value);
var step = emptyStep();
step.keyword = $.trim(split.head.slice(1));
step.mods = split.mods;
model.steps.push(step);
return;
}
if (!model.steps.length) {
model.steps.push(emptyStep());
}
model.steps[model.steps.length - 1].words.push(parseWord(value));
});
return model;
}
// ---- writing it back ---------------------------------------------------------------------------
function joinMods(mods) {
return (mods || []).map(function (mo) {
return mo.flag ? "<" + mo.key + ">" : "<" + mo.key + ":" + (mo.value || "") + ">";
}).join("");
}
// A row nobody filled in: no word, no language, nothing hanging off it.
function wordIsEmpty(word) {
return !$.trim(word.word || "") && !$.trim(word.lang || "") &&
!(word.mods || []).length && !(word.steps || []).length;
}
function joinWord(word) {
var lang = $.trim(word.lang || "");
var out = (lang ? lang + ":" : "") + $.trim(word.word || "") + joinMods(word.mods);
if ((word.steps || []).length) {
out += "<etimon:" + joinChain(word.steps, true) + ">";
}
return out;
}
// inline: the chain sits inside "<etimon:…>", whose colon is the first keyword's own.
function joinChain(steps, inline) {
return (steps || []).map(function (step, index) {
var keyword = $.trim(step.keyword || "");
return (inline && index === 0 && keyword ? "" : ":") + keyword + joinMods(step.mods) +
(step.words || []).filter(function (word) {
return !wordIsEmpty(word);
}).map(function (word) {
return "<" + joinWord(word) + ">";
}).join("");
}).join("");
}
function buildCall(model) {
var parts = [NAME, $.trim(model.lang || "")];
(model.steps || []).forEach(function (step) {
var keyword = $.trim(step.keyword || "");
if (keyword || (step.mods || []).length) {
parts.push(":" + keyword + joinMods(step.mods));
}
(step.words || []).forEach(function (word) {
if (!wordIsEmpty(word)) {
parts.push(joinWord(word));
}
});
});
(model.namedOrder || []).forEach(function (key) {
var value = model.named[key];
if (value !== undefined && value !== null && value !== "") {
parts.push(((model.spelling || {})[key] || key) + "=" + value);
}
});
return "{{" + parts.join("|") + "}}";
}
// Which metn= choice a written value belongs to, and what is left over for that choice's own box:
// "3" belongs to the number choice, ":pal*" to the ":kod*" one.
function splitMetn(value) {
var current = $.trim(value || "");
var out = { choice: "", rest: "" };
if (!current) {
return out;
}
(data.metnChoices || []).forEach(function (choice) {
if (out.choice) {
return;
}
if (!choice.free) {
if (choice.value === current) {
out.choice = choice.value;
}
return;
}
if (choice.value === "1" && /^\d+$/.test(current)) {
out.choice = choice.value;
out.rest = current;
} else if (choice.value === ":*" && /^:.+\*$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1, -1);
} else if (choice.value === ":" && /^:[^*]+$/.test(current)) {
out.choice = choice.value;
out.rest = current.slice(1);
}
});
return out;
}
// The inverse: what the dropdown and its box together mean as a metn= value.
function joinMetn(choiceValue, rest) {
var choice = null;
(data.metnChoices || []).forEach(function (c) {
if (c.value === choiceValue) {
choice = c;
}
});
if (!choice) {
return "";
}
if (!choice.free) {
return choice.value;
}
var typed = $.trim(rest || "");
if (!typed) {
return "";
}
if (choice.value === "1") {
return typed;
}
return ":" + typed + (choice.value === ":*" ? "*" : "");
}
function setNamed(model, key, value) {
if (model.namedOrder.indexOf(key) === -1) {
model.namedOrder.push(key);
}
model.named[key] = value;
}
// ---- the form ----------------------------------------------------------------------------------
// Chips for one list of modifiers, plus the ⊕ that adds another. The pills, the value box and the
// chips are the same widgetry a sense word row uses, so they look and behave the same here.
// Whether a modifier applies under the keyword it is being offered for: an "only" list in the data
// (aftype under :qf, maneid under :wergirtin) keeps it off every other keyword's picker.
function modApplies(def, keyword) {
if (!(def.only || []).length) {
return true;
}
var canonical = defFor(keywords(), keyword);
return def.only.indexOf(canonical ? canonical.key : keyword) !== -1;
}
// $chipsHost, if given, takes the chips and the value box, so the ⊕ can sit apart from them.
function modifierEditor(mods, defs, onChange, keywordOf, $chipsHost) {
var $wrap = $("<div class='dsb-etimon-mods'></div>");
var $host = $chipsHost || $wrap;
var $chips = $("<div class='dsb-word-mods'></div>");
var $btn = $("<button type='button' class='dsb-mod-btn'>⊕</button>")
.attr("title", util.msg.addModifier);
var $picker = $("<div class='dsb-mod-picker'></div>").hide();
function label(def) {
return def ? def.key + " — " + def.desc : "";
}
// Same box for adding and for editing: a modifier with a fixed set of values gets a dropdown.
function openValueEditor(def, index) {
$picker.hide();
$host.children(".dsb-mod-val-wrap").remove();
var $box = $("<div class='dsb-mod-val-wrap'></div>");
var $label = $("<span class='dsb-mod-val-label'></span>").text(def.key);
var current = index === -1 ? "" : (mods[index].value || "");
var $field;
if ((def.values || []).length) {
$field = $("<select class='dsb-mod-val-input'></select>");
$field.append($("<option value=''></option>"));
def.values.forEach(function (value) {
$field.append($("<option></option>").val(value).text(value));
});
$field.val(current);
} else {
$field = $("<input type='text' class='dsb-mod-val-input'>")
.attr("size", 24).attr("placeholder", def.desc).val(current);
}
var $ok = $("<button type='button' class='dsb-mod-confirm'>✓</button>");
var $no = $("<button type='button' class='dsb-mod-cancel'>✕</button>");
function apply() {
var value = $.trim(String($field.val() || ""));
if (value) {
if (index === -1) {
mods.push({ key: def.key, value: value });
} else {
mods[index].value = value;
}
onChange();
render();
}
$box.remove();
}
$ok.on("click", apply);
$no.on("click", function () {
$box.remove();
});
$field.on("keydown", function (e) {
if (e.which === 13) {
e.preventDefault();
apply();
}
if (e.which === 27) {
$box.remove();
}
});
$box.append($label, $field, $ok, $no);
$host.append($box);
$field.focus();
}
function render() {
$chips.empty();
mods.forEach(function (mo, index) {
var def = defFor(defs, mo.key);
var $chip = $("<span class='dsb-word-mod-chip' role='button' tabindex='0'></span>")
.attr("title", def ? def.desc : mo.key)
.text(mo.flag ? mo.key : mo.key + ": " + (mo.value || ""));
var $x = $("<button type='button' class='dsb-word-mod-chip-remove'>×</button>")
.attr("title", util.msg.removeModifier);
$x.on("click", function (e) {
e.stopPropagation();
mods.splice(index, 1);
onChange();
render();
});
$chip.on("click", function () {
if (def && !mo.flag) {
openValueEditor(def, index);
}
});
$chip.append($x);
$chips.append($chip);
});
$chips.toggle(!!mods.length);
}
// Rebuilt per open rather than once: the keyword can change while the row is on screen, and with
// it which modifiers are on offer.
$btn.on("click", function (e) {
e.preventDefault();
var keyword = keywordOf ? keywordOf() : "";
$picker.empty();
defs.forEach(function (def) {
// <etimon:…> has the "+ Etîmona vê kelîmeyê" button of its own; as a pill it would be a
// box asking for a whole chain in one line.
if (INLINE_KEYS.indexOf(def.key) !== -1 || !modApplies(def, keyword)) {
return;
}
var $pill = $("<button type='button' class='dsb-mod-pill'></button>")
.text(def.key).attr("title", label(def)).data("mod-key", def.key);
$pill.on("click", function (ev) {
ev.preventDefault();
if (def.flag) {
mods.push({ key: def.key, flag: true });
$picker.hide();
onChange();
render();
return;
}
openValueEditor(def, -1);
});
$picker.append($pill);
});
var used = {};
mods.forEach(function (mo) {
used[mo.key] = true;
});
$picker.children(".dsb-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("dsb-mod-pill-used", isUsed);
});
$picker.toggle();
});
render();
if ($chipsHost) {
$chipsHost.append($chips);
$wrap.append($btn, $picker);
} else {
$wrap.append($chips, $btn, $picker);
}
return $wrap;
}
function languageInput(value, onChange) {
var $input = $("<input type='text' class='dsb-input dsb-etimon-lang'>")
.attr("placeholder", "koda zimanî…")
.val(value || "");
$input.on("input change", function () {
onChange($.trim($input.val()));
});
langs.picker($input, function (code) {
onChange(code);
});
return $input;
}
// The whole form for one call. Structural changes (a step or a word added or dropped) rebuild it from
// the model, which every box writes into as it is typed in.
function buildForm(model, onResize) {
var $element = $("<div class='dsb-etimon'></div>");
var $preview = $("<code class='dsb-etimon-preview'></code>");
function changed() {
$preview.text(buildCall(model));
}
function structureChanged() {
render();
if (onResize) {
onResize();
}
}
// What a keyword constrains, in the two words that matter when picking one.
function keywordBadges(def) {
var out = [];
if (def && def.min > 1) {
out.push(def.min + "+ kelîme");
}
if (def && def.invisible) {
out.push("nayê çapkirin");
}
return out;
}
// An option carries the code, a short label and those badges. The description is long enough to
// be cut off mid-word in a select, so it goes in a line of its own under the control.
function keywordOptionText(def) {
return [":" + def.key].concat(def.short ? [def.short] : [])
.concat(keywordBadges(def)).join(" · ");
}
function keywordSelect(step) {
var $wrap = $("<div class='dsb-etimon-kw'></div>");
var $select = $("<select class='dsb-etimon-keyword'></select>");
$select.append($("<option value=''>— kelîmeya mifteyî —</option>"));
keywords().forEach(function (def) {
$select.append($("<option></option>").val(def.key).text(keywordOptionText(def)));
});
// A keyword written under an alias, or one the table does not know, keeps its own option.
var known = defFor(keywords(), step.keyword);
if (step.keyword && (!known || known.key !== step.keyword)) {
$select.append($("<option></option>").val(step.keyword)
.text(known ? keywordOptionText($.extend({}, known, { key: step.keyword }))
: ":" + step.keyword + " · (ne di belgeyê de)"));
}
$select.val(step.keyword);
var $desc = $("<div class='dsb-etimon-kw-desc dsb-etimon-hint'></div>");
function showDesc() {
var def = defFor(keywords(), $select.val());
$desc.text(def ? def.desc : "").toggle(!!(def && def.desc));
}
$select.on("change", function () {
step.keyword = $select.val();
showDesc();
changed();
structureChanged(); // the hints under it (how many words, what they mean) follow the keyword
});
$select.attr("data-placeholder", "Kelîmeya mifteyî");
$wrap.append(util.wrapNativeSelect($select), $desc);
showDesc();
// The searchable box reads the selection only when told to, so a keyword already chosen shows.
$select.trigger("wiktku:refresh");
return $wrap;
}
function wordRow(step, word, words, nested) {
var $li = $("<li class='dsb-etimon-word'></li>");
var $row = $("<div class='dsb-etimon-word-row'></div>");
var $lang = languageInput(word.lang, function (value) {
word.lang = value;
changed();
});
var $word = $("<input type='text' class='dsb-input dsb-etimon-word-input'>")
.attr("placeholder", "kelîme ( - = tenê navê zimên )")
.val(word.word || "");
$word.on("input", function () {
word.word = $word.val();
changed();
});
var $remove = $("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê kelîmeyê jê bibe");
$remove.on("click", function () {
words.splice(words.indexOf(word), 1);
if (!words.length && !nested) {
words.push(emptyWord());
}
changed();
structureChanged();
});
// ⊕ and × lead the row, so they stay put however wide the chips after the word grow.
var $chipsHost = $("<div class='dsb-etimon-word-mods'></div>");
$row.append(modifierEditor(word.mods, wordMods(), changed, function () {
return step.keyword;
}, $chipsHost), $remove, $lang, $word, $chipsHost);
$li.append($row);
// The way on to the next step when the source word's own page carries no {{etîmon}}.
if ((word.steps || []).length) {
var $nested = $("<div class='dsb-etimon-nested'></div>");
$nested.append($("<div class='dsb-etimon-nested-head'></div>").append(
$("<span class='dsb-adv-label'>Etîmona vê kelîmeyê</span>"),
$("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Etîmona vê kelîmeyê jê bibe")
.on("click", function () {
word.steps = [];
changed();
structureChanged();
})
));
renderSteps($nested, word.steps, true);
$li.append($nested);
} else {
$li.append($("<button type='button' class='dsb-etimon-add dsb-etimon-add-word'></button>")
.text("+ Etîmona vê kelîmeyê binivîse")
.attr("title", "Zincîrê li vir bidomîne, eger rûpela vê kelîmeyê tune be")
.on("click", function () {
word.steps = [$.extend(emptyStep(), { words: [emptyWord()] })];
changed();
structureChanged();
}));
}
return $li;
}
// A step is one array element, so its keyword, its words and anything nested under them move
// together. by is -1 or 1.
function moveStep(steps, step, by) {
var at = steps.indexOf(step);
var to = at + by;
if (at === -1 || to < 0 || to >= steps.length) {
return;
}
steps.splice(at, 1);
steps.splice(to, 0, step);
changed();
structureChanged();
}
function moveButton(steps, step, by) {
var at = steps.indexOf(step);
var up = by < 0;
return $("<button type='button' class='dsb-etimon-move'></button>")
.html(up ? "↑" : "↓")
.attr("title", up ? "Vê gavê jor bibe" : "Vê gavê jêr bibe")
.prop("disabled", up ? at === 0 : at === steps.length - 1)
.on("click", function () {
moveStep(steps, step, by);
});
}
function stepBlock(steps, step, nested) {
var $block = $("<div class='dsb-etimon-step'></div>");
var $head = $("<div class='dsb-etimon-step-head'></div>");
$head.append(keywordSelect(step));
// Inside an <etimon:…> the module reads every <…> group after the keyword as a term, so a
// keyword modifier written there would print as a source word. No picker, then.
if (!nested) {
$head.append(modifierEditor(step.mods, keywordMods(), changed, function () {
return step.keyword;
}));
}
if (steps.length > 1) {
$head.append(moveButton(steps, step, -1), moveButton(steps, step, 1));
}
if (steps.length > 1 || nested) {
$head.append($("<button type='button' class='dsb-etimon-remove'>×</button>")
.attr("title", "Vê gavê jê bibe")
.on("click", function () {
steps.splice(steps.indexOf(step), 1);
changed();
structureChanged();
}));
}
$block.append($head);
var def = defFor(keywords(), step.keyword);
if (def && def.min > 1) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Ev kelîmeya mifteyî bi kêmî " + def.min + " kelîmeyan dixwaze."));
}
if (def && def.invisible) {
$block.append($("<div class='dsb-tpl-help'></div>")
.text("Di metnê de xuya nabe; tenê kategoriyan çêdike."));
}
if (!step.words.length) {
step.words.push(emptyWord());
}
var $words = $("<ul class='dsb-etimon-words'></ul>");
step.words.forEach(function (word) {
$words.append(wordRow(step, word, step.words, nested));
});
$block.append($words);
$block.append($("<button type='button' class='dsb-etimon-add'>+ Kelîme</button>")
.on("click", function () {
step.words.push(emptyWord());
structureChanged();
}));
return $block;
}
function renderSteps($host, steps, nested) {
if (!steps.length) {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
}
steps.forEach(function (step) {
$host.append(stepBlock(steps, step, nested));
});
$host.append($("<button type='button' class='dsb-etimon-add'></button>")
.text("+ Gaveke etîmolojiyê")
.attr("title", "Gaveke din a eynî bangkirinê")
.on("click", function () {
steps.push($.extend(emptyStep(), { words: [emptyWord()] }));
structureChanged();
}));
}
// metn= picks how much of the chain is written out; the values that need a number or a language
// code ("3", ":pal") open a box of their own. The whole chain is the template's default, so it writes
// nothing; "no text" writes metnna=1.
function metnControl() {
var $wrap = $("<div class='dsb-etimon-metn'></div>");
var choices = (data.metnChoices || []).filter(function (choice) {
return choice.value !== "++";
});
var $select = $("<select class='dsb-etimon-metn-select'></select>");
$select.append($("<option value=''>++ — Tevahiya zincîrê</option>"));
$select.append($("<option value='na'>— metn nayê nîşandan —</option>"));
choices.forEach(function (choice) {
$select.append($("<option></option>").val(choice.value).text(choice.value + " — " + choice.desc));
});
var split = splitMetn(model.named.metn === "++" ? "" : model.named.metn);
$select.val(model.named.metnna ? "na" : split.choice);
var $free = $("<input type='text' class='dsb-input dsb-etimon-metn-free'>").val(split.rest);
function choiceFor(value) {
var found = null;
choices.forEach(function (choice) {
if (choice.value === value) {
found = choice;
}
});
return found;
}
function apply() {
var choice = choiceFor($select.val());
$free.toggle(!!(choice && choice.free));
if (choice && choice.free) {
$free.attr("placeholder", choice.value === "1" ? "hejmar…" : "koda zimanî…");
}
var none = $select.val() === "na";
setNamed(model, "metnna", none ? "1" : "");
setNamed(model, "metn", none ? "" : joinMetn($select.val(), $free.val()));
changed();
}
$select.on("change", apply);
$free.on("input", apply);
langs.picker($free, apply);
apply();
$select.attr("data-placeholder", "metn");
$wrap.append($("<span class='dsb-adv-label'>Metn</span>"), util.wrapNativeSelect($select), $free);
$select.trigger("wiktku:refresh");
return $wrap;
}
// The parameters marked "primary" in the data sit in the open next to Metn; the rest are behind
// "Parametreyên din".
function namedParams(primary) {
var $wrap = $("<div class='dsb-etimon-named'></div>");
(data.params || []).forEach(function (param) {
if (!!param.primary !== !!primary) {
return;
}
var value = model.named[param.key] || "";
var $row = $("<div class='dsb-etimon-param'></div>");
if (param.type === "flag") {
$row.addClass("dsb-etimon-param-flag");
var id = "dsb-etimon-" + param.key.replace(/[^A-Za-z0-9]/g, "");
var $box = $("<input type='checkbox'>").attr("id", id).prop("checked", value === "1");
$box.on("change", function () {
setNamed(model, param.key, $box.prop("checked") ? "1" : "");
changed();
});
$row.append($box, $("<label class='dsb-adv-label'></label>").attr("for", id)
.text(param.key + " — " + param.desc));
} else {
var $field;
if (param.type === "select") {
$field = $("<select class='dsb-etimon-param-select'></select>");
$field.append($("<option value=''></option>"));
// A value is a bare string, or {value, label} to say what it does in the option itself.
(param.values || []).forEach(function (v) {
$field.append($("<option></option>").val(v.value || v).text(v.label || v));
});
$field.val(value);
} else {
$field = $("<input type='text' class='dsb-input'>").val(value);
}
$field.on("input change", function () {
setNamed(model, param.key, $.trim(String($field.val() || "")));
changed();
});
$row.append($("<label class='dsb-adv-label'></label>").text(param.key), $field);
if (param.desc) {
$row.append($("<div class='dsb-tpl-help'></div>").text(param.desc));
}
}
$wrap.append($row);
});
return $wrap;
}
function render() {
$element.empty();
$element.append($("<div class='dsb-tpl-name'></div>").append(
$("<a></a>").attr("href", mw.util.getUrl("Şablon:" + NAME))
.attr("target", "_blank").text("{{" + NAME + "}}")
));
var $lang = $("<div class='dsb-etimon-pagelang'></div>");
$lang.append($("<span class='dsb-adv-label'>Zimanê rûpelê</span>"),
languageInput(model.lang, function (value) {
model.lang = value;
changed();
}));
// The page's language heads the form; metn, which says how much of the chain is printed, follows
// the steps and the id.
var $settings = $("<div class='dsb-etimon-settings'></div>");
$settings.append($lang);
$element.append($settings);
var $steps = $("<div class='dsb-etimon-steps'></div>");
renderSteps($steps, model.steps, false);
$element.append($steps);
$element.append(namedParams(true), metnControl());
var $more = $("<details class='dsb-etimon-more'></details>");
$more.append($("<summary>Parametreyên din</summary>"), namedParams(false));
$more.on("toggle", function () {
if (onResize) {
onResize();
}
});
$element.append($more);
$element.append($("<div class='dsb-etimon-preview-wrap'></div>").append($preview));
changed();
}
render();
return {
$element: $element,
values: function () {
return buildCall(model);
}
};
}
// A call for a page in this language with one empty step; the template writes the text and draws the
// tree by default.
function emptyModel(lang) {
return { lang: lang || "", steps: [$.extend(emptyStep(), { words: [emptyWord()] })],
named: {}, namedOrder: [], spelling: {} };
}
// ---- the dialog --------------------------------------------------------------------------------
var manager = null;
function windowManager() {
if (!manager) {
manager = new OO.ui.WindowManager();
$(document.body).append(manager.$element);
}
return manager;
}
// Opens one {{etîmon}} call in the builder. Resolves with the edited wikitext, "" for "Jê bibe", or
// null when it is cancelled -- the contract cite.openTemplateDialog() has, since it is called in its
// place. One at a time, for the same reason: a second window the manager does not track is dead.
var busy = false;
function openDialog(callWikitext) {
var done = $.Deferred();
if (busy) {
return done.resolve(null).promise();
}
var parsed = parseCall(callWikitext);
if (!parsed) {
return done.resolve(null).promise();
}
var model = parsed.steps.length ? parsed : emptyModel(parsed.lang);
busy = true;
done.always(function () {
busy = false;
});
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"]).then(function () {
var result = null;
var ParentDialog = OO.ui.ProcessDialog;
function EtimonDialog(config) {
ParentDialog.call(this, config);
}
OO.inheritClass(EtimonDialog, ParentDialog);
EtimonDialog.static.name = "etimonDialog";
EtimonDialog.static.title = "{{" + NAME + "}}";
EtimonDialog.static.actions = [
{ action: "save", label: "Biguhêre", flags: ["primary", "progressive"] },
{ action: "remove", label: "Jê bibe", flags: "destructive" },
{ label: util.msg.cancel, flags: "safe" }
];
EtimonDialog.prototype.initialize = function () {
var dialog = this;
ParentDialog.prototype.initialize.apply(this, arguments);
this.panel = new OO.ui.PanelLayout({ padded: true, expanded: false });
this.form = buildForm(model, function () {
dialog.updateSize();
});
this.panel.$element.append(this.form.$element);
this.$body.append(this.panel.$element);
};
EtimonDialog.prototype.getBodyHeight = function () {
return this.panel.$element.outerHeight(true);
};
EtimonDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save" || action === "remove") {
return new OO.ui.Process(function () {
result = action === "remove" ? "" : dialog.form.values();
dialog.close();
});
}
return ParentDialog.prototype.getActionProcess.call(this, action);
};
// Marks the page while this is showing, so a floating box of ours (DefSideBoxes' queue) does
// not sit over a dialog the page's stacking order puts underneath it.
$(document.body).addClass("wikt-dialog-open");
done.always(function () {
$(document.body).removeClass("wikt-dialog-open");
});
var windows = windowManager();
var dialog = new EtimonDialog({ size: "large" });
windows.addWindows([dialog]);
// The modal hides the page from screen readers, which is refused while the clicked chip still
// has focus; so the focus leaves it first, and OOUI hands it back when the dialog closes.
var $opener = $(document.activeElement);
$opener.trigger("blur");
var instance = windows.openWindow(dialog, { $returnFocusTo: $opener });
instance.closed.then(function () {
windows.clearWindows();
done.resolve(result);
});
instance.opening.fail(function () {
windows.clearWindows();
done.resolve(null);
});
}, function () {
done.resolve(null);
});
return done.promise();
}
module.exports = {
NAME: NAME,
splitMetn: splitMetn,
joinMetn: joinMetn,
isEtimonName: isEtimonName,
parseCall: parseCall,
buildCall: buildCall,
parseChain: parseChain,
joinChain: joinChain,
openDialog: openDialog
};
}());
// </nowiki>
7stj0hisnp7tizo2d03haooqgl3dg2y
Bikarhêner:Wikihez/ceribandin/werger
2
1359454
6865526
6865381
2026-10-23T19:25:43Z
Wikihez
7344
/* Werger */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6865526
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
=== Navdêr ===
[[Wêne:Micromys minutus (aka).jpg|thumb|Mişkek (1)]]
[[Wêne:3-Tasten-Maus Microsoft.jpg|thumb|'''Mişkekî''' kompûterê (2)]]
{{wp|Mişk (cudakirin)|Mişk}}
{{navdêr|ku|z=n}}
# {{f|ku|guhandar}} [[heywan|Heywanekî]] [[biçûk]] yê [[kirîner]] e û [[şibîn|dişibe]] [[cird]]an lê [[hûrik]]tir e.
#: {{mk|ku|Kitik ne li mal e, navê '''mişkî''' Evdal e.}}
# {{maneid|ku|informatîk}} {{f|ku|informatîk}} [[amûr|Amûrekî]] [[kompûter]]ê ye ku mirov pê [[nivîs]] û [[grafîk]]an [[livandin|dilivîne]].
==== Tewandin ====
{{ku-tewîn-nav|nêr}}
==== Bi alfabeyên din ====
* {{ku-ar|مشک}}
* {{ku-kr|мьшк}}
==== Formên alternatîv ====
* {{hw|ku|mihşk}}
==== Jê ====
{{kol3|ku
| mişkane
| mişke
| mişko
| mişkok
}}
==== Bide ber ====
{{kol3|ku
| meşk
| misk
| hişk
| pişk
| rişk
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-/ceribandin|ckb|mişk|sc=Latn}}
* {{Z|sdh}}: {{W-/ceribandin|sdh|müş|sc=Latn}}
* {{Z|lki}}: {{W-/ceribandin|lki|müş|sc=Latn}}
* {{Z|hac}}: {{W-/ceribandin|hac|miłe|sc=Latn}}
* {{Z|zza}}: {{W+/ceribandin|zza|merre}}
==== Werger ====
{{werger-ser/ceribandin|Test çi dike}}
* {{Z|ab}}: {{W-/ceribandin|ab|аҳәынаҧ|sc=Cyrl}}
* {{Z|af}}: {{W+/ceribandin|af|muis}}
* {{Z|sq}}: {{W+/ceribandin|sq|mi|n}}
* {{Z|de}}: {{W+/ceribandin|de|Maus|m}}
*: {{Z|gsw}}: {{W-/ceribandin|gsw|Muus|m}}
* {{Z|am}}: {{W-/ceribandin|am|አይጥ|sc=Ethi}}
* {{Z|ani}}: {{W-/ceribandin|ani|гьинкӏкӏу}}
* {{Z|an}}: {{W-/ceribandin|an|zorz}}
* {{Z|rup}}: {{W-/ceribandin|rup|shoaric}}
* {{Z|aer}}: {{W-/ceribandin|aer|thakwere}}
* {{Z|as}}: {{W-/ceribandin|as|নিগনি}}
* {{Z|ast}}: {{W+/ceribandin|ast|ratu|n}}, {{W+/ceribandin|ast|ratón|n}}, {{W+/ceribandin|ast|mur}}
* {{Z|tay}}: {{W-/ceribandin|tay|olit}}
* {{Z|av}}: {{W-/ceribandin|av|гӏункӏкӏ|sc=Cyrl}}
* {{Z|ay}}: {{W+/ceribandin|ay|achaku}}
* {{Z|ain}}: {{W-/ceribandin|ain|エㇾㇺ|tr=erm}}, {{W-/ceribandin|ain|エㇾム|tr=ermu}}, {{W-/ceribandin|ain|エルㇺ|tr=erum}}
* {{Z|az}}: {{W+/ceribandin|az|siçan}}
* {{Z|ban}}: {{W-/ceribandin|ban|bikul}}, {{W-/ceribandin|ban|jero ketut}}
* {{Z|eu}}: {{W+/ceribandin|eu|sagu}}
* {{Z|ba}}: {{W-/ceribandin|ba|сысҡан|sc=Cyrl}}
* {{Z|be}}: {{W-/ceribandin|be|мыш|m}}, {{W-/ceribandin|be|мы́шка|m}}
* {{Z|bal}}: {{W-/ceribandin|bal|مشک|tr=mušk}}, {{W-/ceribandin|bal|موشک|tr=múšk}}
* {{Z|bn}}: {{W+/ceribandin|bn|ইঁদুর|sc=Beng}}
* {{Z|ber}}:
*: {{Z|rif}}: {{W-/ceribandin|rif|aɣerḍa|n}}
* {{Z|br}}: {{W+/ceribandin|br|logodenn|m}}
* {{Z|bg}}: {{W+/ceribandin|bg|ми́шка|m}}, {{W-/ceribandin|bg|мишо́к|n}}
* {{Z|my}}: {{W+/ceribandin|my|ကြွက်|sc=Mymr}}
* {{Z|pcc}}: {{W-/ceribandin|pcc|ranz}}, {{W-/ceribandin|pcc|waauc}}
* {{Z|ccc}}: {{W-/ceribandin|ccc|kijsili}}
* {{Z|ce}}: {{W-/ceribandin|ce|дахка}}
* {{Z|cs}}: {{W+/ceribandin|cs|myš|m}}, {{W+/ceribandin|cs|myšák|n}}
* {{Z|chr}}: {{W-/ceribandin|chr|ᏥᏍᏕᏥ}}
* {{Z|ny}}: {{W-/ceribandin|ny|mbewa}}
* {{Z|cic}}: {{W-/ceribandin|cic|pinti'}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-/ceribandin|yue|老鼠|tr=lou5 syu2}}
*: {{Z|dng}}: {{W-/ceribandin|dng|лочў}}
*: {{Z|hak}}: {{W-/ceribandin|hak|老鼠|tr=lo-chhú, ló-chhú, no-chhú}}
*: {{Z|lzh}}: {{W-/ceribandin|lzh|鼠|tr=shǔ}}, {{W-/ceribandin|lzh|鼷|tr=xī}}
*: {{Z|cmn}}: {{W+/ceribandin|cmn|老鼠|tr=lǎoshǔ}}, {{W+/ceribandin|cmn|鼠|tr=shǔ}}, {{W+/ceribandin|cmn|耗子|tr=hàozi}}, {{W+/ceribandin|cmn|小鼠|tr=xiǎoshǔ}}, {{W+/ceribandin|cmn|鼷|tr=xī|guman=erê}}, {{W+|cmn|test}}
*: {{Z|cdo}}: {{W-/ceribandin|cdo|老鼠|tr=lō̤-chṳ̄}}
*: {{Z|nan}}: {{W+/ceribandin|nan|鳥鼠|tr=niáu-chhí, niáu-chhú}}, {{W+/ceribandin|nan|鼠|tr=chhí, chhú}}, {{W+/ceribandin|nan|錢鼠|tr=chîⁿ-chhí, chîⁿ-chhú}}, {{W-/ceribandin|nan|老鼠|tr=ló͘-chhír, ló͘-chhí, niáu-chhú, niáu-chhí, niáu-chhír}}
*: {{Z|wuu}}: {{W-/ceribandin|wuu|老鼠}}
* {{Z|ckt}}: {{W-/ceribandin|ckt|пипиӄыԓгын|tr=pipiqyḷgyn}}
* {{Z|cv}}: {{W-/ceribandin|cv|шӑши|sc=Cyrl}}
* {{Z|dlm}}: {{W-/ceribandin|dlm|surco|n}}
* {{Z|da}}: {{W+/ceribandin|da|mus|g}}
* {{Z|dv}}: {{W-/ceribandin|dv|މީދާ|sc=Thaa}}
* {{Z|id}}: {{W+/ceribandin|id|tikus}}
* {{Z|ar}}: {{W-/ceribandin|ar|فَأْرَة|m}}, {{W-/ceribandin|ar|فَأْر|n}}
*: {{Z|arz}}: {{W-/ceribandin|arz|فار|tr=fār}}
*: {{Z|ary}}: {{W-/ceribandin|ary|فار|tr=fār}}
* {{Z|hy}}: {{W+/ceribandin|hy|մուկ}}
*: {{Z|xcl}}: {{W-/ceribandin|xcl|մուկն|sc=Armn}}
* {{Z|myv}}: {{W-/ceribandin|myv|чеерь}}
* {{Z|eo}}: {{W+/ceribandin|eo|muso}}, {{W-/ceribandin|eo|musino}}, {{W-/ceribandin|eo|musido}}, {{W-/ceribandin|eo|musidino}}
* {{Z|et}}: {{W+/ceribandin|et|hiir}}
* {{Z|eve}}: {{W-/ceribandin|eve|ча̄ма̇кчан}}
* {{Z|evn}}: {{W-/ceribandin|evn|сиңэрэкэн}}
* {{Z|ee}}: {{W-/ceribandin|ee|afi|nt}}
* {{Z|fa}}: {{W+/ceribandin|fa|موش|tr=muš}}, {{W+/ceribandin|fa|موشک|tr=mušk}}
* {{Z|fo}}: {{W+/ceribandin|fo|mús|m}}
* {{Z|fi}}: {{W+/ceribandin|fi|hiiri}}
* {{Z|fr}}: {{W+/ceribandin|fr|souris|m}}
* {{Z|fur}}: {{W-/ceribandin|fur|surîs|m}}
* {{Z|gmw-fri}}:
*: {{Z|frr}}: {{W-/ceribandin|frr|müs|nt}}
*: {{Z|fy}}: {{W-/ceribandin|fy|mûs}}
* {{Z|gd}}: {{W-/ceribandin|gd|luch|m}}
* {{Z|gl}}: {{W+/ceribandin|gl|rato|n}}
* {{Z|gu}}: {{W-/ceribandin|gu|ઉંદર}}
* {{Z|ka}}: {{W+/ceribandin|ka|თაგვი|sc=Geor}}
* {{Z|haw}}: {{W-/ceribandin|haw|ʻiole liʻiliʻi}}
* {{Z|ha}}: {{W-/ceribandin|ha|ɓera}}
* {{Z|hi}}: {{W+/ceribandin|hi|चूहा|n|sc=Deva}}
* {{Z|hil}}: {{W-/ceribandin|hil|ilaga}}, {{W-/ceribandin|hil|balabaw}}
* {{Z|mww}}: {{W-/ceribandin|mww|nas}}
* {{Z|nl}}: {{W+/ceribandin|nl|muis|m}}
* {{Z|he}}: {{W+/ceribandin|he|עַכְבָּר|n|tr=akhbár|sc=Hebr}}
* {{Z|io}}: {{W+/ceribandin|io|muso}}
* {{Z|ig}}: {{W-/ceribandin|ig|ọke}}
* {{Z|ilo}}: {{W-/ceribandin|ilo|bao}}
* {{Z|ang}}: {{W-/ceribandin|ang|mūs}}
* {{Z|en}}: {{W+/ceribandin|en|mouse}}
* {{Z|inh}}: {{W-/ceribandin|inh|дахка}}
* {{Z|ia}}: {{W-/ceribandin|ia|mus}}, {{W-/ceribandin|ia|mure}}
* {{Z|ga}}: {{W+/ceribandin|ga|luch|m}}, {{W+/ceribandin|ga|luchóg|m}}
* {{Z|it}}: {{W+/ceribandin|it|topo|n}}, {{W+/ceribandin|it|sorcio|n}}, {{W+/ceribandin|it|ratto|n}}
* {{Z|is}}: {{W+/ceribandin|is|mús|m}}
* {{Z|ja}}: {{W+/ceribandin|ja|鼠|tr=ねずみ, nezumi}}, {{W+/ceribandin|ja|ネズミ|tr=nezumi}}, {{W+/ceribandin|ja|ハツカネズミ|tr=hatsukanezumi|sc=Jpan}}, {{W+/ceribandin|ja|マウス|tr=mausu|sc=Jpan}}
* {{Z|jra}}: {{W-/ceribandin|jra|tơkuih}}
* {{Z|jv}}: {{W+/ceribandin|jv|tikus}}
* {{Z|kl}}: {{W-/ceribandin|kl|teriaq}}
* {{Z|xal}}: {{W-/ceribandin|xal|хулһн|sc=Cyrl}}
* {{Z|kn}}: {{W+/ceribandin|kn|ಇಲಿ|sc=Knda}}
* {{Z|kr}}: {{W-/ceribandin|kr|jilwa}}
* {{Z|pam}}: {{W-/ceribandin|pam|dagis}}
* {{Z|krl}}: {{W-/ceribandin|krl|hiiri}}
* {{Z|kar}}:
*: {{Z|ksw}}: {{W-/ceribandin|ksw|ယုၢ်}}
* {{Z|csb}}: {{W-/ceribandin|csb|mësz|m}}
* {{Z|ca}}: {{W+/ceribandin|ca|ratolí|n}}
* {{Z|qu}}: {{W-/ceribandin|qu|ukuca}}, {{W-/ceribandin|qu|ukuş}}, {{W-/ceribandin|qu|juk'ucha}}
* {{Z|ky}}: {{W+/ceribandin|ky|чычкан|sc=Cyrl}}
* {{Z|si}}: {{W-/ceribandin|si|මීයා|sc=Sinh}}
* {{Z|rw}}: {{W-/ceribandin|rw|umushushwe|c3/4}}, {{W-/ceribandin|rw|imbeba|c9/10}}, {{W-/ceribandin|rw|im-beba}}
* {{Z|ko}}: {{W+/ceribandin|ko|생쥐}}, {{W+/ceribandin|ko|쥐}}
* {{Z|kw}}: {{W-/ceribandin|kw|logosen|m}}
* {{Z|rop}}: {{W-/ceribandin|rop|brudbrud}}
* {{Z|xug}}: {{W-/ceribandin|xug|っゑっちゅー|tr=wwecchū}}
* {{Z|fkv}}: {{W-/ceribandin|fkv|hiiri}}
* {{Z|ltg}}: {{W-/ceribandin|ltg|pele}}
* {{Z|la}}: {{W+/ceribandin|la|mūs|n|m}}
* {{Z|lv}}: {{W+/ceribandin|lv|pele|m}}
* {{Z|lo}}: {{W+/ceribandin|lo|ໜູ}}
* {{Z|lez}}: {{W-/ceribandin|lez|кьиф}}
* {{Z|lt}}: {{W+/ceribandin|lt|pelė|m}}, {{W-/ceribandin|lt|pelėnas|n}}
* {{Z|lmo}}: {{W-/ceribandin|lmo|ratt}}
* {{Z|khb}}: {{W-/ceribandin|khb|ᦐᦴ}}
* {{Z|lg}}: {{W-/ceribandin|lg|emmese}}
* {{Z|lb}}: {{W+/ceribandin|lb|Maus|m}}
* {{Z|lut}}: {{W-/ceribandin|lut|k'ʷat'ad}}
* {{Z|mk}}: {{W+/ceribandin|mk|глушец|n}}, {{W-/ceribandin|mk|глувче|nt}}, {{W-/ceribandin|mk|миш|n}}, {{W+/ceribandin|mk|глушец|n}}
* {{Z|ml}}: {{W+/ceribandin|ml|ചുണ്ടെലി|sc=Mlym}}
* {{Z|ms}}: {{W+/ceribandin|ms|tikus}}, {{W-/ceribandin|ms|mencit}}
* {{Z|mt}}: {{W-/ceribandin|mt|ġurdien|n}}
* {{Z|mnc}}: {{W-/ceribandin|mnc|ᠰᡳᠩᡤᡝᡵᡳ}}
* {{Z|gv}}: {{W-/ceribandin|gv|lugh|n}}
* {{Z|mi}}: {{W+/ceribandin|mi|kiore}}
* {{Z|mr}}: {{W-/ceribandin|mr|उंन्दिर|sc=Deva}}
* Marî:
*: {{Z|mhr}}: {{W-/ceribandin|mhr|коля}}
*: {{Z|mrj}}: {{W-/ceribandin|mrj|каля}}
* {{Z|mzn}}: {{W-/ceribandin|mzn|گل|tr=gal}}
* {{Z|hu}}: {{W+/ceribandin|hu|egér}}
* {{Z|egy}}: {{W-egy|pnw|h=p:n-nw-w-F27}}
* {{Z|mvi}}: {{W-/ceribandin|mvi|ゆむぬ|tr=yumunu}}
* {{Z|mic}}: {{W-/ceribandin|mic|apigji'j|an}}, {{W-/ceribandin|mic|apugji'j|an}}
* {{Z|mwl}}: {{W-/ceribandin|mwl|rato|n}}
* {{Z|mdf}}: {{W-/ceribandin|mdf|шеер}}
* {{Z|mn}}: {{W+/ceribandin|mn|хулгана|sc=Cyrl}}, {{W+/ceribandin|mn|оготно|sc=Cyrl}}
* {{Z|nah}}: {{W+/ceribandin|nah|quimichin}}
* {{Z|gld}}: {{W-/ceribandin|gld|сингэрэ}}
* {{Z|nap}}: {{W-/ceribandin|nap|sorece|n}}
* {{Z|nv}}: {{W-/ceribandin|nv|naʼatsʼǫǫsí}}
* {{Z|nds}}:
*: {{Z|nds-de}}: {{W-/ceribandin|nds-de|Muus|m}}
* {{Z|ne}}: {{W+/ceribandin|ne|मुसो|sc=Deva}}
* {{Z|niv}}: {{W-/ceribandin|niv|мухтук|sc=Cyrl}}
* {{Z|nrf}}: {{W-/ceribandin|nrf|souothis|m}}
* {{Z|no}}: {{W+/ceribandin|no|mus|m}}
* {{Z|oj}}: {{W-/ceribandin|oj|waawaabiganoojiinh}}
* {{Z|ryu}}: {{W-/ceribandin|ryu|ゑんちゅ|tr=wenchu}}
* {{Z|oc}}: {{W+/ceribandin|oc|mirga|m}}, {{W-/ceribandin|oc|fureta|m}}, {{W+/ceribandin|oc|ratuga|m}}
* {{Z|or}}: {{W+/ceribandin|or|ମୂଷା}}
* {{Z|osa}}: {{W-/ceribandin|osa|įchóka}}
* {{Z|os}}: {{W-/ceribandin|os|мыст|sc=Cyrl}}
* {{Z|ug}}: {{W-/ceribandin|ug|چاشقان|sc=ug-Arab}}
* {{Z|uz}}: {{W+/ceribandin|uz|sichqon}}
* {{Z|ps}}: {{W+/ceribandin|ps|موږک|n|tr=muģák|sc=ps-Arab}}
* {{Z|pdt}}: {{W-/ceribandin|pdt|Mus|m}}
* {{Z|pl}}: {{W+/ceribandin|pl|mysz|m}}, {{W+/ceribandin|pl|myszka|m}}
* {{Z|pt}}: {{W+/ceribandin|pt|rato|n}}, {{W+/ceribandin|pt|camundongo|n}}
* {{Z|prg}}: {{W-/ceribandin|prg|pelē}}
* {{Z|pa}}: {{W+/ceribandin|pa|ਚੂਹਾ}}
* {{Z|kk}}: {{W-/ceribandin|kk|тышқан|sc=Cyrl}}
* {{Z|rm}}: {{W-/ceribandin|rm|mieur}}
* {{Z|ro}}: {{W+/ceribandin|ro|șoarece|n}}
* {{Z|ru}}: {{W+/ceribandin|ru|мышь|m}}, {{W+/ceribandin|ru|мы́шка|m}}
* {{Z|skb}}: {{W-/ceribandin|skb|หนู่}}
* {{Z|smi}}:
*: {{Z|se}}: {{W-/ceribandin|se|sáhpán}}
*: {{Z|smn}}: {{W-/ceribandin|smn|säplig}}
*: {{Z|sms}}: {{W-/ceribandin|sms|sä´ppli}}
* {{Z|sm}}: {{W-/ceribandin|sm|'isumu}}, {{W-/ceribandin|sm|'iole}}
* {{Z|sa}}: {{W+/ceribandin|sa|मूष्|n|m}}, {{W+/ceribandin|sa|आखु|n}}
* {{Z|sc}}: {{W-/ceribandin|sc|tope}}, {{W-/ceribandin|sc|topi}}
*: {{Z|sc}}: {{W-/ceribandin|sc|medrona|m}}, {{W-/ceribandin|sc|topi}}, {{W-/ceribandin|sc|sórixi|n}}
* {{Z|ceb}}: {{W-/ceribandin|ceb|ilaga}}
* {{Z|scn}}: {{W+/ceribandin|scn|surci|n}}, {{W+/ceribandin|scn|sùrici|n}}
* {{Z|sd}}: {{W-/ceribandin|sd|ڪوئو|tr=kūʾō|cuda=ڪوُئو|sc=sd-Arab}}
* {{Z|sh}}:
*: Kirîlî: {{W-/ceribandin|sh|ми̏ш|n}}
*: Latînî: {{W+/ceribandin|sh|mȉš|n}}
* {{Z|sco}}: {{W-/ceribandin|sco|moose}}
* {{Z|cu}}:
*: Kirîlî: {{W-/ceribandin|cu|мꙑшь|m}}
* {{Z|orv}}: {{W-/ceribandin|orv|мꙑшь|m}}
* {{Z|sk}}: {{W+/ceribandin|sk|myš|m}}
* {{Z|sl}}: {{W+/ceribandin|sl|miš|m}}
* {{Z|so}}: {{W+/ceribandin|so|jiir|n}}
* {{Z|ckb}}: {{W+|ckb|test}}
* Sorbî:
*: {{Z|hsb}}: {{W-/ceribandin|hsb|myš|m}}, {{W-/ceribandin|hsb|myška|m}}
*: {{Z|dsb}}: {{W-/ceribandin|dsb|myš|m}}, {{W-/ceribandin|dsb|myška|}}
* {{Z|st}}: {{W+/ceribandin|st|tweba}}
* {{Z|es}}: {{W+/ceribandin|es|ratón|n}}, {{W+/ceribandin|es|mur|n}}
* {{Z|sw}}: {{W+/ceribandin|sw|kipanya}}
* {{Z|sv}}: {{W+/ceribandin|sv|mus|g}}
* {{Z|shn}}: {{W-/ceribandin|shn|ၼူ}}
* {{Z|tg}}: {{W+/ceribandin|tg|муш|sc=Cyrl}}
* {{Z|tl}}: {{W+/ceribandin|tl|daga}}
* {{Z|tht}}: {{W-/ceribandin|tht|dlūne}}
* {{Z|tly}}: {{W-/ceribandin|tly|морә|tr=morǝ}}
* {{Z|ta}}: {{W+/ceribandin|ta|சுண்டெலி|sc=Taml}}
* {{Z|twf}}: {{W-/ceribandin|twf|cìyúna}}
* {{Z|roa-tar}}: {{W-/ceribandin|roa-tar|topo|n}}
* {{Z|blt}}: {{W-/ceribandin|blt|ꪘꪴ}}
* {{Z|th}}: {{W+/ceribandin|th|หนู}}
*: {{Z|nod}}: {{W-/ceribandin|nod|ᩉ᩠ᨶᩪ}}
* {{Z|te}}: {{W+/ceribandin|te|ఎలుక|sc=Telu}}
* {{Z|crh}}: {{W-/ceribandin|crh|sıçan}}
* {{Z|tt}}: {{W+/ceribandin|tt|тычкан|sc=Cyrl}}
* {{Z|ti}}: {{W-/ceribandin|ti|ኣንጭዋ}}
* {{Z|tr}}: {{W+/ceribandin|tr|fare}}, {{W+/ceribandin|tr|sıçan}}
*: {{Z|ota}}: {{W-/ceribandin|ota|صیچان|tr=sıçan|sc=ota-Arab}}, {{W-/ceribandin|ota|فأرة|tr=fare|sc=ota-Arab}}, {{W-/ceribandin|ota|فأر|tr=far|sc=ota-Arab}}, {{W-/ceribandin|ota|موش|tr=muş}}
* {{Z|tk}}: {{W-/ceribandin|tk|syçan}}, {{W-/ceribandin|tk|sıçan}}
* {{Z|bo}}: {{W-/ceribandin|bo|ཙི་ཙི|sc=Tibt}}
* {{Z|tli}}: {{W-/ceribandin|tli|kagaák}}
* {{Z|tpi}}: {{W-/ceribandin|tpi|liklik rat}}
* {{Z|to}}: {{W-/ceribandin|to|kumā}}
* {{Z|tn}}: {{W-/ceribandin|tn|pêba}}
* {{Z|tum}}: {{W-/ceribandin|tum|mbewa}}
* {{Z|udm}}: {{W-/ceribandin|udm|шыр}}
* {{Z|ur}}: {{W-/ceribandin|ur|چوہا|n|tr=cūhā|sc=ur-Arab}}
* {{Z|uk}}: {{W+/ceribandin|uk|ми́ша|m}}, {{W-/ceribandin|uk|миш|m}}, {{W-/ceribandin|uk|ми́шка|m}}, {{W-/ceribandin|uk|миша́к|n}}
* {{Z|vec}}: {{W-/ceribandin|vec|ràto}}, {{W-/ceribandin|vec|sorze}}
* {{Z|vi}}: {{W+/ceribandin|vi|chuột}}
* {{Z|vo}}: {{W+/ceribandin|vo|mug}}
* {{Z|wa}}: {{W+/ceribandin|wa|sori|m}}
* {{Z|cy}}: {{W+/ceribandin|cy|llygoden|m}}, {{W+/ceribandin|cy|llygod|p}}
* {{Z|wym}}: {{W-/ceribandin|wym|maojs}}
* {{Z|wo}}: {{W-/ceribandin|wo|janax|cuda=janax ji}}
* {{Z|km}}: {{W-/ceribandin|km|កណ្ដុរស្រូវ}}, {{W+/ceribandin|km|កណ្ដុរ}}
* Kipchak:
*: {{Z|qwm}}: {{W-/ceribandin|qwm|սըչխան|tr=sïč̣χan}}
* {{Z|xh}}: {{W-/ceribandin|xh|ilifu}}
* {{Z|rys}}: {{W-/ceribandin|rys|うぇんちゅ|tr=Wenchu}}
* {{Z|yai}}: {{W-/ceribandin|yai|муш}}
* {{Z|sah}}: {{W-/ceribandin|sah|кутуйах|sc=Cyrl}}
* {{Z|yag}}: {{W-/ceribandin|yag|wasána}}
* {{Z|yi}}: {{W-/ceribandin|yi|מויז|sc=Hebr}}
* {{Z|yoi}}: {{W-/ceribandin|yoi|うやんとぅ|tr=uyantu}}
* {{Z|yo}}: {{W-/ceribandin|yo|èkúté}}, {{W-/ceribandin|yo|eliri}}
* {{Z|el}}: {{W+/ceribandin|el|ποντίκι|nt}}, {{W+/ceribandin|el|ποντικός|n|sc=Grek}}
*: {{Z|grc}}: {{W-/ceribandin|grc|μῦς|n}}, {{W-/ceribandin|grc|ποντικός|n}}
* {{Z|za}}: {{W-/ceribandin|za|nou}}
* {{Z|zu}}: {{W+/ceribandin|zu|igundane|c5|c6}}
{{werger-bin/ceribandin}}
:{{werger çavkanî|en|mouse|oldid=75626351|beş=Ev qismê Wergerê}}
{{werger-ser|(guhandar) Heywanekî biçûk yê kirîner e û dişibe cirdan lê hûriktir e.}}
* {{Z|sh}}: {{W|sh|twar}}
{{werger-bin}}
:{{werger çavkanî|en|meadow|beş=Ev qismê Wergerê|oldid=75926579}}
kenylwiqd5nra2y7k3qwv00vvgr3meg
6865527
6865526
2026-10-23T19:26:20Z
Wikihez
7344
Kurmancî/Navdêr: qutiyeke tercimeyê hate zêdekirin, +fransî: [[test]] (DefSideBoxes)
6865527
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
=== Navdêr ===
[[Wêne:Micromys minutus (aka).jpg|thumb|Mişkek (1)]]
[[Wêne:3-Tasten-Maus Microsoft.jpg|thumb|'''Mişkekî''' kompûterê (2)]]
{{wp|Mişk (cudakirin)|Mişk}}
{{navdêr|ku|z=n}}
# {{f|ku|guhandar}} [[heywan|Heywanekî]] [[biçûk]] yê [[kirîner]] e û [[şibîn|dişibe]] [[cird]]an lê [[hûrik]]tir e.
#: {{mk|ku|Kitik ne li mal e, navê '''mişkî''' Evdal e.}}
# {{maneid|ku|informatîk}} {{f|ku|informatîk}} [[amûr|Amûrekî]] [[kompûter]]ê ye ku mirov pê [[nivîs]] û [[grafîk]]an [[livandin|dilivîne]].
==== Tewandin ====
{{ku-tewîn-nav|nêr}}
==== Bi alfabeyên din ====
* {{ku-ar|مشک}}
* {{ku-kr|мьшк}}
==== Formên alternatîv ====
* {{hw|ku|mihşk}}
==== Jê ====
{{kol3|ku
| mişkane
| mişke
| mişko
| mişkok
}}
==== Bide ber ====
{{kol3|ku
| meşk
| misk
| hişk
| pişk
| rişk
}}
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-/ceribandin|ckb|mişk|sc=Latn}}
* {{Z|sdh}}: {{W-/ceribandin|sdh|müş|sc=Latn}}
* {{Z|lki}}: {{W-/ceribandin|lki|müş|sc=Latn}}
* {{Z|hac}}: {{W-/ceribandin|hac|miłe|sc=Latn}}
* {{Z|zza}}: {{W+/ceribandin|zza|merre}}
==== Werger ====
{{werger-ser/ceribandin|Test çi dike}}
* {{Z|ab}}: {{W-/ceribandin|ab|аҳәынаҧ|sc=Cyrl}}
* {{Z|af}}: {{W+/ceribandin|af|muis}}
* {{Z|sq}}: {{W+/ceribandin|sq|mi|n}}
* {{Z|de}}: {{W+/ceribandin|de|Maus|m}}
*: {{Z|gsw}}: {{W-/ceribandin|gsw|Muus|m}}
* {{Z|am}}: {{W-/ceribandin|am|አይጥ|sc=Ethi}}
* {{Z|ani}}: {{W-/ceribandin|ani|гьинкӏкӏу}}
* {{Z|an}}: {{W-/ceribandin|an|zorz}}
* {{Z|rup}}: {{W-/ceribandin|rup|shoaric}}
* {{Z|aer}}: {{W-/ceribandin|aer|thakwere}}
* {{Z|as}}: {{W-/ceribandin|as|নিগনি}}
* {{Z|ast}}: {{W+/ceribandin|ast|ratu|n}}, {{W+/ceribandin|ast|ratón|n}}, {{W+/ceribandin|ast|mur}}
* {{Z|tay}}: {{W-/ceribandin|tay|olit}}
* {{Z|av}}: {{W-/ceribandin|av|гӏункӏкӏ|sc=Cyrl}}
* {{Z|ay}}: {{W+/ceribandin|ay|achaku}}
* {{Z|ain}}: {{W-/ceribandin|ain|エㇾㇺ|tr=erm}}, {{W-/ceribandin|ain|エㇾム|tr=ermu}}, {{W-/ceribandin|ain|エルㇺ|tr=erum}}
* {{Z|az}}: {{W+/ceribandin|az|siçan}}
* {{Z|ban}}: {{W-/ceribandin|ban|bikul}}, {{W-/ceribandin|ban|jero ketut}}
* {{Z|eu}}: {{W+/ceribandin|eu|sagu}}
* {{Z|ba}}: {{W-/ceribandin|ba|сысҡан|sc=Cyrl}}
* {{Z|be}}: {{W-/ceribandin|be|мыш|m}}, {{W-/ceribandin|be|мы́шка|m}}
* {{Z|bal}}: {{W-/ceribandin|bal|مشک|tr=mušk}}, {{W-/ceribandin|bal|موشک|tr=múšk}}
* {{Z|bn}}: {{W+/ceribandin|bn|ইঁদুর|sc=Beng}}
* {{Z|ber}}:
*: {{Z|rif}}: {{W-/ceribandin|rif|aɣerḍa|n}}
* {{Z|br}}: {{W+/ceribandin|br|logodenn|m}}
* {{Z|bg}}: {{W+/ceribandin|bg|ми́шка|m}}, {{W-/ceribandin|bg|мишо́к|n}}
* {{Z|my}}: {{W+/ceribandin|my|ကြွက်|sc=Mymr}}
* {{Z|pcc}}: {{W-/ceribandin|pcc|ranz}}, {{W-/ceribandin|pcc|waauc}}
* {{Z|ccc}}: {{W-/ceribandin|ccc|kijsili}}
* {{Z|ce}}: {{W-/ceribandin|ce|дахка}}
* {{Z|cs}}: {{W+/ceribandin|cs|myš|m}}, {{W+/ceribandin|cs|myšák|n}}
* {{Z|chr}}: {{W-/ceribandin|chr|ᏥᏍᏕᏥ}}
* {{Z|ny}}: {{W-/ceribandin|ny|mbewa}}
* {{Z|cic}}: {{W-/ceribandin|cic|pinti'}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-/ceribandin|yue|老鼠|tr=lou5 syu2}}
*: {{Z|dng}}: {{W-/ceribandin|dng|лочў}}
*: {{Z|hak}}: {{W-/ceribandin|hak|老鼠|tr=lo-chhú, ló-chhú, no-chhú}}
*: {{Z|lzh}}: {{W-/ceribandin|lzh|鼠|tr=shǔ}}, {{W-/ceribandin|lzh|鼷|tr=xī}}
*: {{Z|cmn}}: {{W+/ceribandin|cmn|老鼠|tr=lǎoshǔ}}, {{W+/ceribandin|cmn|鼠|tr=shǔ}}, {{W+/ceribandin|cmn|耗子|tr=hàozi}}, {{W+/ceribandin|cmn|小鼠|tr=xiǎoshǔ}}, {{W+/ceribandin|cmn|鼷|tr=xī|guman=erê}}, {{W+|cmn|test}}
*: {{Z|cdo}}: {{W-/ceribandin|cdo|老鼠|tr=lō̤-chṳ̄}}
*: {{Z|nan}}: {{W+/ceribandin|nan|鳥鼠|tr=niáu-chhí, niáu-chhú}}, {{W+/ceribandin|nan|鼠|tr=chhí, chhú}}, {{W+/ceribandin|nan|錢鼠|tr=chîⁿ-chhí, chîⁿ-chhú}}, {{W-/ceribandin|nan|老鼠|tr=ló͘-chhír, ló͘-chhí, niáu-chhú, niáu-chhí, niáu-chhír}}
*: {{Z|wuu}}: {{W-/ceribandin|wuu|老鼠}}
* {{Z|ckt}}: {{W-/ceribandin|ckt|пипиӄыԓгын|tr=pipiqyḷgyn}}
* {{Z|cv}}: {{W-/ceribandin|cv|шӑши|sc=Cyrl}}
* {{Z|dlm}}: {{W-/ceribandin|dlm|surco|n}}
* {{Z|da}}: {{W+/ceribandin|da|mus|g}}
* {{Z|dv}}: {{W-/ceribandin|dv|މީދާ|sc=Thaa}}
* {{Z|id}}: {{W+/ceribandin|id|tikus}}
* {{Z|ar}}: {{W-/ceribandin|ar|فَأْرَة|m}}, {{W-/ceribandin|ar|فَأْر|n}}
*: {{Z|arz}}: {{W-/ceribandin|arz|فار|tr=fār}}
*: {{Z|ary}}: {{W-/ceribandin|ary|فار|tr=fār}}
* {{Z|hy}}: {{W+/ceribandin|hy|մուկ}}
*: {{Z|xcl}}: {{W-/ceribandin|xcl|մուկն|sc=Armn}}
* {{Z|myv}}: {{W-/ceribandin|myv|чеерь}}
* {{Z|eo}}: {{W+/ceribandin|eo|muso}}, {{W-/ceribandin|eo|musino}}, {{W-/ceribandin|eo|musido}}, {{W-/ceribandin|eo|musidino}}
* {{Z|et}}: {{W+/ceribandin|et|hiir}}
* {{Z|eve}}: {{W-/ceribandin|eve|ча̄ма̇кчан}}
* {{Z|evn}}: {{W-/ceribandin|evn|сиңэрэкэн}}
* {{Z|ee}}: {{W-/ceribandin|ee|afi|nt}}
* {{Z|fa}}: {{W+/ceribandin|fa|موش|tr=muš}}, {{W+/ceribandin|fa|موشک|tr=mušk}}
* {{Z|fo}}: {{W+/ceribandin|fo|mús|m}}
* {{Z|fi}}: {{W+/ceribandin|fi|hiiri}}
* {{Z|fr}}: {{W+/ceribandin|fr|souris|m}}
* {{Z|fur}}: {{W-/ceribandin|fur|surîs|m}}
* {{Z|gmw-fri}}:
*: {{Z|frr}}: {{W-/ceribandin|frr|müs|nt}}
*: {{Z|fy}}: {{W-/ceribandin|fy|mûs}}
* {{Z|gd}}: {{W-/ceribandin|gd|luch|m}}
* {{Z|gl}}: {{W+/ceribandin|gl|rato|n}}
* {{Z|gu}}: {{W-/ceribandin|gu|ઉંદર}}
* {{Z|ka}}: {{W+/ceribandin|ka|თაგვი|sc=Geor}}
* {{Z|haw}}: {{W-/ceribandin|haw|ʻiole liʻiliʻi}}
* {{Z|ha}}: {{W-/ceribandin|ha|ɓera}}
* {{Z|hi}}: {{W+/ceribandin|hi|चूहा|n|sc=Deva}}
* {{Z|hil}}: {{W-/ceribandin|hil|ilaga}}, {{W-/ceribandin|hil|balabaw}}
* {{Z|mww}}: {{W-/ceribandin|mww|nas}}
* {{Z|nl}}: {{W+/ceribandin|nl|muis|m}}
* {{Z|he}}: {{W+/ceribandin|he|עַכְבָּר|n|tr=akhbár|sc=Hebr}}
* {{Z|io}}: {{W+/ceribandin|io|muso}}
* {{Z|ig}}: {{W-/ceribandin|ig|ọke}}
* {{Z|ilo}}: {{W-/ceribandin|ilo|bao}}
* {{Z|ang}}: {{W-/ceribandin|ang|mūs}}
* {{Z|en}}: {{W+/ceribandin|en|mouse}}
* {{Z|inh}}: {{W-/ceribandin|inh|дахка}}
* {{Z|ia}}: {{W-/ceribandin|ia|mus}}, {{W-/ceribandin|ia|mure}}
* {{Z|ga}}: {{W+/ceribandin|ga|luch|m}}, {{W+/ceribandin|ga|luchóg|m}}
* {{Z|it}}: {{W+/ceribandin|it|topo|n}}, {{W+/ceribandin|it|sorcio|n}}, {{W+/ceribandin|it|ratto|n}}
* {{Z|is}}: {{W+/ceribandin|is|mús|m}}
* {{Z|ja}}: {{W+/ceribandin|ja|鼠|tr=ねずみ, nezumi}}, {{W+/ceribandin|ja|ネズミ|tr=nezumi}}, {{W+/ceribandin|ja|ハツカネズミ|tr=hatsukanezumi|sc=Jpan}}, {{W+/ceribandin|ja|マウス|tr=mausu|sc=Jpan}}
* {{Z|jra}}: {{W-/ceribandin|jra|tơkuih}}
* {{Z|jv}}: {{W+/ceribandin|jv|tikus}}
* {{Z|kl}}: {{W-/ceribandin|kl|teriaq}}
* {{Z|xal}}: {{W-/ceribandin|xal|хулһн|sc=Cyrl}}
* {{Z|kn}}: {{W+/ceribandin|kn|ಇಲಿ|sc=Knda}}
* {{Z|kr}}: {{W-/ceribandin|kr|jilwa}}
* {{Z|pam}}: {{W-/ceribandin|pam|dagis}}
* {{Z|krl}}: {{W-/ceribandin|krl|hiiri}}
* {{Z|kar}}:
*: {{Z|ksw}}: {{W-/ceribandin|ksw|ယုၢ်}}
* {{Z|csb}}: {{W-/ceribandin|csb|mësz|m}}
* {{Z|ca}}: {{W+/ceribandin|ca|ratolí|n}}
* {{Z|qu}}: {{W-/ceribandin|qu|ukuca}}, {{W-/ceribandin|qu|ukuş}}, {{W-/ceribandin|qu|juk'ucha}}
* {{Z|ky}}: {{W+/ceribandin|ky|чычкан|sc=Cyrl}}
* {{Z|si}}: {{W-/ceribandin|si|මීයා|sc=Sinh}}
* {{Z|rw}}: {{W-/ceribandin|rw|umushushwe|c3/4}}, {{W-/ceribandin|rw|imbeba|c9/10}}, {{W-/ceribandin|rw|im-beba}}
* {{Z|ko}}: {{W+/ceribandin|ko|생쥐}}, {{W+/ceribandin|ko|쥐}}
* {{Z|kw}}: {{W-/ceribandin|kw|logosen|m}}
* {{Z|rop}}: {{W-/ceribandin|rop|brudbrud}}
* {{Z|xug}}: {{W-/ceribandin|xug|っゑっちゅー|tr=wwecchū}}
* {{Z|fkv}}: {{W-/ceribandin|fkv|hiiri}}
* {{Z|ltg}}: {{W-/ceribandin|ltg|pele}}
* {{Z|la}}: {{W+/ceribandin|la|mūs|n|m}}
* {{Z|lv}}: {{W+/ceribandin|lv|pele|m}}
* {{Z|lo}}: {{W+/ceribandin|lo|ໜູ}}
* {{Z|lez}}: {{W-/ceribandin|lez|кьиф}}
* {{Z|lt}}: {{W+/ceribandin|lt|pelė|m}}, {{W-/ceribandin|lt|pelėnas|n}}
* {{Z|lmo}}: {{W-/ceribandin|lmo|ratt}}
* {{Z|khb}}: {{W-/ceribandin|khb|ᦐᦴ}}
* {{Z|lg}}: {{W-/ceribandin|lg|emmese}}
* {{Z|lb}}: {{W+/ceribandin|lb|Maus|m}}
* {{Z|lut}}: {{W-/ceribandin|lut|k'ʷat'ad}}
* {{Z|mk}}: {{W+/ceribandin|mk|глушец|n}}, {{W-/ceribandin|mk|глувче|nt}}, {{W-/ceribandin|mk|миш|n}}, {{W+/ceribandin|mk|глушец|n}}
* {{Z|ml}}: {{W+/ceribandin|ml|ചുണ്ടെലി|sc=Mlym}}
* {{Z|ms}}: {{W+/ceribandin|ms|tikus}}, {{W-/ceribandin|ms|mencit}}
* {{Z|mt}}: {{W-/ceribandin|mt|ġurdien|n}}
* {{Z|mnc}}: {{W-/ceribandin|mnc|ᠰᡳᠩᡤᡝᡵᡳ}}
* {{Z|gv}}: {{W-/ceribandin|gv|lugh|n}}
* {{Z|mi}}: {{W+/ceribandin|mi|kiore}}
* {{Z|mr}}: {{W-/ceribandin|mr|उंन्दिर|sc=Deva}}
* Marî:
*: {{Z|mhr}}: {{W-/ceribandin|mhr|коля}}
*: {{Z|mrj}}: {{W-/ceribandin|mrj|каля}}
* {{Z|mzn}}: {{W-/ceribandin|mzn|گل|tr=gal}}
* {{Z|hu}}: {{W+/ceribandin|hu|egér}}
* {{Z|egy}}: {{W-egy|pnw|h=p:n-nw-w-F27}}
* {{Z|mvi}}: {{W-/ceribandin|mvi|ゆむぬ|tr=yumunu}}
* {{Z|mic}}: {{W-/ceribandin|mic|apigji'j|an}}, {{W-/ceribandin|mic|apugji'j|an}}
* {{Z|mwl}}: {{W-/ceribandin|mwl|rato|n}}
* {{Z|mdf}}: {{W-/ceribandin|mdf|шеер}}
* {{Z|mn}}: {{W+/ceribandin|mn|хулгана|sc=Cyrl}}, {{W+/ceribandin|mn|оготно|sc=Cyrl}}
* {{Z|nah}}: {{W+/ceribandin|nah|quimichin}}
* {{Z|gld}}: {{W-/ceribandin|gld|сингэрэ}}
* {{Z|nap}}: {{W-/ceribandin|nap|sorece|n}}
* {{Z|nv}}: {{W-/ceribandin|nv|naʼatsʼǫǫsí}}
* {{Z|nds}}:
*: {{Z|nds-de}}: {{W-/ceribandin|nds-de|Muus|m}}
* {{Z|ne}}: {{W+/ceribandin|ne|मुसो|sc=Deva}}
* {{Z|niv}}: {{W-/ceribandin|niv|мухтук|sc=Cyrl}}
* {{Z|nrf}}: {{W-/ceribandin|nrf|souothis|m}}
* {{Z|no}}: {{W+/ceribandin|no|mus|m}}
* {{Z|oj}}: {{W-/ceribandin|oj|waawaabiganoojiinh}}
* {{Z|ryu}}: {{W-/ceribandin|ryu|ゑんちゅ|tr=wenchu}}
* {{Z|oc}}: {{W+/ceribandin|oc|mirga|m}}, {{W-/ceribandin|oc|fureta|m}}, {{W+/ceribandin|oc|ratuga|m}}
* {{Z|or}}: {{W+/ceribandin|or|ମୂଷା}}
* {{Z|osa}}: {{W-/ceribandin|osa|įchóka}}
* {{Z|os}}: {{W-/ceribandin|os|мыст|sc=Cyrl}}
* {{Z|ug}}: {{W-/ceribandin|ug|چاشقان|sc=ug-Arab}}
* {{Z|uz}}: {{W+/ceribandin|uz|sichqon}}
* {{Z|ps}}: {{W+/ceribandin|ps|موږک|n|tr=muģák|sc=ps-Arab}}
* {{Z|pdt}}: {{W-/ceribandin|pdt|Mus|m}}
* {{Z|pl}}: {{W+/ceribandin|pl|mysz|m}}, {{W+/ceribandin|pl|myszka|m}}
* {{Z|pt}}: {{W+/ceribandin|pt|rato|n}}, {{W+/ceribandin|pt|camundongo|n}}
* {{Z|prg}}: {{W-/ceribandin|prg|pelē}}
* {{Z|pa}}: {{W+/ceribandin|pa|ਚੂਹਾ}}
* {{Z|kk}}: {{W-/ceribandin|kk|тышқан|sc=Cyrl}}
* {{Z|rm}}: {{W-/ceribandin|rm|mieur}}
* {{Z|ro}}: {{W+/ceribandin|ro|șoarece|n}}
* {{Z|ru}}: {{W+/ceribandin|ru|мышь|m}}, {{W+/ceribandin|ru|мы́шка|m}}
* {{Z|skb}}: {{W-/ceribandin|skb|หนู่}}
* {{Z|smi}}:
*: {{Z|se}}: {{W-/ceribandin|se|sáhpán}}
*: {{Z|smn}}: {{W-/ceribandin|smn|säplig}}
*: {{Z|sms}}: {{W-/ceribandin|sms|sä´ppli}}
* {{Z|sm}}: {{W-/ceribandin|sm|'isumu}}, {{W-/ceribandin|sm|'iole}}
* {{Z|sa}}: {{W+/ceribandin|sa|मूष्|n|m}}, {{W+/ceribandin|sa|आखु|n}}
* {{Z|sc}}: {{W-/ceribandin|sc|tope}}, {{W-/ceribandin|sc|topi}}
*: {{Z|sc}}: {{W-/ceribandin|sc|medrona|m}}, {{W-/ceribandin|sc|topi}}, {{W-/ceribandin|sc|sórixi|n}}
* {{Z|ceb}}: {{W-/ceribandin|ceb|ilaga}}
* {{Z|scn}}: {{W+/ceribandin|scn|surci|n}}, {{W+/ceribandin|scn|sùrici|n}}
* {{Z|sd}}: {{W-/ceribandin|sd|ڪوئو|tr=kūʾō|cuda=ڪوُئو|sc=sd-Arab}}
* {{Z|sh}}:
*: Kirîlî: {{W-/ceribandin|sh|ми̏ш|n}}
*: Latînî: {{W+/ceribandin|sh|mȉš|n}}
* {{Z|sco}}: {{W-/ceribandin|sco|moose}}
* {{Z|cu}}:
*: Kirîlî: {{W-/ceribandin|cu|мꙑшь|m}}
* {{Z|orv}}: {{W-/ceribandin|orv|мꙑшь|m}}
* {{Z|sk}}: {{W+/ceribandin|sk|myš|m}}
* {{Z|sl}}: {{W+/ceribandin|sl|miš|m}}
* {{Z|so}}: {{W+/ceribandin|so|jiir|n}}
* {{Z|ckb}}: {{W+|ckb|test}}
* Sorbî:
*: {{Z|hsb}}: {{W-/ceribandin|hsb|myš|m}}, {{W-/ceribandin|hsb|myška|m}}
*: {{Z|dsb}}: {{W-/ceribandin|dsb|myš|m}}, {{W-/ceribandin|dsb|myška|}}
* {{Z|st}}: {{W+/ceribandin|st|tweba}}
* {{Z|es}}: {{W+/ceribandin|es|ratón|n}}, {{W+/ceribandin|es|mur|n}}
* {{Z|sw}}: {{W+/ceribandin|sw|kipanya}}
* {{Z|sv}}: {{W+/ceribandin|sv|mus|g}}
* {{Z|shn}}: {{W-/ceribandin|shn|ၼူ}}
* {{Z|tg}}: {{W+/ceribandin|tg|муш|sc=Cyrl}}
* {{Z|tl}}: {{W+/ceribandin|tl|daga}}
* {{Z|tht}}: {{W-/ceribandin|tht|dlūne}}
* {{Z|tly}}: {{W-/ceribandin|tly|морә|tr=morǝ}}
* {{Z|ta}}: {{W+/ceribandin|ta|சுண்டெலி|sc=Taml}}
* {{Z|twf}}: {{W-/ceribandin|twf|cìyúna}}
* {{Z|roa-tar}}: {{W-/ceribandin|roa-tar|topo|n}}
* {{Z|blt}}: {{W-/ceribandin|blt|ꪘꪴ}}
* {{Z|th}}: {{W+/ceribandin|th|หนู}}
*: {{Z|nod}}: {{W-/ceribandin|nod|ᩉ᩠ᨶᩪ}}
* {{Z|te}}: {{W+/ceribandin|te|ఎలుక|sc=Telu}}
* {{Z|crh}}: {{W-/ceribandin|crh|sıçan}}
* {{Z|tt}}: {{W+/ceribandin|tt|тычкан|sc=Cyrl}}
* {{Z|ti}}: {{W-/ceribandin|ti|ኣንጭዋ}}
* {{Z|tr}}: {{W+/ceribandin|tr|fare}}, {{W+/ceribandin|tr|sıçan}}
*: {{Z|ota}}: {{W-/ceribandin|ota|صیچان|tr=sıçan|sc=ota-Arab}}, {{W-/ceribandin|ota|فأرة|tr=fare|sc=ota-Arab}}, {{W-/ceribandin|ota|فأر|tr=far|sc=ota-Arab}}, {{W-/ceribandin|ota|موش|tr=muş}}
* {{Z|tk}}: {{W-/ceribandin|tk|syçan}}, {{W-/ceribandin|tk|sıçan}}
* {{Z|bo}}: {{W-/ceribandin|bo|ཙི་ཙི|sc=Tibt}}
* {{Z|tli}}: {{W-/ceribandin|tli|kagaák}}
* {{Z|tpi}}: {{W-/ceribandin|tpi|liklik rat}}
* {{Z|to}}: {{W-/ceribandin|to|kumā}}
* {{Z|tn}}: {{W-/ceribandin|tn|pêba}}
* {{Z|tum}}: {{W-/ceribandin|tum|mbewa}}
* {{Z|udm}}: {{W-/ceribandin|udm|шыр}}
* {{Z|ur}}: {{W-/ceribandin|ur|چوہا|n|tr=cūhā|sc=ur-Arab}}
* {{Z|uk}}: {{W+/ceribandin|uk|ми́ша|m}}, {{W-/ceribandin|uk|миш|m}}, {{W-/ceribandin|uk|ми́шка|m}}, {{W-/ceribandin|uk|миша́к|n}}
* {{Z|vec}}: {{W-/ceribandin|vec|ràto}}, {{W-/ceribandin|vec|sorze}}
* {{Z|vi}}: {{W+/ceribandin|vi|chuột}}
* {{Z|vo}}: {{W+/ceribandin|vo|mug}}
* {{Z|wa}}: {{W+/ceribandin|wa|sori|m}}
* {{Z|cy}}: {{W+/ceribandin|cy|llygoden|m}}, {{W+/ceribandin|cy|llygod|p}}
* {{Z|wym}}: {{W-/ceribandin|wym|maojs}}
* {{Z|wo}}: {{W-/ceribandin|wo|janax|cuda=janax ji}}
* {{Z|km}}: {{W-/ceribandin|km|កណ្ដុរស្រូវ}}, {{W+/ceribandin|km|កណ្ដុរ}}
* Kipchak:
*: {{Z|qwm}}: {{W-/ceribandin|qwm|սըչխան|tr=sïč̣χan}}
* {{Z|xh}}: {{W-/ceribandin|xh|ilifu}}
* {{Z|rys}}: {{W-/ceribandin|rys|うぇんちゅ|tr=Wenchu}}
* {{Z|yai}}: {{W-/ceribandin|yai|муш}}
* {{Z|sah}}: {{W-/ceribandin|sah|кутуйах|sc=Cyrl}}
* {{Z|yag}}: {{W-/ceribandin|yag|wasána}}
* {{Z|yi}}: {{W-/ceribandin|yi|מויז|sc=Hebr}}
* {{Z|yoi}}: {{W-/ceribandin|yoi|うやんとぅ|tr=uyantu}}
* {{Z|yo}}: {{W-/ceribandin|yo|èkúté}}, {{W-/ceribandin|yo|eliri}}
* {{Z|el}}: {{W+/ceribandin|el|ποντίκι|nt}}, {{W+/ceribandin|el|ποντικός|n|sc=Grek}}
*: {{Z|grc}}: {{W-/ceribandin|grc|μῦς|n}}, {{W-/ceribandin|grc|ποντικός|n}}
* {{Z|za}}: {{W-/ceribandin|za|nou}}
* {{Z|zu}}: {{W+/ceribandin|zu|igundane|c5|c6}}
{{werger-bin/ceribandin}}
:{{werger çavkanî|en|mouse|oldid=75626351|beş=Ev qismê Wergerê}}
{{werger-ser|(guhandar) Heywanekî biçûk yê kirîner e û dişibe cirdan lê hûriktir e.}}
* {{Z|sh}}: {{W|sh|twar}}
{{werger-bin}}
:{{werger çavkanî|en|meadow|beş=Ev qismê Wergerê|oldid=75926579}}
{{werger-ser|test}}
* {{Z|fr}}: {{W+|fr|test|m}}
{{werger-bin}}
3sg9eva2xim52csypz1fhd4ermfonkn
MediaWiki:Gadget-wikt.translation-adder.js
8
1359496
6865549
6865471
2026-10-23T20:09:00Z
Wikihez
7344
Language field: wikt.edit's shared dropdown (same matching and names as before)
6865549
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.translation-adder.js]]
*
* Adds a form under each translation box ({{werger-ser}} … {{werger-bin}}) to add a line such as
* * {{Z|de}}: {{W+|de|Wasser|n}}
* in Kurmancî alphabetical order, and a ± link on the box heading to change its gloss. Forked from
* en.wiktionary's [[en:MediaWiki:Gadget-TranslationAdder.js]] (by [[en:User:Conrad.Irwin]] and others).
*
* DefSideBoxes ([[MediaWiki:Gadget-wikt.dsb.js]]) loads it from the pencil on a Werger heading and calls
* attach(root, editor) with an editor from wikt.dsb-panels, so the changes join its save queue. Without
* an editor, edits pile up in this gadget's own box at the top left.
*
* The form reads only boxes from [[Modul:translations]] (table.translations with data-gloss); the older
* div.translations boxes are left to the "translation_editor" gadget.
*/
/* global mw, $, AdderWrapper, LangMetadata, Editor */
require("./wikt.translation-adder/editor.js");
require("./wikt.translation-adder/language-utils.js");
require("./wikt.translation-adder/data.js");
var langs = require("ext.gadget.wikt.edit").langs;
var NAMES_PAGE = "Modul:ziman/code to canonical name.json";
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// Box templates in wikitext; the /ceribandin copies are the test versions.
var TOP = /\{\{\s*[Ww]erger-(?:ser|bnr-h)(?:\/ceribandin)?\s*(?=[|}])/;
var BOTTOM = /\{\{\s*[Ww]erger-bin(?:\/ceribandin)?\s*\}\}/;
// code -> Kurmancî name, and back; filled once from NAMES_PAGE.
var codeToName = null;
var nameToCode = null;
var namesPromise = null;
function loadNames() {
if (!namesPromise) {
namesPromise = $.get({
url: mw.util.getUrl(NAMES_PAGE, { action: "raw", maxage: 86400, smaxage: 86400 }),
dataType: "json"
}).then(function (data) {
codeToName = data;
nameToCode = {};
Object.keys(data).forEach(function (code) {
var name = data[code].toLowerCase();
if (!nameToCode[name]) {
nameToCode[name] = code;
}
});
});
}
return namesPromise;
}
// A language code from a code or a Kurmancî name, or null.
function toCode(txt) {
txt = (txt || "").trim();
if (codeToName[txt]) {
return txt;
}
return nameToCode[txt.toLowerCase()] || null;
}
// The name of a language, or of a family lines are nested under.
function groupName(code) {
return codeToName[code] || new LangMetadata().getGroupName(code) || code;
}
function ucfirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey(s) {
return Array.from(s.toLowerCase().replace(/['ʼ", -]/g, "")).map(function (ch) {
var i = ALPHABET.indexOf(ch);
if (i !== -1) {
return String.fromCharCode(0x100 + i * 2);
}
i = ALPHABET.indexOf(ch.normalize("NFD").charAt(0));
return i !== -1 ? String.fromCharCode(0x101 + i * 2) : ch;
}).join("");
}
function compareNames(a, b) {
var ka = sortKey(a), kb = sortKey(b);
if (ka !== kb) {
return ka < kb ? -1 : 1;
}
return a < b ? -1 : a > b ? 1 : 0;
}
function sameName(a, b) {
return sortKey(a) === sortKey(b);
}
// The HTML of a rendered line after its "Language:" label.
function afterColon(html) {
var div = document.createElement('div');
div.innerHTML = html;
var walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);
var node;
while ((node = walker.nextNode())) {
var i = node.data.indexOf(':');
if (i !== -1) {
var range = document.createRange();
range.setStart(div, 0);
range.setEnd(node, i + 1);
range.deleteContents();
break;
}
}
return div.innerHTML;
}
var PICKER_SIZE = 12;
// A name without its diacritics, so "ital" finds "îtalî".
function fold(s) {
return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
// folded name -> code, built on first use.
var foldedNames = null;
// Up to PICKER_SIZE codes for what was typed: the code itself, then names starting with it (shortest
// first, so "fran" offers fransî before franc-comtoisî), then codes starting with it, common languages
// first ("cm" offers cmn), then names containing it.
function matchLanguages(term) {
term = term.trim().toLowerCase();
var exact = codeToName[term] ? [term] : [];
if (term.length < 2) {
return exact;
}
if (!foldedNames) {
foldedNames = Object.keys(nameToCode).map(function (name) {
return [fold(name), nameToCode[name]];
});
}
var folded = fold(term);
var meta = new LangMetadata();
var starts = [], contains = [];
foldedNames.forEach(function (pair) {
var i = pair[0].indexOf(folded);
if (pair[1] === term || i === -1) {
return;
}
(i === 0 ? starts : contains).push(pair[1]);
});
var codes = Object.keys(codeToName).filter(function (code) {
return code !== term && code.indexOf(term) === 0 && starts.indexOf(code) === -1;
}).sort(function (a, b) {
return (meta.isCommon(b) - meta.isCommon(a)) || a.length - b.length || (a < b ? -1 : 1);
});
function byName(a, b) {
return compareNames(codeToName[a], codeToName[b]);
}
starts.sort(function (a, b) {
return codeToName[a].length - codeToName[b].length || byName(a, b);
});
contains = contains.filter(function (code) {
return codes.indexOf(code) === -1;
});
return exact.concat(starts, codes, contains.sort(byName)).slice(0, PICKER_SIZE);
}
// Wraps a language input in wikt.edit's dropdown of matching languages; picking one writes its name, and
// a code typed in is replaced by its name when the field is left. onChange runs whenever the value changes.
function languagePicker(input, onChange) {
var box = newNode('span', { 'class': 'translation-adder-langbox' }, input);
langs.suggest(input, {
items: function (term) {
return matchLanguages(term).map(function (code) {
return { value: codeToName[code], code: code };
});
},
onPick: onChange
});
input.addEventListener('input', onChange);
input.addEventListener('blur', function () {
var value = input.value.trim();
if (codeToName[value]) {
input.value = codeToName[value];
}
onChange();
});
return box;
}
// Puts node at the end of a line's own content: after its last translation and that translation's ✎ ×,
// before the lines nested under it.
function appendToLine(line, node) {
if (/(?:^|\s)translation-adder-wrap(?:\s|$)/.test(line.className) && line.firstElementChild) {
line = line.firstElementChild;
}
var stop = null;
for (var child = line.firstChild; child; child = child.nextSibling) {
if (/^(UL|DL)$/.test(child.nodeName) ||
(child.nodeType === 1 && /(?:^|\s)translation-adder-line-tools(?:\s|$)/.test(child.className))) {
stop = child;
break;
}
}
line.insertBefore(node, stop);
}
function newNode(tagname) {
var node = document.createElement(tagname);
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
if (typeof argument == 'string') {
node.appendChild(document.createTextNode(argument));
} else if (typeof argument == 'object') {
if (argument instanceof Node) {
node.appendChild(argument);
} else {
for (var j in argument) {
if (j === 'class') {
node.className = argument[j];
} else if (j == 'style') {
node.style.cssText = argument[j];
} else if (typeof argument[j] == 'function') {
node.addEventListener(j, argument[j], false);
} else {
node.setAttribute(j, argument[j]);
}
}
}
}
}
return node;
}
// Form settings kept in localStorage.
function Preferences() {
var KEY = "wikt-translation-adder";
var storage = mw.storage.getObject(KEY) || {};
this.get = function (name, def) {
return storage[name] || def;
};
this.set = function (name, value) {
if (value === null || storage[name] === value)
return;
storage[name] = value;
mw.storage.setObject(KEY, storage);
};
}
var util = {
// Index of str (a string or RegExp) in text at or after pos, skipping comments, nowiki and templates
// other than a match itself; -1 if none.
getVanillaIndexOf: function (str, text, pos) {
function find(s) {
if (typeof s === 'string')
return text.indexOf(s, pos);
var i = text.substring(pos).search(s);
return i === -1 ? -1 : i + pos;
}
pos = pos || 0;
do {
var cpos = text.indexOf('<!--', pos),
tpos = text.indexOf('{{', pos),
wpos = text.indexOf('<nowiki>', pos),
spos = find(str);
pos = Math.min(
cpos == -1 ? Infinity : cpos,
tpos == -1 ? Infinity : tpos,
wpos == -1 ? Infinity : wpos,
spos == -1 ? Infinity : spos
);
if (pos == Infinity)
return -1;
if (pos == spos)
return pos;
var close = pos == cpos ? '-->' : pos == wpos ? '</' + 'nowiki>' : '}}'; // split, so this page's own <nowiki> does not end here
var end = text.indexOf(close, pos);
if (end == -1)
return -1;
pos = end + close.length;
} while (true);
},
// The template starting at pos: {name, end (after "}}"), params: [{key, value}]} (key null if positional).
parseTemplate: function (text, pos) {
var depth = 0, parts = [], start = pos + 2;
for (var i = pos; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === '{{' || two === '[[') {
depth++;
i++;
} else if (two === '}}' || two === ']]') {
depth--;
i++;
if (depth === 0) {
parts.push(text.substring(start, i - 1));
break;
}
} else if (text.charAt(i) === '|' && depth === 1) {
parts.push(text.substring(start, i));
start = i + 1;
}
}
return {
name: parts[0],
end: i + 1,
params: parts.slice(1).map(function (p) {
var m = /^\s*([^=\[\]{}|]+?)\s*=([\s\S]*)$/.exec(p);
return m ? { key: m[1], value: m[2] } : { key: null, value: p };
})
};
},
// The gloss (first positional parameter) of the box template starting at pos.
getWikitextGloss: function (text, pos) {
var params = util.parseTemplate(text, pos).params;
for (var i = 0; i < params.length; i++) {
if (params[i].key === null || params[i].key === '1')
return params[i].value.trim();
}
return '';
},
// The box template at pos with gloss as its first positional parameter.
setWikitextGloss: function (text, pos, gloss) {
var t = util.parseTemplate(text, pos);
var done = false;
var params = t.params.map(function (p) {
if (!done && (p.key === null || p.key === '1')) {
done = true;
return p.key === null ? gloss : '1=' + gloss;
}
return p.key === null ? p.value : p.key + '=' + p.value;
});
if (!done)
params.unshift(gloss);
return text.substring(0, pos) + '{{' + t.name + params.map(function (p) { return '|' + p; }).join('') +
'}}' + text.substring(t.end);
},
// Whether the words of the box's gloss (as the page shows it) appear, in order, in the wikitext gloss.
matchGloss: function (wikitextGloss, gloss) {
wikitextGloss = wikitextGloss.replace(/\[\[(?:[^\[\]|]*\|)?([^\[\]]*)\]\]/g, '$1').replace(/'{2,}/g, '');
if (/^\s*$/.test(gloss))
return /^\s*$/.test(wikitextGloss);
var words = gloss.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
var pos = 0;
for (var i = 0; i < words.length; i++) {
pos = wikitextGloss.indexOf(words[i], pos);
if (pos == -1)
return false;
}
return true;
},
// The table.translations of a node inside a box, or of a box heading.
getTable: function (node) {
var frame = $(node).closest('.NavFrame');
var table = $(node).closest('table.translations');
return table.length ? table[0] : frame.find('table.translations')[0];
},
getTransGloss: function (node) {
var table = util.getTable(node);
return table ? table.dataset.gloss || '' : '';
},
// [start, end] of the box's list in text: from the line after the opening template to the closing one.
getTransTable: function (text, node) {
var gloss = util.getTransGloss(node);
var table = util.getTable(node);
var tables = $('table.translations').get();
var all = [], found = [];
var pos = -1;
while ((pos = util.getVanillaIndexOf(TOP, text, pos + 1)) > -1) {
all.push(pos);
if (util.matchGloss(util.getWikitextGloss(text, pos), gloss))
found.push(pos);
}
if (found.length != 1) {
var same = tables.filter(function (t) {
return (t.dataset.gloss || '') === gloss;
});
if (all.length === tables.length) {
// Every box on the page is one in the wikitext: they match by order.
found = [all[tables.indexOf(table)]];
} else if (found.length > 1 && same.length === found.length) {
// Boxes with the same gloss are told apart by their order on the page.
found = [found[same.indexOf(table)]];
}
}
if (found.length == 1 && found[0] !== undefined) {
var start = util.parseTemplate(text, found[0]).end;
start = text.indexOf('\n', start) + 1;
var end = text.substring(start).search(BOTTOM);
if (start > 0 && end > -1)
return [start, start + end, found[0]];
}
console.trace("getTransTable failure; node = %o, gloss = %o", node, gloss);
return false;
},
validateNoWikisyntax: function (field, nonempty) {
return function (txt, error) {
if (/[\{\}#]/.test(txt))
return error("Di qada " + field + " de herfên {}# bi kar neyne.");
if (nonempty && !txt)
return error(ucfirst(field) + " binivîse.");
return txt;
};
},
// Codes from [[Modul:gender and number/data]], in the order they are written.
genderList: {
"n": "nêr", "n-d": "nêr, dual", "n-p": "nêr, pirjimar",
"m": "mê", "m-d": "mê, dual", "m-p": "mê, pirjimar",
"g": "giştî", "g-d": "giştî, dual", "g-p": "giştî, pirjimar",
"nt": "nêtar", "nt-d": "nêtar, dual", "nt-p": "nêtar, pirjimar",
"y": "yekjimar", "d": "dual", "p": "pirjimar",
"impf": "împerfektîv", "pf": "perfektîv"
},
genderRows: [["n", "n-d", "n-p"], ["m", "m-d", "m-p"], ["g", "g-d", "g-p"], ["nt", "nt-d", "nt-p"],
["y", "d", "p"], ["impf", "pf"]]
};
// The language name a line starts with ("Almanî: …").
function getLangName(li) {
var guess = li.textContent || '';
guess = guess.substr(0, guess.indexOf(':'));
return guess.replace(/^\s*/, '');
}
// The language code of a line, from {{Z}}'s span.trad-xx or else its first translation's lang attribute.
// Only the line's own nodes count: a group line written as text ("Sorbî:") has no code of its own.
function getLangCode(li) {
var own = /(?:^|\s)trans-(\S+)/.exec(li.className);
if (own)
return own[1];
var lang = false;
for (var node = li.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (node.nodeType !== 1) {
continue;
}
var spans = [node].concat(Array.prototype.slice.call(node.getElementsByTagName('span')));
for (var i = 0; i < spans.length; i++) {
var m = /(?:^|\s)trad-(\S+)/.exec(spans[i].className);
if (m)
return m[1];
if (!lang && spans[i].lang)
lang = spans[i].lang;
}
}
return lang;
}
// Whether a line is the group line for code: {{Z|code}}, or the group's name written as text.
function isGroupLine(li, code) {
var own = getLangCode(li);
return own ? own === code : sameName(getLangName(li), groupName(code));
}
function TranslationAdders(editor, $root) {
// The ± link on a box heading, which edits its gloss.
function TranslationLabeller(insertDiv) {
var original_span;
var adder_form;
var initial_value;
var edit_button;
var editing = false;
var adderInterface = {
'fields': {
'gloss': function (txt, error) {
return util.validateNoWikisyntax('mane', true)(txt, error);
}
},
'createForm': function () {
var thisId = "a" + String(Math.random()).replace(".", "");
adder_form = newNode('form', {
style: 'display: inline',
width: 'auto',
click: kill_event
},
newNode('label', {
'for': thisId
}, "Mane: "),
newNode('input', {
type: 'text',
name: 'gloss',
value: initial_value,
style: 'width: 50%',
title: 'Maneya ku ev tercime jê re ne, bi kurtî',
id: thisId
}),
newNode('input', {
type: 'submit',
name: 'preview',
value: 'Pêşdîtin'
})
);
return adder_form;
},
'onsubmit': function (values, render) {
render("''" + values.gloss + "''", function (new_html) {
if (editing)
toggle_editing(false);
var old_html = original_span.innerHTML;
editor.addEdit({
'undo': function () {
original_span.innerHTML = old_html;
if (!editing) toggle_editing();
},
'redo': function () {
original_span.innerHTML = new_html;
if (editing) toggle_editing();
},
'edit': function (text) {
return perform_edit(text, values.gloss);
},
'summary': 'mane: "' + (values.gloss.length > 50 ? values.gloss.substr(0, 50) + '…"' : values.gloss + '"')
}, original_span);
});
}
};
function perform_edit(wikitext, gloss) {
var pos = util.getTransTable(wikitext, insertDiv);
if (!pos) {
editor.error("Qutiya tercimeyan nehat dîtin.");
return wikitext;
}
return util.setWikitextGloss(wikitext, pos[2], gloss);
}
// Don't open and close the box when using the form.
function kill_event(e) {
e.stopPropagation();
}
function toggle_editing() {
if (editing) {
adder_form.style.display = "none";
original_span.style.display = "inline";
editing = false;
return;
}
editing = true;
edit_button.text("Tê barkirin…");
editor.withCurrentText(function (currentText) {
var pos = util.getTransTable(currentText, insertDiv);
edit_button.text('±');
if (!pos)
return editor.error("Qutiya tercimeyan nehat dîtin.");
initial_value = util.getWikitextGloss(currentText, pos[2]);
if (!original_span) {
original_span = newNode('span', {
'class': 'wt-edit-recurse'
});
for (var i = 0; i < insertDiv.childNodes.length; i++) {
var child = insertDiv.childNodes[i];
if (child != edit_button[0] && child.className !== 'NavToggle') {
original_span.appendChild(insertDiv.removeChild(child));
i--;
}
}
insertDiv.appendChild(original_span);
new AdderWrapper(editor, adderInterface, insertDiv, original_span);
}
original_span.style.display = "none";
adder_form.style.display = "inline";
adder_form.getElementsByTagName('input')[0].focus();
});
}
edit_button = $('<a href="#" class="translation-adder-gloss">±</a>')
.attr("title", "Maneya qutiyê biguherîne").css("padding", "2px").css("margin-left", "-5px")
.on("click", function (e) {
e.preventDefault();
kill_event(e);
toggle_editing();
return false;
});
$(insertDiv).prepend(edit_button);
}
function TranslationAdder(insertUl) {
var langmetadata = new LangMetadata();
this.fields = {
lang: function (txt, error) {
var code = toCode(txt);
if (!code)
return error("Koda zimanî an navê zimanî binivîse (de, almanî, …).");
if (code == 'ku' || code == 'kmr')
return error("Zimanekî ji bilî kurmancî hilbijêre.");
return code;
},
word: function (txt, error) {
if (txt.indexOf(',') == -1 || forceComma) {
forceComma = false;
if (langmetadata.expectedCase(toCode(thiz.elements.lang.value), mw.config.get('wgTitle'), txt) || forceCase) {
forceCase = false;
return util.validateNoWikisyntax('tercime', true)(txt, error);
}
if (prefs.get('case-knowledge', 'none') == 'none') {
return error(newNode('span',
"Tercime pir caran bi herfa biçûk dest pê dikin. Heke tu bawer î ku bi herfa mezin e, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceCase = true;
inputForm.onsubmit();
prefs.set('case-knowledge', 'guru');
}
}, "ji bo berdewamkirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('case-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke tu ne bawer î ku ev tercime bi herfa mezin e.");
error(msg);
return txt;
}
}
if (prefs.get('comma-knowledge', 'none') == 'none') {
return error(newNode('span',
"Her carê tenê tercimeyekê lê zêde bike. Heke ev tercimeyek e ku virgul tê de heye, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceComma = true;
inputForm.onsubmit();
prefs.set('comma-knowledge', 'guru');
}
}, "ji bo lêzêdekirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('comma-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke te dixwest çend tercimeyan bi hev re lê zêde bikî: ev nabe.");
error(msg);
return txt;
}
},
qual: util.validateNoWikisyntax('not'),
tr: util.validateNoWikisyntax('transliterasyon'),
lit: util.validateNoWikisyntax('maneya herfî'),
rawPageName: util.validateNoWikisyntax('navê rûpelê'),
nested: function (txt, error) {
if (!txt)
return '';
var group = toCode(txt) || (langmetadata.getGroupName(txt.trim()) ? txt.trim() : null);
return group || error("Ji bo \"di bin zimanê\" koda zimanekî an malbatekê binivîse.");
},
nclass1: util.validateNoWikisyntax('sinifa navdêrê'),
nclass2: util.validateNoWikisyntax('sinifa pirjimariyê')
};
var fields = this.fields;
Object.keys(util.genderList).forEach(function (g) {
fields[g] = 'checkbox';
});
this.createForm = function () {
function createGenderNode(name, text) {
var inp = document.createElement("input");
inp.type = "checkbox";
inp.name = name;
var lbl = document.createElement("label");
lbl.appendChild(inp);
lbl.appendChild(document.createTextNode(text));
return lbl;
}
var genderControls = {};
for (var g in util.genderList) genderControls[g] = createGenderNode(g, util.genderList[g]);
var langName = newNode('span', { 'class': 'translation-adder-langname' });
var controls = {
lang: newNode('input', {
size: 12,
type: 'text',
name: 'lang',
value: prefs.get('curlang', ''),
placeholder: 'ziman',
title: 'Koda zimanî (de, fr, …) an navê wî (almanî, fransî, …)'
}),
transliteration: newNode('p', "Transliterasyon: ",
newNode('input', {
name: "tr",
title: "Tercime bi herfên latînî."
}), " (nimûne: zìmǔ ji bo 字母)"),
literal: newNode('p', "Maneya herfî: ",
newNode('input', {
name: "lit",
title: "Maneya kelîme bi kelîme ya tercimeyê."
})),
qualifier: newNode('p', "Not: ", newNode('input', {
name: 'qual',
title: "Notek kurt li ser tercimeyê"
}), " (nimûne: kevn, devkî)"),
display: newNode('p', "Navê rûpelê: ", newNode('input', {
name: 'rawPageName',
title: "Heke tercime ne forma bingehîn be, navê rûpela ku jê re lînk bê dayîn."
}), " (nimûne: 疲れる ji bo 疲れた)"),
nested: newNode('p', "Di bin zimanê: ", newNode('input', {
name: 'nested',
title: "Zimanê ku ev ziman di bin wî de tê rêzkirin"
}), " (nimûne: zh ji bo mandarînî)"),
genders: genderControls,
nclass: newNode('p',
"Sinifa navdêrê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass1',
title: "Sinifa navdêrê di forma yekjimar de."
}),
" Sinifa pirjimariyê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass2',
title: "Sinifa navdêrê di forma pirjimar de, heke hebe."
}))
};
var $gender = $("<p>");
util.genderRows.forEach(function (row) {
var $div = $("<div>");
row.forEach(function (code) { $div.append(controls.genders[code]); });
$gender.append($div);
});
controls.gender = $gender[0];
langInput = controls.lang;
var showButton = newNode('span', {
'click': function () {
advancedMode = !advancedMode;
showButton.textContent = advancedMode ? " Kêmtir" : " Zêdetir";
updateFieldVisibility.call(langInput, true);
},
'style': "color: var(--wikt-palette-blue,#0000FF); cursor: pointer;"
}, advancedMode ? " Kêmtir" : " Zêdetir");
function autoTransliterate() {
var rawPageName = langmetadata.computeRawPageName(toCode(thiz.elements.lang.value), thiz.elements.word.value);
if (rawPageName && rawPageName !== thiz.elements.word.value)
thiz.elements.rawPageName.value = rawPageName;
}
function updateFieldVisibility(preserve) {
preserve = (preserve === true);
function show() {
for (var i = 0; i < arguments.length; i++) {
arguments[i].style.display = arguments[i].nodeName == 'P' ? "block" : "inline";
}
}
function hide() {
for (var i = 0; i < arguments.length; i++)
arguments[i].style.display = "none";
}
// Shows the remaining arguments if the first is true, else hides them.
function toggle(condition) {
var nodes = [].slice.call(arguments, 1);
(condition ? show : hide).apply(this, nodes);
}
var lang = toCode(langInput.value) || '';
// A code in the field gets its name beside it, until leaving the field puts the name in.
var typed = langInput.value.trim();
langName.textContent = codeToName[typed] ? " (" + codeToName[typed] + ")" : "";
if (!preserve) {
autoTransliterate();
// Mandarînî goes under Çînî and so on, unless "Di bin zimanê" is changed.
thiz.elements.nested.value = lang ? langmetadata.getNested(lang) : '';
}
var scriptGuess = langmetadata.guessScript(lang);
if (!advancedMode) {
var g = lang ? langmetadata.getGenders(lang) : undefined;
if (g === undefined) {
hide(controls.gender);
} else {
for (var genderName in util.genderList) {
toggle(g.indexOf(genderName) != -1, controls.genders[genderName]);
}
}
toggle(g, controls.gender);
toggle(langmetadata.hasNounClasses(lang), controls.nclass);
var hasAutomaticTransliteration = new window.LanguageUtils().HasAutomaticTransliteration(lang);
toggle(scriptGuess && scriptGuess != 'Latn' && !hasAutomaticTransliteration, controls.transliteration);
toggle(langmetadata.needsRawPageName(lang), controls.display);
hide(controls.literal, controls.qualifier, controls.nested);
} else {
show.apply(this, [controls.gender].concat(
Object.keys(util.genderList).map(function (k) { return controls.genders[k]; }),
[controls.nclass, controls.transliteration, controls.literal, controls.qualifier, controls.display,
controls.nested]));
}
}
var langBox = languagePicker(langInput, function () {
updateFieldVisibility.call(langInput);
});
window.setTimeout(function () {
updateFieldVisibility.call(langInput);
}, 0);
inputForm = newNode('form',
newNode('p', "Tercimeyekê lê zêde bike: ",
langBox, langName, newNode('b', ': '), newNode('input', {
'name': 'word',
placeholder: 'tercime',
title: 'Tercime bi wî zimanî',
size: 20,
keyup: autoTransliterate,
change: autoTransliterate
}),
newNode('input', {
'type': 'submit',
'value': 'Pêşdîtin'
}), showButton
),
controls.gender,
controls.nclass,
controls.transliteration,
controls.literal,
controls.display,
controls.qualifier,
controls.nested
);
return inputForm;
};
this.onsubmit = function (values, render) {
var wikitext;
langmetadata.retrieveRawPageName(values.lang, values.word, values.rawPageName, function (rawPageName, needsRawPageName) {
if (needsRawPageName) {
values.rawPageName = rawPageName;
values.wordMinus = '';
} else {
values.rawPageName = '';
values.wordMinus = rawPageName;
}
langmetadata.hasWiktionaryWithEntry(values.lang, values.rawPageName || values.wordMinus, function (hasInterwiki) {
var templateBody = '|' + values.lang + '|' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) +
(values.nclass1 ? '|c' + values.nclass1 : '') +
(values.nclass2 ? '|c' + values.nclass2 : '');
templateBody += Object.keys(util.genderList).map(function (k) {
return values[k] ? '|' + k : '';
}).join("");
templateBody +=
(values.tr ? '|tr=' + values.tr : '') +
(values.lit ? '|lit=' + values.lit : '') +
(values.rawPageName ? '|cuda=' + values.word : '');
var qualifier = values.qual ? ' {{i|' + values.qual + '}}' : '';
wikitext = '{{Z|' + values.lang + '}}: {{' + (hasInterwiki ? "W+" : "W") + templateBody + "}}" + qualifier;
render(wikitext, function (html) {
registerEdits(values, wikitext, html);
});
});
});
};
var thiz = this;
var prefs = new Preferences();
var langInput;
var inputForm;
var advancedMode = prefs.get('more-display', 'none') != 'none';
var forceComma = false;
var forceCase = false;
function resetElements() {
prefs.set('more-display', advancedMode ? 'block' : 'none');
['word', 'nclass1', 'nclass2', 'lit', 'tr', 'rawPageName', 'qual'].forEach(function (f) {
thiz.elements[f].value = '';
});
Object.keys(util.genderList).forEach(function (g) {
thiz.elements[g].checked = false;
});
prefs.set('curlang', thiz.elements.lang.value);
}
// Runs after the new line has been rendered: finds where it goes and registers the edit.
function registerEdits(values, wikitext, content) {
var li = newNode('li', {
'class': 'trans-' + values.lang
});
li.innerHTML = content;
var lang = getLangName(li);
var summary = '+' + codeToName[values.lang] + ': [[' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) + ']]';
var insertBefore = null;
var nextLanguage = null;
var nestedCode = values.nested;
// A top-level line for the language already there takes the translation, and then moves under its group.
var table = util.getTable(insertUl);
var nestAfter = nestedCode && misplacedLines(table, values.lang).length ? values.lang : null;
if (nestAfter) {
nestedCode = '';
}
var nestedHeading = nestedCode ? ucfirst(groupName(nestedCode)) : '';
var nestedWikitext = "\n* {{Z|" + nestedCode + "}}:\n*: " + wikitext;
function addEdit(edit, span) {
var fields = ["lang", "word", "nclass1", "nclass2", "rawPageName", "qual", "tr", "lit"];
editor.addEdit({
'undo': function () {
edit.undo();
if (fields.slice(1).every(function (f) { return thiz.elements[f].value === ""; })) {
for (var i = 0; i < fields.length; i++) {
thiz.elements[fields[i]].value = values[fields[i]];
}
for (var genderName in util.genderList) {
thiz.elements[genderName].checked = values[genderName];
}
}
},
'redo': function () {
edit.redo();
// Editing mode, so the new translation has its ✎ and × at once.
setLineEditing(table, editor, true);
for (var i = 0; i < fields.length; i++) {
if (thiz.elements[fields[i]].value != values[fields[i]])
return;
}
resetElements();
},
'edit': edit.edit,
'summary': summary
}, span);
if (nestAfter) {
editor.addEdit(nestEdit(table, nestAfter), null);
}
}
if (lang && !nestedCode) {
// Every line in this box.
var lis = $(insertUl).parent().parent().find('li').get()
.concat($(insertUl).parent().parent().find('dd').get());
for (var j = 0; j < lis.length; j++) {
if (lis[j].getElementsByTagName('form').length > 0)
continue;
var ln = getLangName(lis[j]);
if (ln && sameName(ln, lang)) {
var span = newNode('span', { 'class': 'translation-adder-added' });
var parent = lis[j];
if (parent.getElementsByTagName('ul').length + parent.getElementsByTagName('dl').length == 0) {
span.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(parent, span);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to the language's existing line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
var node = parent.firstChild;
var hastrans = false;
while (node) {
if (node.nodeType == 1) {
var nn = node.nodeName;
if (nn == 'UL' || nn == 'DL') {
span.innerHTML = (hastrans ? ", " : " ") + afterColon(content);
addEdit({
'redo': function () {
parent.insertBefore(span, node);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to a language that has nested lines under it.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + (hastrans ? "," : "") + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
hastrans = true;
}
}
node = node.nextSibling;
}
}
} else if (ln && compareNames(ln, lang) > 0 && (!nextLanguage || compareNames(ln, nextLanguage) < 0) && lis[j].parentNode.parentNode.nodeName != 'LI') {
nextLanguage = ln;
var before = lis[j];
insertBefore = [{
'redo': function () {
before.parentNode.insertBefore(li, before);
},
'undo': function () {
before.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, ln, getLangCode(before), function (text, ipos) {
// A new line before the next language's line.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend);
})
}, li];
}
}
}
if (nestedCode) {
nextLanguage = null;
insertBefore = null;
li.innerHTML = nestedHeading + ":" + "<dl><dd class=\"trans-" + values.lang + "\">" + content + "</dd></dl>";
var tops = insertUl.parentNode.parentNode.getElementsByTagName('li');
for (var k = 0; k < tops.length; k++) {
if (tops[k].getElementsByTagName('form').length > 0)
continue;
// Only top-level lines.
if (tops[k].parentNode.parentNode.nodeName != 'TD')
continue;
var tln = getLangName(tops[k]);
if (tln && sameName(tln, nestedHeading)) {
var group = tops[k];
var sublis = group.getElementsByTagName('li');
if (!sublis.length)
sublis = group.getElementsByTagName('dd');
if (sublis.length == 0) {
var dd = newNode('dd', {
'class': 'trans-' + values.lang
});
var dl = newNode('dl', dd);
dd.innerHTML = content;
addEdit({
'redo': function () {
group.appendChild(dl);
},
'undo': function () {
group.removeChild(dl);
},
'edit': getEditFunction(values, wikitext, nestedHeading, nestedCode, function (text, ipos) {
// The first nested line under the group's line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "\n*: " + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
}
var item = newNode(sublis[0].nodeName, {
'class': 'trans-' + values.lang
});
var linestart = item.nodeName == 'DD' ? '\n*: ' : '\n** ';
item.innerHTML = content;
for (var m = 0; m < sublis.length; m++) {
var subln = getLangName(sublis[m]);
var sub = sublis[m];
if (subln && sameName(subln, lang)) {
var addSpan = newNode('span', { 'class': 'translation-adder-added' });
addSpan.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(sub, addSpan);
},
'undo': function () {
sub.removeChild(addSpan);
},
'edit': getEditFunction(values, wikitext, lang, values.lang, function (text, ipos) {
// Adding to an existing nested line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, addSpan);
return resetElements();
} else if (subln && langmetadata.nestsBefore(lang, subln)) {
addEdit({
'redo': function () {
sub.parentNode.insertBefore(item, sub);
},
'undo': function () {
sub.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, subln, getLangCode(sub), function (text, ipos) {
// A nested line in order.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
}
}
var last = sublis[sublis.length - 1];
addEdit({
'redo': function () {
last.parentNode.appendChild(item);
},
'undo': function () {
last.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, getLangName(last), getLangCode(last), function (text, ipos) {
// A nested line at the end of its group.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
} else if (tln && compareNames(tln, nestedHeading) > 0 && (!nextLanguage || compareNames(tln, nextLanguage) < 0)) {
nextLanguage = tln;
var next = tops[k];
insertBefore = [{
'redo': function () {
next.parentNode.insertBefore(li, next);
},
'undo': function () {
next.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, tln, getLangCode(next), function (text, ipos) {
// A new group line with the nested line under it.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + nestedWikitext + text.substr(lineend);
})
}, li];
}
}
wikitext = "{{Z|" + nestedCode + "}}:\n*: " + wikitext;
}
li.className = "trans-" + values.lang;
if (insertBefore) {
addEdit(insertBefore[0], insertBefore[1]);
} else {
// After the last line.
addEdit({
'redo': function () {
insertUl.appendChild(li);
},
'undo': function () {
insertUl.removeChild(li);
},
'edit': getEditFunction(values, wikitext)
}, li);
}
return resetElements();
}
// The wikitext change for this submission. With findLanguage, callback gets the position of that
// language's line; without, the line goes at the end of the box.
function getEditFunction(values, wikitext, findLanguage, findLangCode, callback) {
return function (text) {
var p = util.getTransTable(text, insertUl);
if (!p)
return editor.error("Qutiya tercimeyan ji bo '" + values.lang + ":" + values.word +
"' nehat dîtin. Maneyên qutiyan divê ji hev cuda bin.");
var stapos = p[0];
var endpos = p[1];
if (findLanguage) {
var box = text.substring(stapos, endpos);
var sub = values.nested ? "[:*]?" : "";
var escapedName = mw.util.escapeRegExp(findLanguage);
var patterns = [
"\\*" + sub + " ?\\[\\[" + escapedName + "\\]\\]:",
"\\*" + sub + " ?" + escapedName + ":"
];
if (findLangCode)
patterns.unshift("\\*" + sub + " ?\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(findLangCode) + "\\s*(?:\\|[^{}]*)?\\}\\}\\s*:");
var ipos = -1;
for (var i = 0; i < patterns.length && ipos == -1; i++) {
ipos = box.search(new RegExp(patterns[i], "i"));
}
if (ipos > -1) {
return callback(text, ipos + stapos);
}
return editor.error("Rêza zimanê " + findLanguage + " nehat dîtin. Ji kerema xwe rêzê rast bike.");
}
return text.substr(0, endpos) + "* " + wikitext + "\n" + text.substr(endpos);
};
}
}
function createTranslationAdderRow(li) {
var $ul = $("<ul>").append(li);
return $("<tr>")
.addClass("translation-adder-row")
.append($("<td>").attr("colspan", "3").css("text-align", "left").append($ul));
}
$root.find('table.translations').each(function () {
if (this.dataset.translationAdder) {
return;
}
this.dataset.translationAdder = "1";
var td = $(this).find("td.translations-cell")[0];
if (!td) {
return;
}
var uls = $(td).children("ul").get();
if (uls.length == 0) {
uls = [td.appendChild(newNode('ul'))];
}
var li = document.createElement('li');
$(this).append(createTranslationAdderRow(li));
new AdderWrapper(editor, new TranslationAdder(uls[uls.length - 1]), li);
var head = $(this).closest('.NavFrame').children('.NavHead')[0];
if (head) {
new TranslationLabeller(head);
}
});
}
// HTML for wikitext as this page renders it, without the paragraph around it.
function render(wikitext) {
return new mw.Api().parse(wikitext, {
title: mw.config.get('wgPageName'),
pst: true,
disablelimitreport: true
}).then(function (html) {
var $out = $('<div>').html(html);
var $p = $out.find('p').first();
return ($p.length ? $p : $out.children().first()).html().trim();
});
}
// The wikitext of a translation line: {start, restStart, end, rest}, where rest is what follows
// "Language:"; null when the line is not found.
function findLine(text, line) {
var box = util.getTransTable(text, line);
if (!box) {
return null;
}
var code = getLangCode(line);
var name = getLangName(line);
var label = code ?
"\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(code) + "\\s*(?:\\|[^{}\\n]*)?\\}\\}" :
"(?:\\[\\[" + mw.util.escapeRegExp(name) + "\\]\\]|" + mw.util.escapeRegExp(name) + ")";
var part = text.substring(box[0], box[1]);
var m = new RegExp("^\\*[:*]* ?" + label + "\\s*:[ \\t]*(.*)$", "im").exec(part);
if (!m) {
return null;
}
var start = box[0] + m.index;
var end = start + m[0].length;
return { start: start, restStart: end - m[1].length, end: end, rest: m[1], label: text.substring(start, end - m[1].length) };
}
// A line's own nodes: everything before the lines nested under it, without this gadget's buttons.
function ownNodes(line) {
var nodes = [];
for (var node = line.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (!(node.nodeType === 1 && /(?:^|\s)translation-adder-(?:line-tools|term-edit)(?:\s|$)/.test(node.className))) {
nodes.push(node);
}
}
return nodes;
}
var TERM_CALL = /\{\{\s*W[+-]?(?:\/ceribandin)?\s*\|/g;
// The {{W}} calls in the wikitext after "Language:", in order: [{start, end, text}].
function termCalls(rest) {
var out = [];
var m;
TERM_CALL.lastIndex = 0;
while ((m = TERM_CALL.exec(rest))) {
var end = util.parseTemplate(rest, m.index).end;
out.push({ start: m.index, end: end, text: rest.substring(m.index, end) });
TERM_CALL.lastIndex = end;
}
return out;
}
// rest with its i-th {{W}} call replaced by text, or removed with the comma that joins it to the others.
function replaceTerm(rest, i, text) {
var call = termCalls(rest)[i];
if (!call) {
return null;
}
if (text) {
return rest.substring(0, call.start) + text + rest.substring(call.end);
}
var before = rest.substring(0, call.start);
var after = rest.substring(call.end);
if (/,\s*$/.test(before)) {
before = before.replace(/\s*,\s*$/, '');
} else {
after = after.replace(/^\s*,\s*/, '');
}
return (before + after).trim();
}
// The rendered translations of a line, in order: the element each {{W}} call starts with.
function termNodes(line) {
var nodes = [];
ownNodes(line).forEach(function (n) {
if (n.nodeType === 1 && /(?:^|\s)translation-adder-added(?:\s|$)/.test(n.className)) {
nodes.push.apply(nodes, Array.prototype.slice.call(n.childNodes));
} else {
nodes.push(n);
}
});
// A translation starts with its link's span, or with the "ttbc" span of one marked for checking.
// The second form after a "/" (traditional / simplified Chinese) belongs to the same translation.
var terms = [];
var afterSlash = false;
nodes.forEach(function (n) {
if (n.nodeType === 3) {
if (n.data.trim()) {
afterSlash = n.data.trim() === '/';
}
return;
}
if (n.nodeType !== 1) {
return;
}
// The separator is drawn in a span of its own.
if (!n.lang && n.textContent.trim() === '/') {
afterSlash = true;
return;
}
var isTerm = /(?:^|\s)ttbc(?:\s|$)/.test(n.className) ||
(n.lang && !/(?:^|\s)(?:tr|ts)(?:\s|$)/.test(n.className));
if (isTerm && !(afterSlash && terms.length)) {
terms.push(n);
}
afterSlash = false;
});
return terms;
}
// The template dialog of [[MediaWiki:Gadget-wikt.cite.js]], built from the template's TemplateData.
// Resolves with the edited call, "" when it is removed, or null when cancelled.
function templateDialog(call) {
return mw.loader.using('ext.gadget.wikt.cite').then(function (require) {
return require('ext.gadget.wikt.cite').openTemplateDialog(call);
});
}
// On one translation line: ✎ and × after each translation (its template in a dialog, and removing it).
function LineTools(editor, line) {
var name = getLangName(line);
if (!name) {
return;
}
var code = getLangCode(line);
var title = ucfirst(codeToName[code] || name);
var $tools = $('<span class="translation-adder-line-tools">');
var self = this;
this.$tools = $tools;
this.line = line;
// Whether editing mode is on; set by setLineEditing().
this.editing = true;
var nested = line.querySelector('ul, dl');
if (nested) {
$(nested).before($tools);
} else {
$(line).append($tools);
}
function notFound() {
return editor.error("Rêza " + title + " di wîkîmetnê de nehat dîtin.");
}
// The i-th translation drawn is the i-th {{W}} call only when the line holds nothing else that draws
// one ({{g}}, {{l}}, …); otherwise ✎ and × could reach the wrong one, so they refuse.
function callsMatch(found) {
if (termCalls(found.rest).length === termNodes(line).length) {
return true;
}
editor.error("Tercimeyên rêza " + title + " bi wîkîmetnê re li hev nayên; rêzê bi destan biguherîne.");
return false;
}
// Queues a new text after "Language:", drawn in place from its rendering.
function queueRest(makeRest, summary) {
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var rest = found && makeRest(found.rest);
if (rest === null || rest === undefined) {
return notFound();
}
render(found.label.replace(/^\*[:*]* ?/, '') + ' ' + rest).then(function (html) {
var before = ownNodes(line);
var holder = document.createElement('span');
holder.innerHTML = html;
var after = Array.prototype.slice.call(holder.childNodes);
editor.addEdit({
redo: function () {
before.forEach(function (n) { $(n).detach(); });
after.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
undo: function () {
after.forEach(function (n) { $(n).detach(); });
before.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
edit: function (current) {
var at = findLine(current, line);
var next = at && makeRest(at.rest);
if (next === null || next === undefined) {
return notFound();
}
return current.substring(0, at.restStart) + next + current.substring(at.end);
},
summary: summary
}, line);
});
});
}
// The pencil after each translation, put back whenever the line is redrawn.
this.markTerms = function () {
$(line).children('.translation-adder-term-edit')
.add($(line).children('.translation-adder-added').children('.translation-adder-term-edit'))
.remove();
var terms = termNodes(line);
terms.forEach(function (term, i) {
var $edit = $('<a href="#" class="translation-adder-term-edit translation-adder-term-pencil"></a>')
.attr({ title: 'Vê tercimeyê biguherîne', 'aria-label': 'Vê tercimeyê biguherîne' });
// After everything the translation draws: its interwiki link, gender and transliteration.
var last = term;
while (last.nextSibling && !/^(UL|DL)$/.test(last.nextSibling.nodeName) &&
terms.indexOf(last.nextSibling) === -1 &&
!(last.nextSibling.nodeType === 3 && /[,;]/.test(last.nextSibling.data)) &&
!(last.nextSibling.nodeType === 1 &&
/translation-adder-(?:line-tools|added)/.test(last.nextSibling.className))) {
last = last.nextSibling;
}
var $del = $('<a href="#" class="translation-adder-term-edit translation-adder-term-close"></a>')
.attr({ title: 'Vê tercimeyê rake', 'aria-label': 'Vê tercimeyê rake' });
$(last).after($edit, $del);
$edit.add($del).toggle(self.editing);
// The only translation of a line with nothing under it takes the line with it.
$del.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
if (!found || !termCalls(found.rest)[i]) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
if (termCalls(found.rest).length === 1 && !line.querySelector('ul, dl')) {
removeLine();
} else {
queueRest(function (rest) {
return replaceTerm(rest, i, '');
}, '−' + title);
}
});
});
$edit.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var call = found && termCalls(found.rest)[i];
if (!call) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
templateDialog(call.text).then(function (edited) {
if (edited === null || edited === call.text) {
return;
}
queueRest(function (rest) {
return replaceTerm(rest, i, edited);
}, (edited ? '±' : '−') + title);
});
});
});
});
};
this.markTerms();
function removeLine() {
editor.addEdit({
redo: function () {
line.classList.add('translation-adder-removed');
},
undo: function () {
line.classList.remove('translation-adder-removed');
},
edit: function (current) {
var at = findLine(current, line);
if (!at) {
return notFound();
}
return current.substring(0, at.start) + current.substring(at.end + (current.charAt(at.end) === '\n' ? 1 : 0));
},
summary: '−' + title
}, line);
}
}
// ---- nesting: a language en.wiktionary nests (cmn) goes under its group's line ({{Z|zh}}) ----
// The language code of a wikitext line, from its {{Z}}.
function lineCode(l) {
var m = /^\*[:*]* ?\{\{\s*Z\s*\|\s*([^|}\s]+)/.exec(l);
return m ? m[1] : null;
}
// The language name of a wikitext line, for sorting.
function lineName(l) {
var code = lineCode(l);
if (code) {
return groupName(code);
}
var m = /^\*[:*]* ?(?:\[\[)?([^\]:|]+)/.exec(l);
return m ? m[1].trim() : '';
}
function isChildLine(l) {
return /^\*[:*]/.test(l) && !/^\*[:*][:*]/.test(l);
}
// text with the top-level lines of node's box whose language nests (only that language, if given) moved
// under their group line, which is added in order where there is none. A group that already has a line for
// the language gets its translations added to it.
function nestLines(text, node, only) {
var p = util.getTransTable(text, node);
if (!p) {
return null;
}
var meta = new LangMetadata();
var body = text.substring(p[0], p[1]);
var trimmed = body.replace(/\n+$/, '');
var tail = body.slice(trimmed.length);
var head = [], entries = [];
trimmed.split('\n').forEach(function (l) {
if (/^\*(?![:*])/.test(l)) {
entries.push({ line: l, code: lineCode(l), children: [] });
} else if (entries.length) {
entries[entries.length - 1].children.push(l);
} else {
head.push(l);
}
});
var moved = 0;
entries.slice().forEach(function (e) {
var parent = e.code && meta.getNested(e.code);
if (!parent || (only && e.code !== only)) {
return;
}
entries.splice(entries.indexOf(e), 1);
var group = entries.filter(function (x) {
return x.code ? x.code === parent : sameName(lineName(x.line), groupName(parent));
})[0];
if (!group) {
group = { line: '* {{Z|' + parent + '}}:', code: parent, children: [] };
var at = entries.findIndex(function (x) {
return compareNames(lineName(x.line), groupName(parent)) > 0;
});
entries.splice(at === -1 ? entries.length : at, 0, group);
}
var prefix = group.children.some(function (c) { return /^\*\*/.test(c); }) ? '**' : '*:';
var deeper = e.children.map(function (c) { return prefix + c.slice(1); });
var same = group.children.findIndex(function (c) { return isChildLine(c) && lineCode(c) === e.code; });
if (same !== -1) {
var terms = e.line.replace(/^\*\s*\{\{[^{}]*\}\}\s*:\s*/, '');
group.children[same] = group.children[same].replace(/\s*$/, '') + (terms ? ', ' + terms : '');
[].splice.apply(group.children, [same + 1, 0].concat(deeper));
} else {
var name = codeToName[e.code] || e.code;
var at2 = group.children.findIndex(function (c) {
return isChildLine(c) && compareNames(lineName(c), name) > 0;
});
[].splice.apply(group.children, [at2 === -1 ? group.children.length : at2, 0]
.concat([prefix + ' ' + e.line.replace(/^\*\s*/, '')], deeper));
}
moved++;
});
if (!moved) {
return text;
}
var out = head.slice();
entries.forEach(function (x) {
out.push(x.line);
out.push.apply(out, x.children);
});
return text.substring(0, p[0]) + out.join('\n') + tail + text.substring(p[1]);
}
// The top-level lines of a box.
function topLines(table) {
return $(table).find('td.translations-cell').first().children('ul').children('li').get();
}
// The top-level lines whose language nests: [{li, code, parent}].
function misplacedLines(table, only) {
var meta = new LangMetadata();
return topLines(table).map(function (li) {
var code = getLangCode(li);
var parent = code && meta.getNested(code);
return parent && (!only || code === only) ? { li: li, code: code, parent: parent } : null;
}).filter(Boolean);
}
// Draws nestLines() on the page: the lines move under their group, highlighted. Returns the undo.
function nestDom(table, only) {
var undos = [];
misplacedLines(table, only).forEach(function (m) {
var group = topLines(table).filter(function (li) { return isGroupLine(li, m.parent); })[0];
if (!group) {
var made = newNode('li', { 'class': 'trans-' + m.parent + ' translation-adder-new' },
ucfirst(groupName(m.parent)) + ':');
var next = topLines(table).filter(function (li) {
return compareNames(getLangName(li), getLangName(made)) > 0;
})[0];
m.li.parentNode.insertBefore(made, next || null);
group = made;
undos.push(function () { $(made).remove(); });
}
var list = $(group).children('dl, ul')[0];
if (!list) {
var newList = list = group.appendChild(newNode('dl'));
undos.push(function () { $(newList).remove(); });
}
var home = m.li.parentNode, after = m.li.nextSibling;
var item = $(list).children().filter(function () { return getLangCode(this) === m.code; })[0];
if (item) {
var span = newNode('span', { 'class': 'translation-adder-new' });
var holder = document.createElement('div');
ownNodes(m.li).forEach(function (n) { holder.appendChild(n.cloneNode(true)); });
span.innerHTML = ', ' + afterColon(holder.innerHTML);
var sub = $(item).children('dl, ul')[0];
item.insertBefore(span, sub || null);
m.li.style.display = 'none';
undos.push(function () { $(span).remove(); m.li.style.display = ''; });
} else {
var before = $(list).children().filter(function () {
return compareNames(getLangName(this), getLangName(m.li)) > 0;
})[0];
// In a "*:" list the line is drawn as a <dd>, as the saved page will have it.
var wrap = list.nodeName === 'DL' ? newNode('dd', { 'class': 'translation-adder-wrap' }) : null;
if (wrap) {
wrap.appendChild(m.li);
list.insertBefore(wrap, before || null);
} else {
list.insertBefore(m.li, before || null);
}
m.li.classList.add('translation-adder-new');
undos.push(function () {
m.li.classList.remove('translation-adder-new');
home.insertBefore(m.li, after);
$(wrap).remove();
});
}
});
return function () {
undos.reverse().forEach(function (f) { f(); });
};
}
// The change that nests the lines of table's box (only one language's, if given).
function nestEdit(table, only) {
var undo = null;
return {
redo: function () {
undo = nestDom(table, only);
},
undo: function () {
if (undo) {
undo();
undo = null;
}
},
edit: function (text) {
return nestLines(text, table, only);
},
summary: 'tercime di bin zimanên xwe de'
};
}
// Shows (on) or hides the ✎ and × on every translation line of the boxes in root, and on a box with
// lines that belong under another language a link that moves them there.
function setLineEditing(root, editor, on) {
return loadNames().then(function () {
editor = editor || new Editor();
// root may be a box itself.
var $tables = $(root).find('table.translations').addBack('table.translations');
$tables.each(function () {
var table = this;
var $row = $(table).data('translationAdderNest');
if (!$row && on) {
var count = misplacedLines(table).length;
if (!count) {
return;
}
var $nest = $('<a href="#">').text(count === 1 ?
'Rêzekê di bin zimanê wê de bi cih bike' : count + ' rêzan di bin zimanên wan de bi cih bike');
$row = $('<tr class="translation-adder-nest-row">').append($('<td colspan="3">').append('↳ ', $nest));
$(table).find('td.translations-cell').first().parent().before($row);
$(table).data('translationAdderNest', $row);
$nest.on('click', function (e) {
e.preventDefault();
editor.addEdit(nestEdit(table), null);
$row.remove();
});
}
if ($row) {
$row.toggle(on);
}
});
$tables.find('td.translations-cell').find('li, dd').each(function () {
var tools = $(this).data('translationAdderTools');
if (!tools && on) {
tools = new LineTools(editor, this);
$(this).data('translationAdderTools', tools);
}
if (tools && tools.$tools) {
tools.editing = on;
tools.$tools.toggle(on);
if (on) {
tools.markTerms();
}
$(this).find('.translation-adder-term-edit').filter(function () {
return $(this).closest('li, dd')[0] === tools.line;
}).toggle(on);
}
});
});
}
// Adds the forms to the boxes in root (an element or jQuery object). editor collects the changes: by
// default this gadget's own Editor; DefSideBoxes passes one that queues them in its save box.
function attach(root, editor) {
return loadNames().then(function () {
TranslationAdders(editor || new Editor(), $(root));
});
}
window.wikt = window.wikt || {};
window.wikt.translationAdder = {
attach: attach,
setLineEditing: setLineEditing,
compareNames: compareNames
};
module.exports = window.wikt.translationAdder;
// </nowiki>
7np02tdo9fkhkeyv5zzeox34sefaaec
6865558
6865549
2026-10-23T20:17:11Z
Wikihez
7344
Use the dropdown from wikt.suggest
6865558
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.translation-adder.js]]
*
* Adds a form under each translation box ({{werger-ser}} … {{werger-bin}}) to add a line such as
* * {{Z|de}}: {{W+|de|Wasser|n}}
* in Kurmancî alphabetical order, and a ± link on the box heading to change its gloss. Forked from
* en.wiktionary's [[en:MediaWiki:Gadget-TranslationAdder.js]] (by [[en:User:Conrad.Irwin]] and others).
*
* DefSideBoxes ([[MediaWiki:Gadget-wikt.dsb.js]]) loads it from the pencil on a Werger heading and calls
* attach(root, editor) with an editor from wikt.dsb-panels, so the changes join its save queue. Without
* an editor, edits pile up in this gadget's own box at the top left.
*
* The form reads only boxes from [[Modul:translations]] (table.translations with data-gloss); the older
* div.translations boxes are left to the "translation_editor" gadget.
*/
/* global mw, $, AdderWrapper, LangMetadata, Editor */
require("./wikt.translation-adder/editor.js");
require("./wikt.translation-adder/language-utils.js");
require("./wikt.translation-adder/data.js");
var suggest = require("ext.gadget.wikt.suggest");
var NAMES_PAGE = "Modul:ziman/code to canonical name.json";
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// Box templates in wikitext; the /ceribandin copies are the test versions.
var TOP = /\{\{\s*[Ww]erger-(?:ser|bnr-h)(?:\/ceribandin)?\s*(?=[|}])/;
var BOTTOM = /\{\{\s*[Ww]erger-bin(?:\/ceribandin)?\s*\}\}/;
// code -> Kurmancî name, and back; filled once from NAMES_PAGE.
var codeToName = null;
var nameToCode = null;
var namesPromise = null;
function loadNames() {
if (!namesPromise) {
namesPromise = $.get({
url: mw.util.getUrl(NAMES_PAGE, { action: "raw", maxage: 86400, smaxage: 86400 }),
dataType: "json"
}).then(function (data) {
codeToName = data;
nameToCode = {};
Object.keys(data).forEach(function (code) {
var name = data[code].toLowerCase();
if (!nameToCode[name]) {
nameToCode[name] = code;
}
});
});
}
return namesPromise;
}
// A language code from a code or a Kurmancî name, or null.
function toCode(txt) {
txt = (txt || "").trim();
if (codeToName[txt]) {
return txt;
}
return nameToCode[txt.toLowerCase()] || null;
}
// The name of a language, or of a family lines are nested under.
function groupName(code) {
return codeToName[code] || new LangMetadata().getGroupName(code) || code;
}
function ucfirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey(s) {
return Array.from(s.toLowerCase().replace(/['ʼ", -]/g, "")).map(function (ch) {
var i = ALPHABET.indexOf(ch);
if (i !== -1) {
return String.fromCharCode(0x100 + i * 2);
}
i = ALPHABET.indexOf(ch.normalize("NFD").charAt(0));
return i !== -1 ? String.fromCharCode(0x101 + i * 2) : ch;
}).join("");
}
function compareNames(a, b) {
var ka = sortKey(a), kb = sortKey(b);
if (ka !== kb) {
return ka < kb ? -1 : 1;
}
return a < b ? -1 : a > b ? 1 : 0;
}
function sameName(a, b) {
return sortKey(a) === sortKey(b);
}
// The HTML of a rendered line after its "Language:" label.
function afterColon(html) {
var div = document.createElement('div');
div.innerHTML = html;
var walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);
var node;
while ((node = walker.nextNode())) {
var i = node.data.indexOf(':');
if (i !== -1) {
var range = document.createRange();
range.setStart(div, 0);
range.setEnd(node, i + 1);
range.deleteContents();
break;
}
}
return div.innerHTML;
}
var PICKER_SIZE = 12;
// A name without its diacritics, so "ital" finds "îtalî".
function fold(s) {
return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
// folded name -> code, built on first use.
var foldedNames = null;
// Up to PICKER_SIZE codes for what was typed: the code itself, then names starting with it (shortest
// first, so "fran" offers fransî before franc-comtoisî), then codes starting with it, common languages
// first ("cm" offers cmn), then names containing it.
function matchLanguages(term) {
term = term.trim().toLowerCase();
var exact = codeToName[term] ? [term] : [];
if (term.length < 2) {
return exact;
}
if (!foldedNames) {
foldedNames = Object.keys(nameToCode).map(function (name) {
return [fold(name), nameToCode[name]];
});
}
var folded = fold(term);
var meta = new LangMetadata();
var starts = [], contains = [];
foldedNames.forEach(function (pair) {
var i = pair[0].indexOf(folded);
if (pair[1] === term || i === -1) {
return;
}
(i === 0 ? starts : contains).push(pair[1]);
});
var codes = Object.keys(codeToName).filter(function (code) {
return code !== term && code.indexOf(term) === 0 && starts.indexOf(code) === -1;
}).sort(function (a, b) {
return (meta.isCommon(b) - meta.isCommon(a)) || a.length - b.length || (a < b ? -1 : 1);
});
function byName(a, b) {
return compareNames(codeToName[a], codeToName[b]);
}
starts.sort(function (a, b) {
return codeToName[a].length - codeToName[b].length || byName(a, b);
});
contains = contains.filter(function (code) {
return codes.indexOf(code) === -1;
});
return exact.concat(starts, codes, contains.sort(byName)).slice(0, PICKER_SIZE);
}
// Wraps a language input in wikt.suggest's dropdown of matching languages; picking one writes its name, and
// a code typed in is replaced by its name when the field is left. onChange runs whenever the value changes.
function languagePicker(input, onChange) {
var box = newNode('span', { 'class': 'translation-adder-langbox' }, input);
suggest(input, {
items: function (term) {
return matchLanguages(term).map(function (code) {
return { value: codeToName[code], code: code };
});
},
onPick: onChange
});
input.addEventListener('input', onChange);
input.addEventListener('blur', function () {
var value = input.value.trim();
if (codeToName[value]) {
input.value = codeToName[value];
}
onChange();
});
return box;
}
// Puts node at the end of a line's own content: after its last translation and that translation's ✎ ×,
// before the lines nested under it.
function appendToLine(line, node) {
if (/(?:^|\s)translation-adder-wrap(?:\s|$)/.test(line.className) && line.firstElementChild) {
line = line.firstElementChild;
}
var stop = null;
for (var child = line.firstChild; child; child = child.nextSibling) {
if (/^(UL|DL)$/.test(child.nodeName) ||
(child.nodeType === 1 && /(?:^|\s)translation-adder-line-tools(?:\s|$)/.test(child.className))) {
stop = child;
break;
}
}
line.insertBefore(node, stop);
}
function newNode(tagname) {
var node = document.createElement(tagname);
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
if (typeof argument == 'string') {
node.appendChild(document.createTextNode(argument));
} else if (typeof argument == 'object') {
if (argument instanceof Node) {
node.appendChild(argument);
} else {
for (var j in argument) {
if (j === 'class') {
node.className = argument[j];
} else if (j == 'style') {
node.style.cssText = argument[j];
} else if (typeof argument[j] == 'function') {
node.addEventListener(j, argument[j], false);
} else {
node.setAttribute(j, argument[j]);
}
}
}
}
}
return node;
}
// Form settings kept in localStorage.
function Preferences() {
var KEY = "wikt-translation-adder";
var storage = mw.storage.getObject(KEY) || {};
this.get = function (name, def) {
return storage[name] || def;
};
this.set = function (name, value) {
if (value === null || storage[name] === value)
return;
storage[name] = value;
mw.storage.setObject(KEY, storage);
};
}
var util = {
// Index of str (a string or RegExp) in text at or after pos, skipping comments, nowiki and templates
// other than a match itself; -1 if none.
getVanillaIndexOf: function (str, text, pos) {
function find(s) {
if (typeof s === 'string')
return text.indexOf(s, pos);
var i = text.substring(pos).search(s);
return i === -1 ? -1 : i + pos;
}
pos = pos || 0;
do {
var cpos = text.indexOf('<!--', pos),
tpos = text.indexOf('{{', pos),
wpos = text.indexOf('<nowiki>', pos),
spos = find(str);
pos = Math.min(
cpos == -1 ? Infinity : cpos,
tpos == -1 ? Infinity : tpos,
wpos == -1 ? Infinity : wpos,
spos == -1 ? Infinity : spos
);
if (pos == Infinity)
return -1;
if (pos == spos)
return pos;
var close = pos == cpos ? '-->' : pos == wpos ? '</' + 'nowiki>' : '}}'; // split, so this page's own <nowiki> does not end here
var end = text.indexOf(close, pos);
if (end == -1)
return -1;
pos = end + close.length;
} while (true);
},
// The template starting at pos: {name, end (after "}}"), params: [{key, value}]} (key null if positional).
parseTemplate: function (text, pos) {
var depth = 0, parts = [], start = pos + 2;
for (var i = pos; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === '{{' || two === '[[') {
depth++;
i++;
} else if (two === '}}' || two === ']]') {
depth--;
i++;
if (depth === 0) {
parts.push(text.substring(start, i - 1));
break;
}
} else if (text.charAt(i) === '|' && depth === 1) {
parts.push(text.substring(start, i));
start = i + 1;
}
}
return {
name: parts[0],
end: i + 1,
params: parts.slice(1).map(function (p) {
var m = /^\s*([^=\[\]{}|]+?)\s*=([\s\S]*)$/.exec(p);
return m ? { key: m[1], value: m[2] } : { key: null, value: p };
})
};
},
// The gloss (first positional parameter) of the box template starting at pos.
getWikitextGloss: function (text, pos) {
var params = util.parseTemplate(text, pos).params;
for (var i = 0; i < params.length; i++) {
if (params[i].key === null || params[i].key === '1')
return params[i].value.trim();
}
return '';
},
// The box template at pos with gloss as its first positional parameter.
setWikitextGloss: function (text, pos, gloss) {
var t = util.parseTemplate(text, pos);
var done = false;
var params = t.params.map(function (p) {
if (!done && (p.key === null || p.key === '1')) {
done = true;
return p.key === null ? gloss : '1=' + gloss;
}
return p.key === null ? p.value : p.key + '=' + p.value;
});
if (!done)
params.unshift(gloss);
return text.substring(0, pos) + '{{' + t.name + params.map(function (p) { return '|' + p; }).join('') +
'}}' + text.substring(t.end);
},
// Whether the words of the box's gloss (as the page shows it) appear, in order, in the wikitext gloss.
matchGloss: function (wikitextGloss, gloss) {
wikitextGloss = wikitextGloss.replace(/\[\[(?:[^\[\]|]*\|)?([^\[\]]*)\]\]/g, '$1').replace(/'{2,}/g, '');
if (/^\s*$/.test(gloss))
return /^\s*$/.test(wikitextGloss);
var words = gloss.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
var pos = 0;
for (var i = 0; i < words.length; i++) {
pos = wikitextGloss.indexOf(words[i], pos);
if (pos == -1)
return false;
}
return true;
},
// The table.translations of a node inside a box, or of a box heading.
getTable: function (node) {
var frame = $(node).closest('.NavFrame');
var table = $(node).closest('table.translations');
return table.length ? table[0] : frame.find('table.translations')[0];
},
getTransGloss: function (node) {
var table = util.getTable(node);
return table ? table.dataset.gloss || '' : '';
},
// [start, end] of the box's list in text: from the line after the opening template to the closing one.
getTransTable: function (text, node) {
var gloss = util.getTransGloss(node);
var table = util.getTable(node);
var tables = $('table.translations').get();
var all = [], found = [];
var pos = -1;
while ((pos = util.getVanillaIndexOf(TOP, text, pos + 1)) > -1) {
all.push(pos);
if (util.matchGloss(util.getWikitextGloss(text, pos), gloss))
found.push(pos);
}
if (found.length != 1) {
var same = tables.filter(function (t) {
return (t.dataset.gloss || '') === gloss;
});
if (all.length === tables.length) {
// Every box on the page is one in the wikitext: they match by order.
found = [all[tables.indexOf(table)]];
} else if (found.length > 1 && same.length === found.length) {
// Boxes with the same gloss are told apart by their order on the page.
found = [found[same.indexOf(table)]];
}
}
if (found.length == 1 && found[0] !== undefined) {
var start = util.parseTemplate(text, found[0]).end;
start = text.indexOf('\n', start) + 1;
var end = text.substring(start).search(BOTTOM);
if (start > 0 && end > -1)
return [start, start + end, found[0]];
}
console.trace("getTransTable failure; node = %o, gloss = %o", node, gloss);
return false;
},
validateNoWikisyntax: function (field, nonempty) {
return function (txt, error) {
if (/[\{\}#]/.test(txt))
return error("Di qada " + field + " de herfên {}# bi kar neyne.");
if (nonempty && !txt)
return error(ucfirst(field) + " binivîse.");
return txt;
};
},
// Codes from [[Modul:gender and number/data]], in the order they are written.
genderList: {
"n": "nêr", "n-d": "nêr, dual", "n-p": "nêr, pirjimar",
"m": "mê", "m-d": "mê, dual", "m-p": "mê, pirjimar",
"g": "giştî", "g-d": "giştî, dual", "g-p": "giştî, pirjimar",
"nt": "nêtar", "nt-d": "nêtar, dual", "nt-p": "nêtar, pirjimar",
"y": "yekjimar", "d": "dual", "p": "pirjimar",
"impf": "împerfektîv", "pf": "perfektîv"
},
genderRows: [["n", "n-d", "n-p"], ["m", "m-d", "m-p"], ["g", "g-d", "g-p"], ["nt", "nt-d", "nt-p"],
["y", "d", "p"], ["impf", "pf"]]
};
// The language name a line starts with ("Almanî: …").
function getLangName(li) {
var guess = li.textContent || '';
guess = guess.substr(0, guess.indexOf(':'));
return guess.replace(/^\s*/, '');
}
// The language code of a line, from {{Z}}'s span.trad-xx or else its first translation's lang attribute.
// Only the line's own nodes count: a group line written as text ("Sorbî:") has no code of its own.
function getLangCode(li) {
var own = /(?:^|\s)trans-(\S+)/.exec(li.className);
if (own)
return own[1];
var lang = false;
for (var node = li.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (node.nodeType !== 1) {
continue;
}
var spans = [node].concat(Array.prototype.slice.call(node.getElementsByTagName('span')));
for (var i = 0; i < spans.length; i++) {
var m = /(?:^|\s)trad-(\S+)/.exec(spans[i].className);
if (m)
return m[1];
if (!lang && spans[i].lang)
lang = spans[i].lang;
}
}
return lang;
}
// Whether a line is the group line for code: {{Z|code}}, or the group's name written as text.
function isGroupLine(li, code) {
var own = getLangCode(li);
return own ? own === code : sameName(getLangName(li), groupName(code));
}
function TranslationAdders(editor, $root) {
// The ± link on a box heading, which edits its gloss.
function TranslationLabeller(insertDiv) {
var original_span;
var adder_form;
var initial_value;
var edit_button;
var editing = false;
var adderInterface = {
'fields': {
'gloss': function (txt, error) {
return util.validateNoWikisyntax('mane', true)(txt, error);
}
},
'createForm': function () {
var thisId = "a" + String(Math.random()).replace(".", "");
adder_form = newNode('form', {
style: 'display: inline',
width: 'auto',
click: kill_event
},
newNode('label', {
'for': thisId
}, "Mane: "),
newNode('input', {
type: 'text',
name: 'gloss',
value: initial_value,
style: 'width: 50%',
title: 'Maneya ku ev tercime jê re ne, bi kurtî',
id: thisId
}),
newNode('input', {
type: 'submit',
name: 'preview',
value: 'Pêşdîtin'
})
);
return adder_form;
},
'onsubmit': function (values, render) {
render("''" + values.gloss + "''", function (new_html) {
if (editing)
toggle_editing(false);
var old_html = original_span.innerHTML;
editor.addEdit({
'undo': function () {
original_span.innerHTML = old_html;
if (!editing) toggle_editing();
},
'redo': function () {
original_span.innerHTML = new_html;
if (editing) toggle_editing();
},
'edit': function (text) {
return perform_edit(text, values.gloss);
},
'summary': 'mane: "' + (values.gloss.length > 50 ? values.gloss.substr(0, 50) + '…"' : values.gloss + '"')
}, original_span);
});
}
};
function perform_edit(wikitext, gloss) {
var pos = util.getTransTable(wikitext, insertDiv);
if (!pos) {
editor.error("Qutiya tercimeyan nehat dîtin.");
return wikitext;
}
return util.setWikitextGloss(wikitext, pos[2], gloss);
}
// Don't open and close the box when using the form.
function kill_event(e) {
e.stopPropagation();
}
function toggle_editing() {
if (editing) {
adder_form.style.display = "none";
original_span.style.display = "inline";
editing = false;
return;
}
editing = true;
edit_button.text("Tê barkirin…");
editor.withCurrentText(function (currentText) {
var pos = util.getTransTable(currentText, insertDiv);
edit_button.text('±');
if (!pos)
return editor.error("Qutiya tercimeyan nehat dîtin.");
initial_value = util.getWikitextGloss(currentText, pos[2]);
if (!original_span) {
original_span = newNode('span', {
'class': 'wt-edit-recurse'
});
for (var i = 0; i < insertDiv.childNodes.length; i++) {
var child = insertDiv.childNodes[i];
if (child != edit_button[0] && child.className !== 'NavToggle') {
original_span.appendChild(insertDiv.removeChild(child));
i--;
}
}
insertDiv.appendChild(original_span);
new AdderWrapper(editor, adderInterface, insertDiv, original_span);
}
original_span.style.display = "none";
adder_form.style.display = "inline";
adder_form.getElementsByTagName('input')[0].focus();
});
}
edit_button = $('<a href="#" class="translation-adder-gloss">±</a>')
.attr("title", "Maneya qutiyê biguherîne").css("padding", "2px").css("margin-left", "-5px")
.on("click", function (e) {
e.preventDefault();
kill_event(e);
toggle_editing();
return false;
});
$(insertDiv).prepend(edit_button);
}
function TranslationAdder(insertUl) {
var langmetadata = new LangMetadata();
this.fields = {
lang: function (txt, error) {
var code = toCode(txt);
if (!code)
return error("Koda zimanî an navê zimanî binivîse (de, almanî, …).");
if (code == 'ku' || code == 'kmr')
return error("Zimanekî ji bilî kurmancî hilbijêre.");
return code;
},
word: function (txt, error) {
if (txt.indexOf(',') == -1 || forceComma) {
forceComma = false;
if (langmetadata.expectedCase(toCode(thiz.elements.lang.value), mw.config.get('wgTitle'), txt) || forceCase) {
forceCase = false;
return util.validateNoWikisyntax('tercime', true)(txt, error);
}
if (prefs.get('case-knowledge', 'none') == 'none') {
return error(newNode('span',
"Tercime pir caran bi herfa biçûk dest pê dikin. Heke tu bawer î ku bi herfa mezin e, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceCase = true;
inputForm.onsubmit();
prefs.set('case-knowledge', 'guru');
}
}, "ji bo berdewamkirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('case-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke tu ne bawer î ku ev tercime bi herfa mezin e.");
error(msg);
return txt;
}
}
if (prefs.get('comma-knowledge', 'none') == 'none') {
return error(newNode('span',
"Her carê tenê tercimeyekê lê zêde bike. Heke ev tercimeyek e ku virgul tê de heye, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceComma = true;
inputForm.onsubmit();
prefs.set('comma-knowledge', 'guru');
}
}, "ji bo lêzêdekirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('comma-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke te dixwest çend tercimeyan bi hev re lê zêde bikî: ev nabe.");
error(msg);
return txt;
}
},
qual: util.validateNoWikisyntax('not'),
tr: util.validateNoWikisyntax('transliterasyon'),
lit: util.validateNoWikisyntax('maneya herfî'),
rawPageName: util.validateNoWikisyntax('navê rûpelê'),
nested: function (txt, error) {
if (!txt)
return '';
var group = toCode(txt) || (langmetadata.getGroupName(txt.trim()) ? txt.trim() : null);
return group || error("Ji bo \"di bin zimanê\" koda zimanekî an malbatekê binivîse.");
},
nclass1: util.validateNoWikisyntax('sinifa navdêrê'),
nclass2: util.validateNoWikisyntax('sinifa pirjimariyê')
};
var fields = this.fields;
Object.keys(util.genderList).forEach(function (g) {
fields[g] = 'checkbox';
});
this.createForm = function () {
function createGenderNode(name, text) {
var inp = document.createElement("input");
inp.type = "checkbox";
inp.name = name;
var lbl = document.createElement("label");
lbl.appendChild(inp);
lbl.appendChild(document.createTextNode(text));
return lbl;
}
var genderControls = {};
for (var g in util.genderList) genderControls[g] = createGenderNode(g, util.genderList[g]);
var langName = newNode('span', { 'class': 'translation-adder-langname' });
var controls = {
lang: newNode('input', {
size: 12,
type: 'text',
name: 'lang',
value: prefs.get('curlang', ''),
placeholder: 'ziman',
title: 'Koda zimanî (de, fr, …) an navê wî (almanî, fransî, …)'
}),
transliteration: newNode('p', "Transliterasyon: ",
newNode('input', {
name: "tr",
title: "Tercime bi herfên latînî."
}), " (nimûne: zìmǔ ji bo 字母)"),
literal: newNode('p', "Maneya herfî: ",
newNode('input', {
name: "lit",
title: "Maneya kelîme bi kelîme ya tercimeyê."
})),
qualifier: newNode('p', "Not: ", newNode('input', {
name: 'qual',
title: "Notek kurt li ser tercimeyê"
}), " (nimûne: kevn, devkî)"),
display: newNode('p', "Navê rûpelê: ", newNode('input', {
name: 'rawPageName',
title: "Heke tercime ne forma bingehîn be, navê rûpela ku jê re lînk bê dayîn."
}), " (nimûne: 疲れる ji bo 疲れた)"),
nested: newNode('p', "Di bin zimanê: ", newNode('input', {
name: 'nested',
title: "Zimanê ku ev ziman di bin wî de tê rêzkirin"
}), " (nimûne: zh ji bo mandarînî)"),
genders: genderControls,
nclass: newNode('p',
"Sinifa navdêrê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass1',
title: "Sinifa navdêrê di forma yekjimar de."
}),
" Sinifa pirjimariyê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass2',
title: "Sinifa navdêrê di forma pirjimar de, heke hebe."
}))
};
var $gender = $("<p>");
util.genderRows.forEach(function (row) {
var $div = $("<div>");
row.forEach(function (code) { $div.append(controls.genders[code]); });
$gender.append($div);
});
controls.gender = $gender[0];
langInput = controls.lang;
var showButton = newNode('span', {
'click': function () {
advancedMode = !advancedMode;
showButton.textContent = advancedMode ? " Kêmtir" : " Zêdetir";
updateFieldVisibility.call(langInput, true);
},
'style': "color: var(--wikt-palette-blue,#0000FF); cursor: pointer;"
}, advancedMode ? " Kêmtir" : " Zêdetir");
function autoTransliterate() {
var rawPageName = langmetadata.computeRawPageName(toCode(thiz.elements.lang.value), thiz.elements.word.value);
if (rawPageName && rawPageName !== thiz.elements.word.value)
thiz.elements.rawPageName.value = rawPageName;
}
function updateFieldVisibility(preserve) {
preserve = (preserve === true);
function show() {
for (var i = 0; i < arguments.length; i++) {
arguments[i].style.display = arguments[i].nodeName == 'P' ? "block" : "inline";
}
}
function hide() {
for (var i = 0; i < arguments.length; i++)
arguments[i].style.display = "none";
}
// Shows the remaining arguments if the first is true, else hides them.
function toggle(condition) {
var nodes = [].slice.call(arguments, 1);
(condition ? show : hide).apply(this, nodes);
}
var lang = toCode(langInput.value) || '';
// A code in the field gets its name beside it, until leaving the field puts the name in.
var typed = langInput.value.trim();
langName.textContent = codeToName[typed] ? " (" + codeToName[typed] + ")" : "";
if (!preserve) {
autoTransliterate();
// Mandarînî goes under Çînî and so on, unless "Di bin zimanê" is changed.
thiz.elements.nested.value = lang ? langmetadata.getNested(lang) : '';
}
var scriptGuess = langmetadata.guessScript(lang);
if (!advancedMode) {
var g = lang ? langmetadata.getGenders(lang) : undefined;
if (g === undefined) {
hide(controls.gender);
} else {
for (var genderName in util.genderList) {
toggle(g.indexOf(genderName) != -1, controls.genders[genderName]);
}
}
toggle(g, controls.gender);
toggle(langmetadata.hasNounClasses(lang), controls.nclass);
var hasAutomaticTransliteration = new window.LanguageUtils().HasAutomaticTransliteration(lang);
toggle(scriptGuess && scriptGuess != 'Latn' && !hasAutomaticTransliteration, controls.transliteration);
toggle(langmetadata.needsRawPageName(lang), controls.display);
hide(controls.literal, controls.qualifier, controls.nested);
} else {
show.apply(this, [controls.gender].concat(
Object.keys(util.genderList).map(function (k) { return controls.genders[k]; }),
[controls.nclass, controls.transliteration, controls.literal, controls.qualifier, controls.display,
controls.nested]));
}
}
var langBox = languagePicker(langInput, function () {
updateFieldVisibility.call(langInput);
});
window.setTimeout(function () {
updateFieldVisibility.call(langInput);
}, 0);
inputForm = newNode('form',
newNode('p', "Tercimeyekê lê zêde bike: ",
langBox, langName, newNode('b', ': '), newNode('input', {
'name': 'word',
placeholder: 'tercime',
title: 'Tercime bi wî zimanî',
size: 20,
keyup: autoTransliterate,
change: autoTransliterate
}),
newNode('input', {
'type': 'submit',
'value': 'Pêşdîtin'
}), showButton
),
controls.gender,
controls.nclass,
controls.transliteration,
controls.literal,
controls.display,
controls.qualifier,
controls.nested
);
return inputForm;
};
this.onsubmit = function (values, render) {
var wikitext;
langmetadata.retrieveRawPageName(values.lang, values.word, values.rawPageName, function (rawPageName, needsRawPageName) {
if (needsRawPageName) {
values.rawPageName = rawPageName;
values.wordMinus = '';
} else {
values.rawPageName = '';
values.wordMinus = rawPageName;
}
langmetadata.hasWiktionaryWithEntry(values.lang, values.rawPageName || values.wordMinus, function (hasInterwiki) {
var templateBody = '|' + values.lang + '|' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) +
(values.nclass1 ? '|c' + values.nclass1 : '') +
(values.nclass2 ? '|c' + values.nclass2 : '');
templateBody += Object.keys(util.genderList).map(function (k) {
return values[k] ? '|' + k : '';
}).join("");
templateBody +=
(values.tr ? '|tr=' + values.tr : '') +
(values.lit ? '|lit=' + values.lit : '') +
(values.rawPageName ? '|cuda=' + values.word : '');
var qualifier = values.qual ? ' {{i|' + values.qual + '}}' : '';
wikitext = '{{Z|' + values.lang + '}}: {{' + (hasInterwiki ? "W+" : "W") + templateBody + "}}" + qualifier;
render(wikitext, function (html) {
registerEdits(values, wikitext, html);
});
});
});
};
var thiz = this;
var prefs = new Preferences();
var langInput;
var inputForm;
var advancedMode = prefs.get('more-display', 'none') != 'none';
var forceComma = false;
var forceCase = false;
function resetElements() {
prefs.set('more-display', advancedMode ? 'block' : 'none');
['word', 'nclass1', 'nclass2', 'lit', 'tr', 'rawPageName', 'qual'].forEach(function (f) {
thiz.elements[f].value = '';
});
Object.keys(util.genderList).forEach(function (g) {
thiz.elements[g].checked = false;
});
prefs.set('curlang', thiz.elements.lang.value);
}
// Runs after the new line has been rendered: finds where it goes and registers the edit.
function registerEdits(values, wikitext, content) {
var li = newNode('li', {
'class': 'trans-' + values.lang
});
li.innerHTML = content;
var lang = getLangName(li);
var summary = '+' + codeToName[values.lang] + ': [[' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) + ']]';
var insertBefore = null;
var nextLanguage = null;
var nestedCode = values.nested;
// A top-level line for the language already there takes the translation, and then moves under its group.
var table = util.getTable(insertUl);
var nestAfter = nestedCode && misplacedLines(table, values.lang).length ? values.lang : null;
if (nestAfter) {
nestedCode = '';
}
var nestedHeading = nestedCode ? ucfirst(groupName(nestedCode)) : '';
var nestedWikitext = "\n* {{Z|" + nestedCode + "}}:\n*: " + wikitext;
function addEdit(edit, span) {
var fields = ["lang", "word", "nclass1", "nclass2", "rawPageName", "qual", "tr", "lit"];
editor.addEdit({
'undo': function () {
edit.undo();
if (fields.slice(1).every(function (f) { return thiz.elements[f].value === ""; })) {
for (var i = 0; i < fields.length; i++) {
thiz.elements[fields[i]].value = values[fields[i]];
}
for (var genderName in util.genderList) {
thiz.elements[genderName].checked = values[genderName];
}
}
},
'redo': function () {
edit.redo();
// Editing mode, so the new translation has its ✎ and × at once.
setLineEditing(table, editor, true);
for (var i = 0; i < fields.length; i++) {
if (thiz.elements[fields[i]].value != values[fields[i]])
return;
}
resetElements();
},
'edit': edit.edit,
'summary': summary
}, span);
if (nestAfter) {
editor.addEdit(nestEdit(table, nestAfter), null);
}
}
if (lang && !nestedCode) {
// Every line in this box.
var lis = $(insertUl).parent().parent().find('li').get()
.concat($(insertUl).parent().parent().find('dd').get());
for (var j = 0; j < lis.length; j++) {
if (lis[j].getElementsByTagName('form').length > 0)
continue;
var ln = getLangName(lis[j]);
if (ln && sameName(ln, lang)) {
var span = newNode('span', { 'class': 'translation-adder-added' });
var parent = lis[j];
if (parent.getElementsByTagName('ul').length + parent.getElementsByTagName('dl').length == 0) {
span.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(parent, span);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to the language's existing line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
var node = parent.firstChild;
var hastrans = false;
while (node) {
if (node.nodeType == 1) {
var nn = node.nodeName;
if (nn == 'UL' || nn == 'DL') {
span.innerHTML = (hastrans ? ", " : " ") + afterColon(content);
addEdit({
'redo': function () {
parent.insertBefore(span, node);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to a language that has nested lines under it.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + (hastrans ? "," : "") + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
hastrans = true;
}
}
node = node.nextSibling;
}
}
} else if (ln && compareNames(ln, lang) > 0 && (!nextLanguage || compareNames(ln, nextLanguage) < 0) && lis[j].parentNode.parentNode.nodeName != 'LI') {
nextLanguage = ln;
var before = lis[j];
insertBefore = [{
'redo': function () {
before.parentNode.insertBefore(li, before);
},
'undo': function () {
before.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, ln, getLangCode(before), function (text, ipos) {
// A new line before the next language's line.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend);
})
}, li];
}
}
}
if (nestedCode) {
nextLanguage = null;
insertBefore = null;
li.innerHTML = nestedHeading + ":" + "<dl><dd class=\"trans-" + values.lang + "\">" + content + "</dd></dl>";
var tops = insertUl.parentNode.parentNode.getElementsByTagName('li');
for (var k = 0; k < tops.length; k++) {
if (tops[k].getElementsByTagName('form').length > 0)
continue;
// Only top-level lines.
if (tops[k].parentNode.parentNode.nodeName != 'TD')
continue;
var tln = getLangName(tops[k]);
if (tln && sameName(tln, nestedHeading)) {
var group = tops[k];
var sublis = group.getElementsByTagName('li');
if (!sublis.length)
sublis = group.getElementsByTagName('dd');
if (sublis.length == 0) {
var dd = newNode('dd', {
'class': 'trans-' + values.lang
});
var dl = newNode('dl', dd);
dd.innerHTML = content;
addEdit({
'redo': function () {
group.appendChild(dl);
},
'undo': function () {
group.removeChild(dl);
},
'edit': getEditFunction(values, wikitext, nestedHeading, nestedCode, function (text, ipos) {
// The first nested line under the group's line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "\n*: " + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
}
var item = newNode(sublis[0].nodeName, {
'class': 'trans-' + values.lang
});
var linestart = item.nodeName == 'DD' ? '\n*: ' : '\n** ';
item.innerHTML = content;
for (var m = 0; m < sublis.length; m++) {
var subln = getLangName(sublis[m]);
var sub = sublis[m];
if (subln && sameName(subln, lang)) {
var addSpan = newNode('span', { 'class': 'translation-adder-added' });
addSpan.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(sub, addSpan);
},
'undo': function () {
sub.removeChild(addSpan);
},
'edit': getEditFunction(values, wikitext, lang, values.lang, function (text, ipos) {
// Adding to an existing nested line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, addSpan);
return resetElements();
} else if (subln && langmetadata.nestsBefore(lang, subln)) {
addEdit({
'redo': function () {
sub.parentNode.insertBefore(item, sub);
},
'undo': function () {
sub.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, subln, getLangCode(sub), function (text, ipos) {
// A nested line in order.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
}
}
var last = sublis[sublis.length - 1];
addEdit({
'redo': function () {
last.parentNode.appendChild(item);
},
'undo': function () {
last.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, getLangName(last), getLangCode(last), function (text, ipos) {
// A nested line at the end of its group.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
} else if (tln && compareNames(tln, nestedHeading) > 0 && (!nextLanguage || compareNames(tln, nextLanguage) < 0)) {
nextLanguage = tln;
var next = tops[k];
insertBefore = [{
'redo': function () {
next.parentNode.insertBefore(li, next);
},
'undo': function () {
next.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, tln, getLangCode(next), function (text, ipos) {
// A new group line with the nested line under it.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + nestedWikitext + text.substr(lineend);
})
}, li];
}
}
wikitext = "{{Z|" + nestedCode + "}}:\n*: " + wikitext;
}
li.className = "trans-" + values.lang;
if (insertBefore) {
addEdit(insertBefore[0], insertBefore[1]);
} else {
// After the last line.
addEdit({
'redo': function () {
insertUl.appendChild(li);
},
'undo': function () {
insertUl.removeChild(li);
},
'edit': getEditFunction(values, wikitext)
}, li);
}
return resetElements();
}
// The wikitext change for this submission. With findLanguage, callback gets the position of that
// language's line; without, the line goes at the end of the box.
function getEditFunction(values, wikitext, findLanguage, findLangCode, callback) {
return function (text) {
var p = util.getTransTable(text, insertUl);
if (!p)
return editor.error("Qutiya tercimeyan ji bo '" + values.lang + ":" + values.word +
"' nehat dîtin. Maneyên qutiyan divê ji hev cuda bin.");
var stapos = p[0];
var endpos = p[1];
if (findLanguage) {
var box = text.substring(stapos, endpos);
var sub = values.nested ? "[:*]?" : "";
var escapedName = mw.util.escapeRegExp(findLanguage);
var patterns = [
"\\*" + sub + " ?\\[\\[" + escapedName + "\\]\\]:",
"\\*" + sub + " ?" + escapedName + ":"
];
if (findLangCode)
patterns.unshift("\\*" + sub + " ?\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(findLangCode) + "\\s*(?:\\|[^{}]*)?\\}\\}\\s*:");
var ipos = -1;
for (var i = 0; i < patterns.length && ipos == -1; i++) {
ipos = box.search(new RegExp(patterns[i], "i"));
}
if (ipos > -1) {
return callback(text, ipos + stapos);
}
return editor.error("Rêza zimanê " + findLanguage + " nehat dîtin. Ji kerema xwe rêzê rast bike.");
}
return text.substr(0, endpos) + "* " + wikitext + "\n" + text.substr(endpos);
};
}
}
function createTranslationAdderRow(li) {
var $ul = $("<ul>").append(li);
return $("<tr>")
.addClass("translation-adder-row")
.append($("<td>").attr("colspan", "3").css("text-align", "left").append($ul));
}
$root.find('table.translations').each(function () {
if (this.dataset.translationAdder) {
return;
}
this.dataset.translationAdder = "1";
var td = $(this).find("td.translations-cell")[0];
if (!td) {
return;
}
var uls = $(td).children("ul").get();
if (uls.length == 0) {
uls = [td.appendChild(newNode('ul'))];
}
var li = document.createElement('li');
$(this).append(createTranslationAdderRow(li));
new AdderWrapper(editor, new TranslationAdder(uls[uls.length - 1]), li);
var head = $(this).closest('.NavFrame').children('.NavHead')[0];
if (head) {
new TranslationLabeller(head);
}
});
}
// HTML for wikitext as this page renders it, without the paragraph around it.
function render(wikitext) {
return new mw.Api().parse(wikitext, {
title: mw.config.get('wgPageName'),
pst: true,
disablelimitreport: true
}).then(function (html) {
var $out = $('<div>').html(html);
var $p = $out.find('p').first();
return ($p.length ? $p : $out.children().first()).html().trim();
});
}
// The wikitext of a translation line: {start, restStart, end, rest}, where rest is what follows
// "Language:"; null when the line is not found.
function findLine(text, line) {
var box = util.getTransTable(text, line);
if (!box) {
return null;
}
var code = getLangCode(line);
var name = getLangName(line);
var label = code ?
"\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(code) + "\\s*(?:\\|[^{}\\n]*)?\\}\\}" :
"(?:\\[\\[" + mw.util.escapeRegExp(name) + "\\]\\]|" + mw.util.escapeRegExp(name) + ")";
var part = text.substring(box[0], box[1]);
var m = new RegExp("^\\*[:*]* ?" + label + "\\s*:[ \\t]*(.*)$", "im").exec(part);
if (!m) {
return null;
}
var start = box[0] + m.index;
var end = start + m[0].length;
return { start: start, restStart: end - m[1].length, end: end, rest: m[1], label: text.substring(start, end - m[1].length) };
}
// A line's own nodes: everything before the lines nested under it, without this gadget's buttons.
function ownNodes(line) {
var nodes = [];
for (var node = line.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (!(node.nodeType === 1 && /(?:^|\s)translation-adder-(?:line-tools|term-edit)(?:\s|$)/.test(node.className))) {
nodes.push(node);
}
}
return nodes;
}
var TERM_CALL = /\{\{\s*W[+-]?(?:\/ceribandin)?\s*\|/g;
// The {{W}} calls in the wikitext after "Language:", in order: [{start, end, text}].
function termCalls(rest) {
var out = [];
var m;
TERM_CALL.lastIndex = 0;
while ((m = TERM_CALL.exec(rest))) {
var end = util.parseTemplate(rest, m.index).end;
out.push({ start: m.index, end: end, text: rest.substring(m.index, end) });
TERM_CALL.lastIndex = end;
}
return out;
}
// rest with its i-th {{W}} call replaced by text, or removed with the comma that joins it to the others.
function replaceTerm(rest, i, text) {
var call = termCalls(rest)[i];
if (!call) {
return null;
}
if (text) {
return rest.substring(0, call.start) + text + rest.substring(call.end);
}
var before = rest.substring(0, call.start);
var after = rest.substring(call.end);
if (/,\s*$/.test(before)) {
before = before.replace(/\s*,\s*$/, '');
} else {
after = after.replace(/^\s*,\s*/, '');
}
return (before + after).trim();
}
// The rendered translations of a line, in order: the element each {{W}} call starts with.
function termNodes(line) {
var nodes = [];
ownNodes(line).forEach(function (n) {
if (n.nodeType === 1 && /(?:^|\s)translation-adder-added(?:\s|$)/.test(n.className)) {
nodes.push.apply(nodes, Array.prototype.slice.call(n.childNodes));
} else {
nodes.push(n);
}
});
// A translation starts with its link's span, or with the "ttbc" span of one marked for checking.
// The second form after a "/" (traditional / simplified Chinese) belongs to the same translation.
var terms = [];
var afterSlash = false;
nodes.forEach(function (n) {
if (n.nodeType === 3) {
if (n.data.trim()) {
afterSlash = n.data.trim() === '/';
}
return;
}
if (n.nodeType !== 1) {
return;
}
// The separator is drawn in a span of its own.
if (!n.lang && n.textContent.trim() === '/') {
afterSlash = true;
return;
}
var isTerm = /(?:^|\s)ttbc(?:\s|$)/.test(n.className) ||
(n.lang && !/(?:^|\s)(?:tr|ts)(?:\s|$)/.test(n.className));
if (isTerm && !(afterSlash && terms.length)) {
terms.push(n);
}
afterSlash = false;
});
return terms;
}
// The template dialog of [[MediaWiki:Gadget-wikt.cite.js]], built from the template's TemplateData.
// Resolves with the edited call, "" when it is removed, or null when cancelled.
function templateDialog(call) {
return mw.loader.using('ext.gadget.wikt.cite').then(function (require) {
return require('ext.gadget.wikt.cite').openTemplateDialog(call);
});
}
// On one translation line: ✎ and × after each translation (its template in a dialog, and removing it).
function LineTools(editor, line) {
var name = getLangName(line);
if (!name) {
return;
}
var code = getLangCode(line);
var title = ucfirst(codeToName[code] || name);
var $tools = $('<span class="translation-adder-line-tools">');
var self = this;
this.$tools = $tools;
this.line = line;
// Whether editing mode is on; set by setLineEditing().
this.editing = true;
var nested = line.querySelector('ul, dl');
if (nested) {
$(nested).before($tools);
} else {
$(line).append($tools);
}
function notFound() {
return editor.error("Rêza " + title + " di wîkîmetnê de nehat dîtin.");
}
// The i-th translation drawn is the i-th {{W}} call only when the line holds nothing else that draws
// one ({{g}}, {{l}}, …); otherwise ✎ and × could reach the wrong one, so they refuse.
function callsMatch(found) {
if (termCalls(found.rest).length === termNodes(line).length) {
return true;
}
editor.error("Tercimeyên rêza " + title + " bi wîkîmetnê re li hev nayên; rêzê bi destan biguherîne.");
return false;
}
// Queues a new text after "Language:", drawn in place from its rendering.
function queueRest(makeRest, summary) {
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var rest = found && makeRest(found.rest);
if (rest === null || rest === undefined) {
return notFound();
}
render(found.label.replace(/^\*[:*]* ?/, '') + ' ' + rest).then(function (html) {
var before = ownNodes(line);
var holder = document.createElement('span');
holder.innerHTML = html;
var after = Array.prototype.slice.call(holder.childNodes);
editor.addEdit({
redo: function () {
before.forEach(function (n) { $(n).detach(); });
after.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
undo: function () {
after.forEach(function (n) { $(n).detach(); });
before.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
edit: function (current) {
var at = findLine(current, line);
var next = at && makeRest(at.rest);
if (next === null || next === undefined) {
return notFound();
}
return current.substring(0, at.restStart) + next + current.substring(at.end);
},
summary: summary
}, line);
});
});
}
// The pencil after each translation, put back whenever the line is redrawn.
this.markTerms = function () {
$(line).children('.translation-adder-term-edit')
.add($(line).children('.translation-adder-added').children('.translation-adder-term-edit'))
.remove();
var terms = termNodes(line);
terms.forEach(function (term, i) {
var $edit = $('<a href="#" class="translation-adder-term-edit translation-adder-term-pencil"></a>')
.attr({ title: 'Vê tercimeyê biguherîne', 'aria-label': 'Vê tercimeyê biguherîne' });
// After everything the translation draws: its interwiki link, gender and transliteration.
var last = term;
while (last.nextSibling && !/^(UL|DL)$/.test(last.nextSibling.nodeName) &&
terms.indexOf(last.nextSibling) === -1 &&
!(last.nextSibling.nodeType === 3 && /[,;]/.test(last.nextSibling.data)) &&
!(last.nextSibling.nodeType === 1 &&
/translation-adder-(?:line-tools|added)/.test(last.nextSibling.className))) {
last = last.nextSibling;
}
var $del = $('<a href="#" class="translation-adder-term-edit translation-adder-term-close"></a>')
.attr({ title: 'Vê tercimeyê rake', 'aria-label': 'Vê tercimeyê rake' });
$(last).after($edit, $del);
$edit.add($del).toggle(self.editing);
// The only translation of a line with nothing under it takes the line with it.
$del.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
if (!found || !termCalls(found.rest)[i]) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
if (termCalls(found.rest).length === 1 && !line.querySelector('ul, dl')) {
removeLine();
} else {
queueRest(function (rest) {
return replaceTerm(rest, i, '');
}, '−' + title);
}
});
});
$edit.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var call = found && termCalls(found.rest)[i];
if (!call) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
templateDialog(call.text).then(function (edited) {
if (edited === null || edited === call.text) {
return;
}
queueRest(function (rest) {
return replaceTerm(rest, i, edited);
}, (edited ? '±' : '−') + title);
});
});
});
});
};
this.markTerms();
function removeLine() {
editor.addEdit({
redo: function () {
line.classList.add('translation-adder-removed');
},
undo: function () {
line.classList.remove('translation-adder-removed');
},
edit: function (current) {
var at = findLine(current, line);
if (!at) {
return notFound();
}
return current.substring(0, at.start) + current.substring(at.end + (current.charAt(at.end) === '\n' ? 1 : 0));
},
summary: '−' + title
}, line);
}
}
// ---- nesting: a language en.wiktionary nests (cmn) goes under its group's line ({{Z|zh}}) ----
// The language code of a wikitext line, from its {{Z}}.
function lineCode(l) {
var m = /^\*[:*]* ?\{\{\s*Z\s*\|\s*([^|}\s]+)/.exec(l);
return m ? m[1] : null;
}
// The language name of a wikitext line, for sorting.
function lineName(l) {
var code = lineCode(l);
if (code) {
return groupName(code);
}
var m = /^\*[:*]* ?(?:\[\[)?([^\]:|]+)/.exec(l);
return m ? m[1].trim() : '';
}
function isChildLine(l) {
return /^\*[:*]/.test(l) && !/^\*[:*][:*]/.test(l);
}
// text with the top-level lines of node's box whose language nests (only that language, if given) moved
// under their group line, which is added in order where there is none. A group that already has a line for
// the language gets its translations added to it.
function nestLines(text, node, only) {
var p = util.getTransTable(text, node);
if (!p) {
return null;
}
var meta = new LangMetadata();
var body = text.substring(p[0], p[1]);
var trimmed = body.replace(/\n+$/, '');
var tail = body.slice(trimmed.length);
var head = [], entries = [];
trimmed.split('\n').forEach(function (l) {
if (/^\*(?![:*])/.test(l)) {
entries.push({ line: l, code: lineCode(l), children: [] });
} else if (entries.length) {
entries[entries.length - 1].children.push(l);
} else {
head.push(l);
}
});
var moved = 0;
entries.slice().forEach(function (e) {
var parent = e.code && meta.getNested(e.code);
if (!parent || (only && e.code !== only)) {
return;
}
entries.splice(entries.indexOf(e), 1);
var group = entries.filter(function (x) {
return x.code ? x.code === parent : sameName(lineName(x.line), groupName(parent));
})[0];
if (!group) {
group = { line: '* {{Z|' + parent + '}}:', code: parent, children: [] };
var at = entries.findIndex(function (x) {
return compareNames(lineName(x.line), groupName(parent)) > 0;
});
entries.splice(at === -1 ? entries.length : at, 0, group);
}
var prefix = group.children.some(function (c) { return /^\*\*/.test(c); }) ? '**' : '*:';
var deeper = e.children.map(function (c) { return prefix + c.slice(1); });
var same = group.children.findIndex(function (c) { return isChildLine(c) && lineCode(c) === e.code; });
if (same !== -1) {
var terms = e.line.replace(/^\*\s*\{\{[^{}]*\}\}\s*:\s*/, '');
group.children[same] = group.children[same].replace(/\s*$/, '') + (terms ? ', ' + terms : '');
[].splice.apply(group.children, [same + 1, 0].concat(deeper));
} else {
var name = codeToName[e.code] || e.code;
var at2 = group.children.findIndex(function (c) {
return isChildLine(c) && compareNames(lineName(c), name) > 0;
});
[].splice.apply(group.children, [at2 === -1 ? group.children.length : at2, 0]
.concat([prefix + ' ' + e.line.replace(/^\*\s*/, '')], deeper));
}
moved++;
});
if (!moved) {
return text;
}
var out = head.slice();
entries.forEach(function (x) {
out.push(x.line);
out.push.apply(out, x.children);
});
return text.substring(0, p[0]) + out.join('\n') + tail + text.substring(p[1]);
}
// The top-level lines of a box.
function topLines(table) {
return $(table).find('td.translations-cell').first().children('ul').children('li').get();
}
// The top-level lines whose language nests: [{li, code, parent}].
function misplacedLines(table, only) {
var meta = new LangMetadata();
return topLines(table).map(function (li) {
var code = getLangCode(li);
var parent = code && meta.getNested(code);
return parent && (!only || code === only) ? { li: li, code: code, parent: parent } : null;
}).filter(Boolean);
}
// Draws nestLines() on the page: the lines move under their group, highlighted. Returns the undo.
function nestDom(table, only) {
var undos = [];
misplacedLines(table, only).forEach(function (m) {
var group = topLines(table).filter(function (li) { return isGroupLine(li, m.parent); })[0];
if (!group) {
var made = newNode('li', { 'class': 'trans-' + m.parent + ' translation-adder-new' },
ucfirst(groupName(m.parent)) + ':');
var next = topLines(table).filter(function (li) {
return compareNames(getLangName(li), getLangName(made)) > 0;
})[0];
m.li.parentNode.insertBefore(made, next || null);
group = made;
undos.push(function () { $(made).remove(); });
}
var list = $(group).children('dl, ul')[0];
if (!list) {
var newList = list = group.appendChild(newNode('dl'));
undos.push(function () { $(newList).remove(); });
}
var home = m.li.parentNode, after = m.li.nextSibling;
var item = $(list).children().filter(function () { return getLangCode(this) === m.code; })[0];
if (item) {
var span = newNode('span', { 'class': 'translation-adder-new' });
var holder = document.createElement('div');
ownNodes(m.li).forEach(function (n) { holder.appendChild(n.cloneNode(true)); });
span.innerHTML = ', ' + afterColon(holder.innerHTML);
var sub = $(item).children('dl, ul')[0];
item.insertBefore(span, sub || null);
m.li.style.display = 'none';
undos.push(function () { $(span).remove(); m.li.style.display = ''; });
} else {
var before = $(list).children().filter(function () {
return compareNames(getLangName(this), getLangName(m.li)) > 0;
})[0];
// In a "*:" list the line is drawn as a <dd>, as the saved page will have it.
var wrap = list.nodeName === 'DL' ? newNode('dd', { 'class': 'translation-adder-wrap' }) : null;
if (wrap) {
wrap.appendChild(m.li);
list.insertBefore(wrap, before || null);
} else {
list.insertBefore(m.li, before || null);
}
m.li.classList.add('translation-adder-new');
undos.push(function () {
m.li.classList.remove('translation-adder-new');
home.insertBefore(m.li, after);
$(wrap).remove();
});
}
});
return function () {
undos.reverse().forEach(function (f) { f(); });
};
}
// The change that nests the lines of table's box (only one language's, if given).
function nestEdit(table, only) {
var undo = null;
return {
redo: function () {
undo = nestDom(table, only);
},
undo: function () {
if (undo) {
undo();
undo = null;
}
},
edit: function (text) {
return nestLines(text, table, only);
},
summary: 'tercime di bin zimanên xwe de'
};
}
// Shows (on) or hides the ✎ and × on every translation line of the boxes in root, and on a box with
// lines that belong under another language a link that moves them there.
function setLineEditing(root, editor, on) {
return loadNames().then(function () {
editor = editor || new Editor();
// root may be a box itself.
var $tables = $(root).find('table.translations').addBack('table.translations');
$tables.each(function () {
var table = this;
var $row = $(table).data('translationAdderNest');
if (!$row && on) {
var count = misplacedLines(table).length;
if (!count) {
return;
}
var $nest = $('<a href="#">').text(count === 1 ?
'Rêzekê di bin zimanê wê de bi cih bike' : count + ' rêzan di bin zimanên wan de bi cih bike');
$row = $('<tr class="translation-adder-nest-row">').append($('<td colspan="3">').append('↳ ', $nest));
$(table).find('td.translations-cell').first().parent().before($row);
$(table).data('translationAdderNest', $row);
$nest.on('click', function (e) {
e.preventDefault();
editor.addEdit(nestEdit(table), null);
$row.remove();
});
}
if ($row) {
$row.toggle(on);
}
});
$tables.find('td.translations-cell').find('li, dd').each(function () {
var tools = $(this).data('translationAdderTools');
if (!tools && on) {
tools = new LineTools(editor, this);
$(this).data('translationAdderTools', tools);
}
if (tools && tools.$tools) {
tools.editing = on;
tools.$tools.toggle(on);
if (on) {
tools.markTerms();
}
$(this).find('.translation-adder-term-edit').filter(function () {
return $(this).closest('li, dd')[0] === tools.line;
}).toggle(on);
}
});
});
}
// Adds the forms to the boxes in root (an element or jQuery object). editor collects the changes: by
// default this gadget's own Editor; DefSideBoxes passes one that queues them in its save box.
function attach(root, editor) {
return loadNames().then(function () {
TranslationAdders(editor || new Editor(), $(root));
});
}
window.wikt = window.wikt || {};
window.wikt.translationAdder = {
attach: attach,
setLineEditing: setLineEditing,
compareNames: compareNames
};
module.exports = window.wikt.translationAdder;
// </nowiki>
8vo04kblukq6c5rnvqq5o8nyduytv3o
6865578
6865558
2026-10-23T20:38:01Z
Wikihez
7344
Language list from wikt.suggest, shared with every language box
6865578
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.translation-adder.js]]
*
* Adds a form under each translation box ({{werger-ser}} … {{werger-bin}}) to add a line such as
* * {{Z|de}}: {{W+|de|Wasser|n}}
* in Kurmancî alphabetical order, and a ± link on the box heading to change its gloss. Forked from
* en.wiktionary's [[en:MediaWiki:Gadget-TranslationAdder.js]] (by [[en:User:Conrad.Irwin]] and others).
*
* DefSideBoxes ([[MediaWiki:Gadget-wikt.dsb.js]]) loads it from the pencil on a Werger heading and calls
* attach(root, editor) with an editor from wikt.dsb-panels, so the changes join its save queue. Without
* an editor, edits pile up in this gadget's own box at the top left.
*
* The form reads only boxes from [[Modul:translations]] (table.translations with data-gloss); the older
* div.translations boxes are left to the "translation_editor" gadget.
*/
/* global mw, $, AdderWrapper, LangMetadata, Editor */
require("./wikt.translation-adder/editor.js");
require("./wikt.translation-adder/language-utils.js");
require("./wikt.translation-adder/data.js");
var suggest = require("ext.gadget.wikt.suggest");
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// Box templates in wikitext; the /ceribandin copies are the test versions.
var TOP = /\{\{\s*[Ww]erger-(?:ser|bnr-h)(?:\/ceribandin)?\s*(?=[|}])/;
var BOTTOM = /\{\{\s*[Ww]erger-bin(?:\/ceribandin)?\s*\}\}/;
// code -> Kurmancî name, and back; filled once from wikt.suggest's language list.
var codeToName = null;
var nameToCode = null;
var namesPromise = null;
function loadNames() {
if (!namesPromise) {
namesPromise = suggest.languages().then(function (data) {
codeToName = data;
nameToCode = {};
Object.keys(data).forEach(function (code) {
var name = data[code].toLowerCase();
if (!nameToCode[name]) {
nameToCode[name] = code;
}
});
});
}
return namesPromise;
}
// A language code from a code or a Kurmancî name, or null.
function toCode(txt) {
txt = (txt || "").trim();
if (codeToName[txt]) {
return txt;
}
return nameToCode[txt.toLowerCase()] || null;
}
// The name of a language, or of a family lines are nested under.
function groupName(code) {
return codeToName[code] || new LangMetadata().getGroupName(code) || code;
}
function ucfirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey(s) {
return Array.from(s.toLowerCase().replace(/['ʼ", -]/g, "")).map(function (ch) {
var i = ALPHABET.indexOf(ch);
if (i !== -1) {
return String.fromCharCode(0x100 + i * 2);
}
i = ALPHABET.indexOf(ch.normalize("NFD").charAt(0));
return i !== -1 ? String.fromCharCode(0x101 + i * 2) : ch;
}).join("");
}
function compareNames(a, b) {
var ka = sortKey(a), kb = sortKey(b);
if (ka !== kb) {
return ka < kb ? -1 : 1;
}
return a < b ? -1 : a > b ? 1 : 0;
}
function sameName(a, b) {
return sortKey(a) === sortKey(b);
}
// The HTML of a rendered line after its "Language:" label.
function afterColon(html) {
var div = document.createElement('div');
div.innerHTML = html;
var walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);
var node;
while ((node = walker.nextNode())) {
var i = node.data.indexOf(':');
if (i !== -1) {
var range = document.createRange();
range.setStart(div, 0);
range.setEnd(node, i + 1);
range.deleteContents();
break;
}
}
return div.innerHTML;
}
var PICKER_SIZE = 12;
// A name without its diacritics, so "ital" finds "îtalî".
function fold(s) {
return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
// folded name -> code, built on first use.
var foldedNames = null;
// Up to PICKER_SIZE codes for what was typed: the code itself, then names starting with it (shortest
// first, so "fran" offers fransî before franc-comtoisî), then codes starting with it, common languages
// first ("cm" offers cmn), then names containing it.
function matchLanguages(term) {
term = term.trim().toLowerCase();
var exact = codeToName[term] ? [term] : [];
if (term.length < 2) {
return exact;
}
if (!foldedNames) {
foldedNames = Object.keys(nameToCode).map(function (name) {
return [fold(name), nameToCode[name]];
});
}
var folded = fold(term);
var meta = new LangMetadata();
var starts = [], contains = [];
foldedNames.forEach(function (pair) {
var i = pair[0].indexOf(folded);
if (pair[1] === term || i === -1) {
return;
}
(i === 0 ? starts : contains).push(pair[1]);
});
var codes = Object.keys(codeToName).filter(function (code) {
return code !== term && code.indexOf(term) === 0 && starts.indexOf(code) === -1;
}).sort(function (a, b) {
return (meta.isCommon(b) - meta.isCommon(a)) || a.length - b.length || (a < b ? -1 : 1);
});
function byName(a, b) {
return compareNames(codeToName[a], codeToName[b]);
}
starts.sort(function (a, b) {
return codeToName[a].length - codeToName[b].length || byName(a, b);
});
contains = contains.filter(function (code) {
return codes.indexOf(code) === -1;
});
return exact.concat(starts, codes, contains.sort(byName)).slice(0, PICKER_SIZE);
}
// Wraps a language input in wikt.suggest's dropdown of matching languages; picking one writes its name, and
// a code typed in is replaced by its name when the field is left. onChange runs whenever the value changes.
function languagePicker(input, onChange) {
var box = newNode('span', { 'class': 'translation-adder-langbox' }, input);
suggest(input, {
items: function (term) {
return matchLanguages(term).map(function (code) {
return { value: codeToName[code], code: code };
});
},
onPick: onChange
});
input.addEventListener('input', onChange);
input.addEventListener('blur', function () {
var value = input.value.trim();
if (codeToName[value]) {
input.value = codeToName[value];
}
onChange();
});
return box;
}
// Puts node at the end of a line's own content: after its last translation and that translation's ✎ ×,
// before the lines nested under it.
function appendToLine(line, node) {
if (/(?:^|\s)translation-adder-wrap(?:\s|$)/.test(line.className) && line.firstElementChild) {
line = line.firstElementChild;
}
var stop = null;
for (var child = line.firstChild; child; child = child.nextSibling) {
if (/^(UL|DL)$/.test(child.nodeName) ||
(child.nodeType === 1 && /(?:^|\s)translation-adder-line-tools(?:\s|$)/.test(child.className))) {
stop = child;
break;
}
}
line.insertBefore(node, stop);
}
function newNode(tagname) {
var node = document.createElement(tagname);
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
if (typeof argument == 'string') {
node.appendChild(document.createTextNode(argument));
} else if (typeof argument == 'object') {
if (argument instanceof Node) {
node.appendChild(argument);
} else {
for (var j in argument) {
if (j === 'class') {
node.className = argument[j];
} else if (j == 'style') {
node.style.cssText = argument[j];
} else if (typeof argument[j] == 'function') {
node.addEventListener(j, argument[j], false);
} else {
node.setAttribute(j, argument[j]);
}
}
}
}
}
return node;
}
// Form settings kept in localStorage.
function Preferences() {
var KEY = "wikt-translation-adder";
var storage = mw.storage.getObject(KEY) || {};
this.get = function (name, def) {
return storage[name] || def;
};
this.set = function (name, value) {
if (value === null || storage[name] === value)
return;
storage[name] = value;
mw.storage.setObject(KEY, storage);
};
}
var util = {
// Index of str (a string or RegExp) in text at or after pos, skipping comments, nowiki and templates
// other than a match itself; -1 if none.
getVanillaIndexOf: function (str, text, pos) {
function find(s) {
if (typeof s === 'string')
return text.indexOf(s, pos);
var i = text.substring(pos).search(s);
return i === -1 ? -1 : i + pos;
}
pos = pos || 0;
do {
var cpos = text.indexOf('<!--', pos),
tpos = text.indexOf('{{', pos),
wpos = text.indexOf('<nowiki>', pos),
spos = find(str);
pos = Math.min(
cpos == -1 ? Infinity : cpos,
tpos == -1 ? Infinity : tpos,
wpos == -1 ? Infinity : wpos,
spos == -1 ? Infinity : spos
);
if (pos == Infinity)
return -1;
if (pos == spos)
return pos;
var close = pos == cpos ? '-->' : pos == wpos ? '</' + 'nowiki>' : '}}'; // split, so this page's own <nowiki> does not end here
var end = text.indexOf(close, pos);
if (end == -1)
return -1;
pos = end + close.length;
} while (true);
},
// The template starting at pos: {name, end (after "}}"), params: [{key, value}]} (key null if positional).
parseTemplate: function (text, pos) {
var depth = 0, parts = [], start = pos + 2;
for (var i = pos; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === '{{' || two === '[[') {
depth++;
i++;
} else if (two === '}}' || two === ']]') {
depth--;
i++;
if (depth === 0) {
parts.push(text.substring(start, i - 1));
break;
}
} else if (text.charAt(i) === '|' && depth === 1) {
parts.push(text.substring(start, i));
start = i + 1;
}
}
return {
name: parts[0],
end: i + 1,
params: parts.slice(1).map(function (p) {
var m = /^\s*([^=\[\]{}|]+?)\s*=([\s\S]*)$/.exec(p);
return m ? { key: m[1], value: m[2] } : { key: null, value: p };
})
};
},
// The gloss (first positional parameter) of the box template starting at pos.
getWikitextGloss: function (text, pos) {
var params = util.parseTemplate(text, pos).params;
for (var i = 0; i < params.length; i++) {
if (params[i].key === null || params[i].key === '1')
return params[i].value.trim();
}
return '';
},
// The box template at pos with gloss as its first positional parameter.
setWikitextGloss: function (text, pos, gloss) {
var t = util.parseTemplate(text, pos);
var done = false;
var params = t.params.map(function (p) {
if (!done && (p.key === null || p.key === '1')) {
done = true;
return p.key === null ? gloss : '1=' + gloss;
}
return p.key === null ? p.value : p.key + '=' + p.value;
});
if (!done)
params.unshift(gloss);
return text.substring(0, pos) + '{{' + t.name + params.map(function (p) { return '|' + p; }).join('') +
'}}' + text.substring(t.end);
},
// Whether the words of the box's gloss (as the page shows it) appear, in order, in the wikitext gloss.
matchGloss: function (wikitextGloss, gloss) {
wikitextGloss = wikitextGloss.replace(/\[\[(?:[^\[\]|]*\|)?([^\[\]]*)\]\]/g, '$1').replace(/'{2,}/g, '');
if (/^\s*$/.test(gloss))
return /^\s*$/.test(wikitextGloss);
var words = gloss.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
var pos = 0;
for (var i = 0; i < words.length; i++) {
pos = wikitextGloss.indexOf(words[i], pos);
if (pos == -1)
return false;
}
return true;
},
// The table.translations of a node inside a box, or of a box heading.
getTable: function (node) {
var frame = $(node).closest('.NavFrame');
var table = $(node).closest('table.translations');
return table.length ? table[0] : frame.find('table.translations')[0];
},
getTransGloss: function (node) {
var table = util.getTable(node);
return table ? table.dataset.gloss || '' : '';
},
// [start, end] of the box's list in text: from the line after the opening template to the closing one.
getTransTable: function (text, node) {
var gloss = util.getTransGloss(node);
var table = util.getTable(node);
var tables = $('table.translations').get();
var all = [], found = [];
var pos = -1;
while ((pos = util.getVanillaIndexOf(TOP, text, pos + 1)) > -1) {
all.push(pos);
if (util.matchGloss(util.getWikitextGloss(text, pos), gloss))
found.push(pos);
}
if (found.length != 1) {
var same = tables.filter(function (t) {
return (t.dataset.gloss || '') === gloss;
});
if (all.length === tables.length) {
// Every box on the page is one in the wikitext: they match by order.
found = [all[tables.indexOf(table)]];
} else if (found.length > 1 && same.length === found.length) {
// Boxes with the same gloss are told apart by their order on the page.
found = [found[same.indexOf(table)]];
}
}
if (found.length == 1 && found[0] !== undefined) {
var start = util.parseTemplate(text, found[0]).end;
start = text.indexOf('\n', start) + 1;
var end = text.substring(start).search(BOTTOM);
if (start > 0 && end > -1)
return [start, start + end, found[0]];
}
console.trace("getTransTable failure; node = %o, gloss = %o", node, gloss);
return false;
},
validateNoWikisyntax: function (field, nonempty) {
return function (txt, error) {
if (/[\{\}#]/.test(txt))
return error("Di qada " + field + " de herfên {}# bi kar neyne.");
if (nonempty && !txt)
return error(ucfirst(field) + " binivîse.");
return txt;
};
},
// Codes from [[Modul:gender and number/data]], in the order they are written.
genderList: {
"n": "nêr", "n-d": "nêr, dual", "n-p": "nêr, pirjimar",
"m": "mê", "m-d": "mê, dual", "m-p": "mê, pirjimar",
"g": "giştî", "g-d": "giştî, dual", "g-p": "giştî, pirjimar",
"nt": "nêtar", "nt-d": "nêtar, dual", "nt-p": "nêtar, pirjimar",
"y": "yekjimar", "d": "dual", "p": "pirjimar",
"impf": "împerfektîv", "pf": "perfektîv"
},
genderRows: [["n", "n-d", "n-p"], ["m", "m-d", "m-p"], ["g", "g-d", "g-p"], ["nt", "nt-d", "nt-p"],
["y", "d", "p"], ["impf", "pf"]]
};
// The language name a line starts with ("Almanî: …").
function getLangName(li) {
var guess = li.textContent || '';
guess = guess.substr(0, guess.indexOf(':'));
return guess.replace(/^\s*/, '');
}
// The language code of a line, from {{Z}}'s span.trad-xx or else its first translation's lang attribute.
// Only the line's own nodes count: a group line written as text ("Sorbî:") has no code of its own.
function getLangCode(li) {
var own = /(?:^|\s)trans-(\S+)/.exec(li.className);
if (own)
return own[1];
var lang = false;
for (var node = li.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (node.nodeType !== 1) {
continue;
}
var spans = [node].concat(Array.prototype.slice.call(node.getElementsByTagName('span')));
for (var i = 0; i < spans.length; i++) {
var m = /(?:^|\s)trad-(\S+)/.exec(spans[i].className);
if (m)
return m[1];
if (!lang && spans[i].lang)
lang = spans[i].lang;
}
}
return lang;
}
// Whether a line is the group line for code: {{Z|code}}, or the group's name written as text.
function isGroupLine(li, code) {
var own = getLangCode(li);
return own ? own === code : sameName(getLangName(li), groupName(code));
}
function TranslationAdders(editor, $root) {
// The ± link on a box heading, which edits its gloss.
function TranslationLabeller(insertDiv) {
var original_span;
var adder_form;
var initial_value;
var edit_button;
var editing = false;
var adderInterface = {
'fields': {
'gloss': function (txt, error) {
return util.validateNoWikisyntax('mane', true)(txt, error);
}
},
'createForm': function () {
var thisId = "a" + String(Math.random()).replace(".", "");
adder_form = newNode('form', {
style: 'display: inline',
width: 'auto',
click: kill_event
},
newNode('label', {
'for': thisId
}, "Mane: "),
newNode('input', {
type: 'text',
name: 'gloss',
value: initial_value,
style: 'width: 50%',
title: 'Maneya ku ev tercime jê re ne, bi kurtî',
id: thisId
}),
newNode('input', {
type: 'submit',
name: 'preview',
value: 'Pêşdîtin'
})
);
return adder_form;
},
'onsubmit': function (values, render) {
render("''" + values.gloss + "''", function (new_html) {
if (editing)
toggle_editing(false);
var old_html = original_span.innerHTML;
editor.addEdit({
'undo': function () {
original_span.innerHTML = old_html;
if (!editing) toggle_editing();
},
'redo': function () {
original_span.innerHTML = new_html;
if (editing) toggle_editing();
},
'edit': function (text) {
return perform_edit(text, values.gloss);
},
'summary': 'mane: "' + (values.gloss.length > 50 ? values.gloss.substr(0, 50) + '…"' : values.gloss + '"')
}, original_span);
});
}
};
function perform_edit(wikitext, gloss) {
var pos = util.getTransTable(wikitext, insertDiv);
if (!pos) {
editor.error("Qutiya tercimeyan nehat dîtin.");
return wikitext;
}
return util.setWikitextGloss(wikitext, pos[2], gloss);
}
// Don't open and close the box when using the form.
function kill_event(e) {
e.stopPropagation();
}
function toggle_editing() {
if (editing) {
adder_form.style.display = "none";
original_span.style.display = "inline";
editing = false;
return;
}
editing = true;
edit_button.text("Tê barkirin…");
editor.withCurrentText(function (currentText) {
var pos = util.getTransTable(currentText, insertDiv);
edit_button.text('±');
if (!pos)
return editor.error("Qutiya tercimeyan nehat dîtin.");
initial_value = util.getWikitextGloss(currentText, pos[2]);
if (!original_span) {
original_span = newNode('span', {
'class': 'wt-edit-recurse'
});
for (var i = 0; i < insertDiv.childNodes.length; i++) {
var child = insertDiv.childNodes[i];
if (child != edit_button[0] && child.className !== 'NavToggle') {
original_span.appendChild(insertDiv.removeChild(child));
i--;
}
}
insertDiv.appendChild(original_span);
new AdderWrapper(editor, adderInterface, insertDiv, original_span);
}
original_span.style.display = "none";
adder_form.style.display = "inline";
adder_form.getElementsByTagName('input')[0].focus();
});
}
edit_button = $('<a href="#" class="translation-adder-gloss">±</a>')
.attr("title", "Maneya qutiyê biguherîne").css("padding", "2px").css("margin-left", "-5px")
.on("click", function (e) {
e.preventDefault();
kill_event(e);
toggle_editing();
return false;
});
$(insertDiv).prepend(edit_button);
}
function TranslationAdder(insertUl) {
var langmetadata = new LangMetadata();
this.fields = {
lang: function (txt, error) {
var code = toCode(txt);
if (!code)
return error("Koda zimanî an navê zimanî binivîse (de, almanî, …).");
if (code == 'ku' || code == 'kmr')
return error("Zimanekî ji bilî kurmancî hilbijêre.");
return code;
},
word: function (txt, error) {
if (txt.indexOf(',') == -1 || forceComma) {
forceComma = false;
if (langmetadata.expectedCase(toCode(thiz.elements.lang.value), mw.config.get('wgTitle'), txt) || forceCase) {
forceCase = false;
return util.validateNoWikisyntax('tercime', true)(txt, error);
}
if (prefs.get('case-knowledge', 'none') == 'none') {
return error(newNode('span',
"Tercime pir caran bi herfa biçûk dest pê dikin. Heke tu bawer î ku bi herfa mezin e, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceCase = true;
inputForm.onsubmit();
prefs.set('case-knowledge', 'guru');
}
}, "ji bo berdewamkirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('case-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke tu ne bawer î ku ev tercime bi herfa mezin e.");
error(msg);
return txt;
}
}
if (prefs.get('comma-knowledge', 'none') == 'none') {
return error(newNode('span',
"Her carê tenê tercimeyekê lê zêde bike. Heke ev tercimeyek e ku virgul tê de heye, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceComma = true;
inputForm.onsubmit();
prefs.set('comma-knowledge', 'guru');
}
}, "ji bo lêzêdekirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('comma-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke te dixwest çend tercimeyan bi hev re lê zêde bikî: ev nabe.");
error(msg);
return txt;
}
},
qual: util.validateNoWikisyntax('not'),
tr: util.validateNoWikisyntax('transliterasyon'),
lit: util.validateNoWikisyntax('maneya herfî'),
rawPageName: util.validateNoWikisyntax('navê rûpelê'),
nested: function (txt, error) {
if (!txt)
return '';
var group = toCode(txt) || (langmetadata.getGroupName(txt.trim()) ? txt.trim() : null);
return group || error("Ji bo \"di bin zimanê\" koda zimanekî an malbatekê binivîse.");
},
nclass1: util.validateNoWikisyntax('sinifa navdêrê'),
nclass2: util.validateNoWikisyntax('sinifa pirjimariyê')
};
var fields = this.fields;
Object.keys(util.genderList).forEach(function (g) {
fields[g] = 'checkbox';
});
this.createForm = function () {
function createGenderNode(name, text) {
var inp = document.createElement("input");
inp.type = "checkbox";
inp.name = name;
var lbl = document.createElement("label");
lbl.appendChild(inp);
lbl.appendChild(document.createTextNode(text));
return lbl;
}
var genderControls = {};
for (var g in util.genderList) genderControls[g] = createGenderNode(g, util.genderList[g]);
var langName = newNode('span', { 'class': 'translation-adder-langname' });
var controls = {
lang: newNode('input', {
size: 12,
type: 'text',
name: 'lang',
value: prefs.get('curlang', ''),
placeholder: 'ziman',
title: 'Koda zimanî (de, fr, …) an navê wî (almanî, fransî, …)'
}),
transliteration: newNode('p', "Transliterasyon: ",
newNode('input', {
name: "tr",
title: "Tercime bi herfên latînî."
}), " (nimûne: zìmǔ ji bo 字母)"),
literal: newNode('p', "Maneya herfî: ",
newNode('input', {
name: "lit",
title: "Maneya kelîme bi kelîme ya tercimeyê."
})),
qualifier: newNode('p', "Not: ", newNode('input', {
name: 'qual',
title: "Notek kurt li ser tercimeyê"
}), " (nimûne: kevn, devkî)"),
display: newNode('p', "Navê rûpelê: ", newNode('input', {
name: 'rawPageName',
title: "Heke tercime ne forma bingehîn be, navê rûpela ku jê re lînk bê dayîn."
}), " (nimûne: 疲れる ji bo 疲れた)"),
nested: newNode('p', "Di bin zimanê: ", newNode('input', {
name: 'nested',
title: "Zimanê ku ev ziman di bin wî de tê rêzkirin"
}), " (nimûne: zh ji bo mandarînî)"),
genders: genderControls,
nclass: newNode('p',
"Sinifa navdêrê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass1',
title: "Sinifa navdêrê di forma yekjimar de."
}),
" Sinifa pirjimariyê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass2',
title: "Sinifa navdêrê di forma pirjimar de, heke hebe."
}))
};
var $gender = $("<p>");
util.genderRows.forEach(function (row) {
var $div = $("<div>");
row.forEach(function (code) { $div.append(controls.genders[code]); });
$gender.append($div);
});
controls.gender = $gender[0];
langInput = controls.lang;
var showButton = newNode('span', {
'click': function () {
advancedMode = !advancedMode;
showButton.textContent = advancedMode ? " Kêmtir" : " Zêdetir";
updateFieldVisibility.call(langInput, true);
},
'style': "color: var(--wikt-palette-blue,#0000FF); cursor: pointer;"
}, advancedMode ? " Kêmtir" : " Zêdetir");
function autoTransliterate() {
var rawPageName = langmetadata.computeRawPageName(toCode(thiz.elements.lang.value), thiz.elements.word.value);
if (rawPageName && rawPageName !== thiz.elements.word.value)
thiz.elements.rawPageName.value = rawPageName;
}
function updateFieldVisibility(preserve) {
preserve = (preserve === true);
function show() {
for (var i = 0; i < arguments.length; i++) {
arguments[i].style.display = arguments[i].nodeName == 'P' ? "block" : "inline";
}
}
function hide() {
for (var i = 0; i < arguments.length; i++)
arguments[i].style.display = "none";
}
// Shows the remaining arguments if the first is true, else hides them.
function toggle(condition) {
var nodes = [].slice.call(arguments, 1);
(condition ? show : hide).apply(this, nodes);
}
var lang = toCode(langInput.value) || '';
// A code in the field gets its name beside it, until leaving the field puts the name in.
var typed = langInput.value.trim();
langName.textContent = codeToName[typed] ? " (" + codeToName[typed] + ")" : "";
if (!preserve) {
autoTransliterate();
// Mandarînî goes under Çînî and so on, unless "Di bin zimanê" is changed.
thiz.elements.nested.value = lang ? langmetadata.getNested(lang) : '';
}
var scriptGuess = langmetadata.guessScript(lang);
if (!advancedMode) {
var g = lang ? langmetadata.getGenders(lang) : undefined;
if (g === undefined) {
hide(controls.gender);
} else {
for (var genderName in util.genderList) {
toggle(g.indexOf(genderName) != -1, controls.genders[genderName]);
}
}
toggle(g, controls.gender);
toggle(langmetadata.hasNounClasses(lang), controls.nclass);
var hasAutomaticTransliteration = new window.LanguageUtils().HasAutomaticTransliteration(lang);
toggle(scriptGuess && scriptGuess != 'Latn' && !hasAutomaticTransliteration, controls.transliteration);
toggle(langmetadata.needsRawPageName(lang), controls.display);
hide(controls.literal, controls.qualifier, controls.nested);
} else {
show.apply(this, [controls.gender].concat(
Object.keys(util.genderList).map(function (k) { return controls.genders[k]; }),
[controls.nclass, controls.transliteration, controls.literal, controls.qualifier, controls.display,
controls.nested]));
}
}
var langBox = languagePicker(langInput, function () {
updateFieldVisibility.call(langInput);
});
window.setTimeout(function () {
updateFieldVisibility.call(langInput);
}, 0);
inputForm = newNode('form',
newNode('p', "Tercimeyekê lê zêde bike: ",
langBox, langName, newNode('b', ': '), newNode('input', {
'name': 'word',
placeholder: 'tercime',
title: 'Tercime bi wî zimanî',
size: 20,
keyup: autoTransliterate,
change: autoTransliterate
}),
newNode('input', {
'type': 'submit',
'value': 'Pêşdîtin'
}), showButton
),
controls.gender,
controls.nclass,
controls.transliteration,
controls.literal,
controls.display,
controls.qualifier,
controls.nested
);
return inputForm;
};
this.onsubmit = function (values, render) {
var wikitext;
langmetadata.retrieveRawPageName(values.lang, values.word, values.rawPageName, function (rawPageName, needsRawPageName) {
if (needsRawPageName) {
values.rawPageName = rawPageName;
values.wordMinus = '';
} else {
values.rawPageName = '';
values.wordMinus = rawPageName;
}
langmetadata.hasWiktionaryWithEntry(values.lang, values.rawPageName || values.wordMinus, function (hasInterwiki) {
var templateBody = '|' + values.lang + '|' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) +
(values.nclass1 ? '|c' + values.nclass1 : '') +
(values.nclass2 ? '|c' + values.nclass2 : '');
templateBody += Object.keys(util.genderList).map(function (k) {
return values[k] ? '|' + k : '';
}).join("");
templateBody +=
(values.tr ? '|tr=' + values.tr : '') +
(values.lit ? '|lit=' + values.lit : '') +
(values.rawPageName ? '|cuda=' + values.word : '');
var qualifier = values.qual ? ' {{i|' + values.qual + '}}' : '';
wikitext = '{{Z|' + values.lang + '}}: {{' + (hasInterwiki ? "W+" : "W") + templateBody + "}}" + qualifier;
render(wikitext, function (html) {
registerEdits(values, wikitext, html);
});
});
});
};
var thiz = this;
var prefs = new Preferences();
var langInput;
var inputForm;
var advancedMode = prefs.get('more-display', 'none') != 'none';
var forceComma = false;
var forceCase = false;
function resetElements() {
prefs.set('more-display', advancedMode ? 'block' : 'none');
['word', 'nclass1', 'nclass2', 'lit', 'tr', 'rawPageName', 'qual'].forEach(function (f) {
thiz.elements[f].value = '';
});
Object.keys(util.genderList).forEach(function (g) {
thiz.elements[g].checked = false;
});
prefs.set('curlang', thiz.elements.lang.value);
}
// Runs after the new line has been rendered: finds where it goes and registers the edit.
function registerEdits(values, wikitext, content) {
var li = newNode('li', {
'class': 'trans-' + values.lang
});
li.innerHTML = content;
var lang = getLangName(li);
var summary = '+' + codeToName[values.lang] + ': [[' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) + ']]';
var insertBefore = null;
var nextLanguage = null;
var nestedCode = values.nested;
// A top-level line for the language already there takes the translation, and then moves under its group.
var table = util.getTable(insertUl);
var nestAfter = nestedCode && misplacedLines(table, values.lang).length ? values.lang : null;
if (nestAfter) {
nestedCode = '';
}
var nestedHeading = nestedCode ? ucfirst(groupName(nestedCode)) : '';
var nestedWikitext = "\n* {{Z|" + nestedCode + "}}:\n*: " + wikitext;
function addEdit(edit, span) {
var fields = ["lang", "word", "nclass1", "nclass2", "rawPageName", "qual", "tr", "lit"];
editor.addEdit({
'undo': function () {
edit.undo();
if (fields.slice(1).every(function (f) { return thiz.elements[f].value === ""; })) {
for (var i = 0; i < fields.length; i++) {
thiz.elements[fields[i]].value = values[fields[i]];
}
for (var genderName in util.genderList) {
thiz.elements[genderName].checked = values[genderName];
}
}
},
'redo': function () {
edit.redo();
// Editing mode, so the new translation has its ✎ and × at once.
setLineEditing(table, editor, true);
for (var i = 0; i < fields.length; i++) {
if (thiz.elements[fields[i]].value != values[fields[i]])
return;
}
resetElements();
},
'edit': edit.edit,
'summary': summary
}, span);
if (nestAfter) {
editor.addEdit(nestEdit(table, nestAfter), null);
}
}
if (lang && !nestedCode) {
// Every line in this box.
var lis = $(insertUl).parent().parent().find('li').get()
.concat($(insertUl).parent().parent().find('dd').get());
for (var j = 0; j < lis.length; j++) {
if (lis[j].getElementsByTagName('form').length > 0)
continue;
var ln = getLangName(lis[j]);
if (ln && sameName(ln, lang)) {
var span = newNode('span', { 'class': 'translation-adder-added' });
var parent = lis[j];
if (parent.getElementsByTagName('ul').length + parent.getElementsByTagName('dl').length == 0) {
span.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(parent, span);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to the language's existing line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
var node = parent.firstChild;
var hastrans = false;
while (node) {
if (node.nodeType == 1) {
var nn = node.nodeName;
if (nn == 'UL' || nn == 'DL') {
span.innerHTML = (hastrans ? ", " : " ") + afterColon(content);
addEdit({
'redo': function () {
parent.insertBefore(span, node);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to a language that has nested lines under it.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + (hastrans ? "," : "") + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
hastrans = true;
}
}
node = node.nextSibling;
}
}
} else if (ln && compareNames(ln, lang) > 0 && (!nextLanguage || compareNames(ln, nextLanguage) < 0) && lis[j].parentNode.parentNode.nodeName != 'LI') {
nextLanguage = ln;
var before = lis[j];
insertBefore = [{
'redo': function () {
before.parentNode.insertBefore(li, before);
},
'undo': function () {
before.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, ln, getLangCode(before), function (text, ipos) {
// A new line before the next language's line.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend);
})
}, li];
}
}
}
if (nestedCode) {
nextLanguage = null;
insertBefore = null;
li.innerHTML = nestedHeading + ":" + "<dl><dd class=\"trans-" + values.lang + "\">" + content + "</dd></dl>";
var tops = insertUl.parentNode.parentNode.getElementsByTagName('li');
for (var k = 0; k < tops.length; k++) {
if (tops[k].getElementsByTagName('form').length > 0)
continue;
// Only top-level lines.
if (tops[k].parentNode.parentNode.nodeName != 'TD')
continue;
var tln = getLangName(tops[k]);
if (tln && sameName(tln, nestedHeading)) {
var group = tops[k];
var sublis = group.getElementsByTagName('li');
if (!sublis.length)
sublis = group.getElementsByTagName('dd');
if (sublis.length == 0) {
var dd = newNode('dd', {
'class': 'trans-' + values.lang
});
var dl = newNode('dl', dd);
dd.innerHTML = content;
addEdit({
'redo': function () {
group.appendChild(dl);
},
'undo': function () {
group.removeChild(dl);
},
'edit': getEditFunction(values, wikitext, nestedHeading, nestedCode, function (text, ipos) {
// The first nested line under the group's line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "\n*: " + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
}
var item = newNode(sublis[0].nodeName, {
'class': 'trans-' + values.lang
});
var linestart = item.nodeName == 'DD' ? '\n*: ' : '\n** ';
item.innerHTML = content;
for (var m = 0; m < sublis.length; m++) {
var subln = getLangName(sublis[m]);
var sub = sublis[m];
if (subln && sameName(subln, lang)) {
var addSpan = newNode('span', { 'class': 'translation-adder-added' });
addSpan.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(sub, addSpan);
},
'undo': function () {
sub.removeChild(addSpan);
},
'edit': getEditFunction(values, wikitext, lang, values.lang, function (text, ipos) {
// Adding to an existing nested line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, addSpan);
return resetElements();
} else if (subln && langmetadata.nestsBefore(lang, subln)) {
addEdit({
'redo': function () {
sub.parentNode.insertBefore(item, sub);
},
'undo': function () {
sub.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, subln, getLangCode(sub), function (text, ipos) {
// A nested line in order.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
}
}
var last = sublis[sublis.length - 1];
addEdit({
'redo': function () {
last.parentNode.appendChild(item);
},
'undo': function () {
last.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, getLangName(last), getLangCode(last), function (text, ipos) {
// A nested line at the end of its group.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
} else if (tln && compareNames(tln, nestedHeading) > 0 && (!nextLanguage || compareNames(tln, nextLanguage) < 0)) {
nextLanguage = tln;
var next = tops[k];
insertBefore = [{
'redo': function () {
next.parentNode.insertBefore(li, next);
},
'undo': function () {
next.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, tln, getLangCode(next), function (text, ipos) {
// A new group line with the nested line under it.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + nestedWikitext + text.substr(lineend);
})
}, li];
}
}
wikitext = "{{Z|" + nestedCode + "}}:\n*: " + wikitext;
}
li.className = "trans-" + values.lang;
if (insertBefore) {
addEdit(insertBefore[0], insertBefore[1]);
} else {
// After the last line.
addEdit({
'redo': function () {
insertUl.appendChild(li);
},
'undo': function () {
insertUl.removeChild(li);
},
'edit': getEditFunction(values, wikitext)
}, li);
}
return resetElements();
}
// The wikitext change for this submission. With findLanguage, callback gets the position of that
// language's line; without, the line goes at the end of the box.
function getEditFunction(values, wikitext, findLanguage, findLangCode, callback) {
return function (text) {
var p = util.getTransTable(text, insertUl);
if (!p)
return editor.error("Qutiya tercimeyan ji bo '" + values.lang + ":" + values.word +
"' nehat dîtin. Maneyên qutiyan divê ji hev cuda bin.");
var stapos = p[0];
var endpos = p[1];
if (findLanguage) {
var box = text.substring(stapos, endpos);
var sub = values.nested ? "[:*]?" : "";
var escapedName = mw.util.escapeRegExp(findLanguage);
var patterns = [
"\\*" + sub + " ?\\[\\[" + escapedName + "\\]\\]:",
"\\*" + sub + " ?" + escapedName + ":"
];
if (findLangCode)
patterns.unshift("\\*" + sub + " ?\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(findLangCode) + "\\s*(?:\\|[^{}]*)?\\}\\}\\s*:");
var ipos = -1;
for (var i = 0; i < patterns.length && ipos == -1; i++) {
ipos = box.search(new RegExp(patterns[i], "i"));
}
if (ipos > -1) {
return callback(text, ipos + stapos);
}
return editor.error("Rêza zimanê " + findLanguage + " nehat dîtin. Ji kerema xwe rêzê rast bike.");
}
return text.substr(0, endpos) + "* " + wikitext + "\n" + text.substr(endpos);
};
}
}
function createTranslationAdderRow(li) {
var $ul = $("<ul>").append(li);
return $("<tr>")
.addClass("translation-adder-row")
.append($("<td>").attr("colspan", "3").css("text-align", "left").append($ul));
}
$root.find('table.translations').each(function () {
if (this.dataset.translationAdder) {
return;
}
this.dataset.translationAdder = "1";
var td = $(this).find("td.translations-cell")[0];
if (!td) {
return;
}
var uls = $(td).children("ul").get();
if (uls.length == 0) {
uls = [td.appendChild(newNode('ul'))];
}
var li = document.createElement('li');
$(this).append(createTranslationAdderRow(li));
new AdderWrapper(editor, new TranslationAdder(uls[uls.length - 1]), li);
var head = $(this).closest('.NavFrame').children('.NavHead')[0];
if (head) {
new TranslationLabeller(head);
}
});
}
// HTML for wikitext as this page renders it, without the paragraph around it.
function render(wikitext) {
return new mw.Api().parse(wikitext, {
title: mw.config.get('wgPageName'),
pst: true,
disablelimitreport: true
}).then(function (html) {
var $out = $('<div>').html(html);
var $p = $out.find('p').first();
return ($p.length ? $p : $out.children().first()).html().trim();
});
}
// The wikitext of a translation line: {start, restStart, end, rest}, where rest is what follows
// "Language:"; null when the line is not found.
function findLine(text, line) {
var box = util.getTransTable(text, line);
if (!box) {
return null;
}
var code = getLangCode(line);
var name = getLangName(line);
var label = code ?
"\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(code) + "\\s*(?:\\|[^{}\\n]*)?\\}\\}" :
"(?:\\[\\[" + mw.util.escapeRegExp(name) + "\\]\\]|" + mw.util.escapeRegExp(name) + ")";
var part = text.substring(box[0], box[1]);
var m = new RegExp("^\\*[:*]* ?" + label + "\\s*:[ \\t]*(.*)$", "im").exec(part);
if (!m) {
return null;
}
var start = box[0] + m.index;
var end = start + m[0].length;
return { start: start, restStart: end - m[1].length, end: end, rest: m[1], label: text.substring(start, end - m[1].length) };
}
// A line's own nodes: everything before the lines nested under it, without this gadget's buttons.
function ownNodes(line) {
var nodes = [];
for (var node = line.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (!(node.nodeType === 1 && /(?:^|\s)translation-adder-(?:line-tools|term-edit)(?:\s|$)/.test(node.className))) {
nodes.push(node);
}
}
return nodes;
}
var TERM_CALL = /\{\{\s*W[+-]?(?:\/ceribandin)?\s*\|/g;
// The {{W}} calls in the wikitext after "Language:", in order: [{start, end, text}].
function termCalls(rest) {
var out = [];
var m;
TERM_CALL.lastIndex = 0;
while ((m = TERM_CALL.exec(rest))) {
var end = util.parseTemplate(rest, m.index).end;
out.push({ start: m.index, end: end, text: rest.substring(m.index, end) });
TERM_CALL.lastIndex = end;
}
return out;
}
// rest with its i-th {{W}} call replaced by text, or removed with the comma that joins it to the others.
function replaceTerm(rest, i, text) {
var call = termCalls(rest)[i];
if (!call) {
return null;
}
if (text) {
return rest.substring(0, call.start) + text + rest.substring(call.end);
}
var before = rest.substring(0, call.start);
var after = rest.substring(call.end);
if (/,\s*$/.test(before)) {
before = before.replace(/\s*,\s*$/, '');
} else {
after = after.replace(/^\s*,\s*/, '');
}
return (before + after).trim();
}
// The rendered translations of a line, in order: the element each {{W}} call starts with.
function termNodes(line) {
var nodes = [];
ownNodes(line).forEach(function (n) {
if (n.nodeType === 1 && /(?:^|\s)translation-adder-added(?:\s|$)/.test(n.className)) {
nodes.push.apply(nodes, Array.prototype.slice.call(n.childNodes));
} else {
nodes.push(n);
}
});
// A translation starts with its link's span, or with the "ttbc" span of one marked for checking.
// The second form after a "/" (traditional / simplified Chinese) belongs to the same translation.
var terms = [];
var afterSlash = false;
nodes.forEach(function (n) {
if (n.nodeType === 3) {
if (n.data.trim()) {
afterSlash = n.data.trim() === '/';
}
return;
}
if (n.nodeType !== 1) {
return;
}
// The separator is drawn in a span of its own.
if (!n.lang && n.textContent.trim() === '/') {
afterSlash = true;
return;
}
var isTerm = /(?:^|\s)ttbc(?:\s|$)/.test(n.className) ||
(n.lang && !/(?:^|\s)(?:tr|ts)(?:\s|$)/.test(n.className));
if (isTerm && !(afterSlash && terms.length)) {
terms.push(n);
}
afterSlash = false;
});
return terms;
}
// The template dialog of [[MediaWiki:Gadget-wikt.cite.js]], built from the template's TemplateData.
// Resolves with the edited call, "" when it is removed, or null when cancelled.
function templateDialog(call) {
return mw.loader.using('ext.gadget.wikt.cite').then(function (require) {
return require('ext.gadget.wikt.cite').openTemplateDialog(call);
});
}
// On one translation line: ✎ and × after each translation (its template in a dialog, and removing it).
function LineTools(editor, line) {
var name = getLangName(line);
if (!name) {
return;
}
var code = getLangCode(line);
var title = ucfirst(codeToName[code] || name);
var $tools = $('<span class="translation-adder-line-tools">');
var self = this;
this.$tools = $tools;
this.line = line;
// Whether editing mode is on; set by setLineEditing().
this.editing = true;
var nested = line.querySelector('ul, dl');
if (nested) {
$(nested).before($tools);
} else {
$(line).append($tools);
}
function notFound() {
return editor.error("Rêza " + title + " di wîkîmetnê de nehat dîtin.");
}
// The i-th translation drawn is the i-th {{W}} call only when the line holds nothing else that draws
// one ({{g}}, {{l}}, …); otherwise ✎ and × could reach the wrong one, so they refuse.
function callsMatch(found) {
if (termCalls(found.rest).length === termNodes(line).length) {
return true;
}
editor.error("Tercimeyên rêza " + title + " bi wîkîmetnê re li hev nayên; rêzê bi destan biguherîne.");
return false;
}
// Queues a new text after "Language:", drawn in place from its rendering.
function queueRest(makeRest, summary) {
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var rest = found && makeRest(found.rest);
if (rest === null || rest === undefined) {
return notFound();
}
render(found.label.replace(/^\*[:*]* ?/, '') + ' ' + rest).then(function (html) {
var before = ownNodes(line);
var holder = document.createElement('span');
holder.innerHTML = html;
var after = Array.prototype.slice.call(holder.childNodes);
editor.addEdit({
redo: function () {
before.forEach(function (n) { $(n).detach(); });
after.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
undo: function () {
after.forEach(function (n) { $(n).detach(); });
before.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
edit: function (current) {
var at = findLine(current, line);
var next = at && makeRest(at.rest);
if (next === null || next === undefined) {
return notFound();
}
return current.substring(0, at.restStart) + next + current.substring(at.end);
},
summary: summary
}, line);
});
});
}
// The pencil after each translation, put back whenever the line is redrawn.
this.markTerms = function () {
$(line).children('.translation-adder-term-edit')
.add($(line).children('.translation-adder-added').children('.translation-adder-term-edit'))
.remove();
var terms = termNodes(line);
terms.forEach(function (term, i) {
var $edit = $('<a href="#" class="translation-adder-term-edit translation-adder-term-pencil"></a>')
.attr({ title: 'Vê tercimeyê biguherîne', 'aria-label': 'Vê tercimeyê biguherîne' });
// After everything the translation draws: its interwiki link, gender and transliteration.
var last = term;
while (last.nextSibling && !/^(UL|DL)$/.test(last.nextSibling.nodeName) &&
terms.indexOf(last.nextSibling) === -1 &&
!(last.nextSibling.nodeType === 3 && /[,;]/.test(last.nextSibling.data)) &&
!(last.nextSibling.nodeType === 1 &&
/translation-adder-(?:line-tools|added)/.test(last.nextSibling.className))) {
last = last.nextSibling;
}
var $del = $('<a href="#" class="translation-adder-term-edit translation-adder-term-close"></a>')
.attr({ title: 'Vê tercimeyê rake', 'aria-label': 'Vê tercimeyê rake' });
$(last).after($edit, $del);
$edit.add($del).toggle(self.editing);
// The only translation of a line with nothing under it takes the line with it.
$del.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
if (!found || !termCalls(found.rest)[i]) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
if (termCalls(found.rest).length === 1 && !line.querySelector('ul, dl')) {
removeLine();
} else {
queueRest(function (rest) {
return replaceTerm(rest, i, '');
}, '−' + title);
}
});
});
$edit.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var call = found && termCalls(found.rest)[i];
if (!call) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
templateDialog(call.text).then(function (edited) {
if (edited === null || edited === call.text) {
return;
}
queueRest(function (rest) {
return replaceTerm(rest, i, edited);
}, (edited ? '±' : '−') + title);
});
});
});
});
};
this.markTerms();
function removeLine() {
editor.addEdit({
redo: function () {
line.classList.add('translation-adder-removed');
},
undo: function () {
line.classList.remove('translation-adder-removed');
},
edit: function (current) {
var at = findLine(current, line);
if (!at) {
return notFound();
}
return current.substring(0, at.start) + current.substring(at.end + (current.charAt(at.end) === '\n' ? 1 : 0));
},
summary: '−' + title
}, line);
}
}
// ---- nesting: a language en.wiktionary nests (cmn) goes under its group's line ({{Z|zh}}) ----
// The language code of a wikitext line, from its {{Z}}.
function lineCode(l) {
var m = /^\*[:*]* ?\{\{\s*Z\s*\|\s*([^|}\s]+)/.exec(l);
return m ? m[1] : null;
}
// The language name of a wikitext line, for sorting.
function lineName(l) {
var code = lineCode(l);
if (code) {
return groupName(code);
}
var m = /^\*[:*]* ?(?:\[\[)?([^\]:|]+)/.exec(l);
return m ? m[1].trim() : '';
}
function isChildLine(l) {
return /^\*[:*]/.test(l) && !/^\*[:*][:*]/.test(l);
}
// text with the top-level lines of node's box whose language nests (only that language, if given) moved
// under their group line, which is added in order where there is none. A group that already has a line for
// the language gets its translations added to it.
function nestLines(text, node, only) {
var p = util.getTransTable(text, node);
if (!p) {
return null;
}
var meta = new LangMetadata();
var body = text.substring(p[0], p[1]);
var trimmed = body.replace(/\n+$/, '');
var tail = body.slice(trimmed.length);
var head = [], entries = [];
trimmed.split('\n').forEach(function (l) {
if (/^\*(?![:*])/.test(l)) {
entries.push({ line: l, code: lineCode(l), children: [] });
} else if (entries.length) {
entries[entries.length - 1].children.push(l);
} else {
head.push(l);
}
});
var moved = 0;
entries.slice().forEach(function (e) {
var parent = e.code && meta.getNested(e.code);
if (!parent || (only && e.code !== only)) {
return;
}
entries.splice(entries.indexOf(e), 1);
var group = entries.filter(function (x) {
return x.code ? x.code === parent : sameName(lineName(x.line), groupName(parent));
})[0];
if (!group) {
group = { line: '* {{Z|' + parent + '}}:', code: parent, children: [] };
var at = entries.findIndex(function (x) {
return compareNames(lineName(x.line), groupName(parent)) > 0;
});
entries.splice(at === -1 ? entries.length : at, 0, group);
}
var prefix = group.children.some(function (c) { return /^\*\*/.test(c); }) ? '**' : '*:';
var deeper = e.children.map(function (c) { return prefix + c.slice(1); });
var same = group.children.findIndex(function (c) { return isChildLine(c) && lineCode(c) === e.code; });
if (same !== -1) {
var terms = e.line.replace(/^\*\s*\{\{[^{}]*\}\}\s*:\s*/, '');
group.children[same] = group.children[same].replace(/\s*$/, '') + (terms ? ', ' + terms : '');
[].splice.apply(group.children, [same + 1, 0].concat(deeper));
} else {
var name = codeToName[e.code] || e.code;
var at2 = group.children.findIndex(function (c) {
return isChildLine(c) && compareNames(lineName(c), name) > 0;
});
[].splice.apply(group.children, [at2 === -1 ? group.children.length : at2, 0]
.concat([prefix + ' ' + e.line.replace(/^\*\s*/, '')], deeper));
}
moved++;
});
if (!moved) {
return text;
}
var out = head.slice();
entries.forEach(function (x) {
out.push(x.line);
out.push.apply(out, x.children);
});
return text.substring(0, p[0]) + out.join('\n') + tail + text.substring(p[1]);
}
// The top-level lines of a box.
function topLines(table) {
return $(table).find('td.translations-cell').first().children('ul').children('li').get();
}
// The top-level lines whose language nests: [{li, code, parent}].
function misplacedLines(table, only) {
var meta = new LangMetadata();
return topLines(table).map(function (li) {
var code = getLangCode(li);
var parent = code && meta.getNested(code);
return parent && (!only || code === only) ? { li: li, code: code, parent: parent } : null;
}).filter(Boolean);
}
// Draws nestLines() on the page: the lines move under their group, highlighted. Returns the undo.
function nestDom(table, only) {
var undos = [];
misplacedLines(table, only).forEach(function (m) {
var group = topLines(table).filter(function (li) { return isGroupLine(li, m.parent); })[0];
if (!group) {
var made = newNode('li', { 'class': 'trans-' + m.parent + ' translation-adder-new' },
ucfirst(groupName(m.parent)) + ':');
var next = topLines(table).filter(function (li) {
return compareNames(getLangName(li), getLangName(made)) > 0;
})[0];
m.li.parentNode.insertBefore(made, next || null);
group = made;
undos.push(function () { $(made).remove(); });
}
var list = $(group).children('dl, ul')[0];
if (!list) {
var newList = list = group.appendChild(newNode('dl'));
undos.push(function () { $(newList).remove(); });
}
var home = m.li.parentNode, after = m.li.nextSibling;
var item = $(list).children().filter(function () { return getLangCode(this) === m.code; })[0];
if (item) {
var span = newNode('span', { 'class': 'translation-adder-new' });
var holder = document.createElement('div');
ownNodes(m.li).forEach(function (n) { holder.appendChild(n.cloneNode(true)); });
span.innerHTML = ', ' + afterColon(holder.innerHTML);
var sub = $(item).children('dl, ul')[0];
item.insertBefore(span, sub || null);
m.li.style.display = 'none';
undos.push(function () { $(span).remove(); m.li.style.display = ''; });
} else {
var before = $(list).children().filter(function () {
return compareNames(getLangName(this), getLangName(m.li)) > 0;
})[0];
// In a "*:" list the line is drawn as a <dd>, as the saved page will have it.
var wrap = list.nodeName === 'DL' ? newNode('dd', { 'class': 'translation-adder-wrap' }) : null;
if (wrap) {
wrap.appendChild(m.li);
list.insertBefore(wrap, before || null);
} else {
list.insertBefore(m.li, before || null);
}
m.li.classList.add('translation-adder-new');
undos.push(function () {
m.li.classList.remove('translation-adder-new');
home.insertBefore(m.li, after);
$(wrap).remove();
});
}
});
return function () {
undos.reverse().forEach(function (f) { f(); });
};
}
// The change that nests the lines of table's box (only one language's, if given).
function nestEdit(table, only) {
var undo = null;
return {
redo: function () {
undo = nestDom(table, only);
},
undo: function () {
if (undo) {
undo();
undo = null;
}
},
edit: function (text) {
return nestLines(text, table, only);
},
summary: 'tercime di bin zimanên xwe de'
};
}
// Shows (on) or hides the ✎ and × on every translation line of the boxes in root, and on a box with
// lines that belong under another language a link that moves them there.
function setLineEditing(root, editor, on) {
return loadNames().then(function () {
editor = editor || new Editor();
// root may be a box itself.
var $tables = $(root).find('table.translations').addBack('table.translations');
$tables.each(function () {
var table = this;
var $row = $(table).data('translationAdderNest');
if (!$row && on) {
var count = misplacedLines(table).length;
if (!count) {
return;
}
var $nest = $('<a href="#">').text(count === 1 ?
'Rêzekê di bin zimanê wê de bi cih bike' : count + ' rêzan di bin zimanên wan de bi cih bike');
$row = $('<tr class="translation-adder-nest-row">').append($('<td colspan="3">').append('↳ ', $nest));
$(table).find('td.translations-cell').first().parent().before($row);
$(table).data('translationAdderNest', $row);
$nest.on('click', function (e) {
e.preventDefault();
editor.addEdit(nestEdit(table), null);
$row.remove();
});
}
if ($row) {
$row.toggle(on);
}
});
$tables.find('td.translations-cell').find('li, dd').each(function () {
var tools = $(this).data('translationAdderTools');
if (!tools && on) {
tools = new LineTools(editor, this);
$(this).data('translationAdderTools', tools);
}
if (tools && tools.$tools) {
tools.editing = on;
tools.$tools.toggle(on);
if (on) {
tools.markTerms();
}
$(this).find('.translation-adder-term-edit').filter(function () {
return $(this).closest('li, dd')[0] === tools.line;
}).toggle(on);
}
});
});
}
// Adds the forms to the boxes in root (an element or jQuery object). editor collects the changes: by
// default this gadget's own Editor; DefSideBoxes passes one that queues them in its save box.
function attach(root, editor) {
return loadNames().then(function () {
TranslationAdders(editor || new Editor(), $(root));
});
}
window.wikt = window.wikt || {};
window.wikt.translationAdder = {
attach: attach,
setLineEditing: setLineEditing,
compareNames: compareNames
};
module.exports = window.wikt.translationAdder;
// </nowiki>
frk1jsujuomf7ggyacwfnj1vecixu6n
6865648
6865578
2026-10-24T08:46:08Z
Wikihez
7344
Imperfective gender box writes împf, as the shared table does
6865648
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.translation-adder.js]]
*
* Adds a form under each translation box ({{werger-ser}} … {{werger-bin}}) to add a line such as
* * {{Z|de}}: {{W+|de|Wasser|n}}
* in Kurmancî alphabetical order, and a ± link on the box heading to change its gloss. Forked from
* en.wiktionary's [[en:MediaWiki:Gadget-TranslationAdder.js]] (by [[en:User:Conrad.Irwin]] and others).
*
* DefSideBoxes ([[MediaWiki:Gadget-wikt.dsb.js]]) loads it from the pencil on a Werger heading and calls
* attach(root, editor) with an editor from wikt.dsb-panels, so the changes join its save queue. Without
* an editor, edits pile up in this gadget's own box at the top left.
*
* The form reads only boxes from [[Modul:translations]] (table.translations with data-gloss); the older
* div.translations boxes are left to the "translation_editor" gadget.
*/
/* global mw, $, AdderWrapper, LangMetadata, Editor */
require("./wikt.translation-adder/editor.js");
require("./wikt.translation-adder/language-utils.js");
require("./wikt.translation-adder/data.js");
var suggest = require("ext.gadget.wikt.suggest");
var ALPHABET = "abcçdeêfghiîjklmnopqrsştuûvwxyz";
// Box templates in wikitext; the /ceribandin copies are the test versions.
var TOP = /\{\{\s*[Ww]erger-(?:ser|bnr-h)(?:\/ceribandin)?\s*(?=[|}])/;
var BOTTOM = /\{\{\s*[Ww]erger-bin(?:\/ceribandin)?\s*\}\}/;
// code -> Kurmancî name, and back; filled once from wikt.suggest's language list.
var codeToName = null;
var nameToCode = null;
var namesPromise = null;
function loadNames() {
if (!namesPromise) {
namesPromise = suggest.languages().then(function (data) {
codeToName = data;
nameToCode = {};
Object.keys(data).forEach(function (code) {
var name = data[code].toLowerCase();
if (!nameToCode[name]) {
nameToCode[name] = code;
}
});
});
}
return namesPromise;
}
// A language code from a code or a Kurmancî name, or null.
function toCode(txt) {
txt = (txt || "").trim();
if (codeToName[txt]) {
return txt;
}
return nameToCode[txt.toLowerCase()] || null;
}
// The name of a language, or of a family lines are nested under.
function groupName(code) {
return codeToName[code] || new LangMetadata().getGroupName(code) || code;
}
function ucfirst(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
// Sort key in Kurmancî alphabetical order; a letter outside the alphabet sorts after its base letter.
function sortKey(s) {
return Array.from(s.toLowerCase().replace(/['ʼ", -]/g, "")).map(function (ch) {
var i = ALPHABET.indexOf(ch);
if (i !== -1) {
return String.fromCharCode(0x100 + i * 2);
}
i = ALPHABET.indexOf(ch.normalize("NFD").charAt(0));
return i !== -1 ? String.fromCharCode(0x101 + i * 2) : ch;
}).join("");
}
function compareNames(a, b) {
var ka = sortKey(a), kb = sortKey(b);
if (ka !== kb) {
return ka < kb ? -1 : 1;
}
return a < b ? -1 : a > b ? 1 : 0;
}
function sameName(a, b) {
return sortKey(a) === sortKey(b);
}
// The HTML of a rendered line after its "Language:" label.
function afterColon(html) {
var div = document.createElement('div');
div.innerHTML = html;
var walker = document.createTreeWalker(div, NodeFilter.SHOW_TEXT);
var node;
while ((node = walker.nextNode())) {
var i = node.data.indexOf(':');
if (i !== -1) {
var range = document.createRange();
range.setStart(div, 0);
range.setEnd(node, i + 1);
range.deleteContents();
break;
}
}
return div.innerHTML;
}
var PICKER_SIZE = 12;
// A name without its diacritics, so "ital" finds "îtalî".
function fold(s) {
return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
// folded name -> code, built on first use.
var foldedNames = null;
// Up to PICKER_SIZE codes for what was typed: the code itself, then names starting with it (shortest
// first, so "fran" offers fransî before franc-comtoisî), then codes starting with it, common languages
// first ("cm" offers cmn), then names containing it.
function matchLanguages(term) {
term = term.trim().toLowerCase();
var exact = codeToName[term] ? [term] : [];
if (term.length < 2) {
return exact;
}
if (!foldedNames) {
foldedNames = Object.keys(nameToCode).map(function (name) {
return [fold(name), nameToCode[name]];
});
}
var folded = fold(term);
var meta = new LangMetadata();
var starts = [], contains = [];
foldedNames.forEach(function (pair) {
var i = pair[0].indexOf(folded);
if (pair[1] === term || i === -1) {
return;
}
(i === 0 ? starts : contains).push(pair[1]);
});
var codes = Object.keys(codeToName).filter(function (code) {
return code !== term && code.indexOf(term) === 0 && starts.indexOf(code) === -1;
}).sort(function (a, b) {
return (meta.isCommon(b) - meta.isCommon(a)) || a.length - b.length || (a < b ? -1 : 1);
});
function byName(a, b) {
return compareNames(codeToName[a], codeToName[b]);
}
starts.sort(function (a, b) {
return codeToName[a].length - codeToName[b].length || byName(a, b);
});
contains = contains.filter(function (code) {
return codes.indexOf(code) === -1;
});
return exact.concat(starts, codes, contains.sort(byName)).slice(0, PICKER_SIZE);
}
// Wraps a language input in wikt.suggest's dropdown of matching languages; picking one writes its name, and
// a code typed in is replaced by its name when the field is left. onChange runs whenever the value changes.
function languagePicker(input, onChange) {
var box = newNode('span', { 'class': 'translation-adder-langbox' }, input);
suggest(input, {
items: function (term) {
return matchLanguages(term).map(function (code) {
return { value: codeToName[code], code: code };
});
},
onPick: onChange
});
input.addEventListener('input', onChange);
input.addEventListener('blur', function () {
var value = input.value.trim();
if (codeToName[value]) {
input.value = codeToName[value];
}
onChange();
});
return box;
}
// Puts node at the end of a line's own content: after its last translation and that translation's ✎ ×,
// before the lines nested under it.
function appendToLine(line, node) {
if (/(?:^|\s)translation-adder-wrap(?:\s|$)/.test(line.className) && line.firstElementChild) {
line = line.firstElementChild;
}
var stop = null;
for (var child = line.firstChild; child; child = child.nextSibling) {
if (/^(UL|DL)$/.test(child.nodeName) ||
(child.nodeType === 1 && /(?:^|\s)translation-adder-line-tools(?:\s|$)/.test(child.className))) {
stop = child;
break;
}
}
line.insertBefore(node, stop);
}
function newNode(tagname) {
var node = document.createElement(tagname);
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
if (typeof argument == 'string') {
node.appendChild(document.createTextNode(argument));
} else if (typeof argument == 'object') {
if (argument instanceof Node) {
node.appendChild(argument);
} else {
for (var j in argument) {
if (j === 'class') {
node.className = argument[j];
} else if (j == 'style') {
node.style.cssText = argument[j];
} else if (typeof argument[j] == 'function') {
node.addEventListener(j, argument[j], false);
} else {
node.setAttribute(j, argument[j]);
}
}
}
}
}
return node;
}
// Form settings kept in localStorage.
function Preferences() {
var KEY = "wikt-translation-adder";
var storage = mw.storage.getObject(KEY) || {};
this.get = function (name, def) {
return storage[name] || def;
};
this.set = function (name, value) {
if (value === null || storage[name] === value)
return;
storage[name] = value;
mw.storage.setObject(KEY, storage);
};
}
var util = {
// Index of str (a string or RegExp) in text at or after pos, skipping comments, nowiki and templates
// other than a match itself; -1 if none.
getVanillaIndexOf: function (str, text, pos) {
function find(s) {
if (typeof s === 'string')
return text.indexOf(s, pos);
var i = text.substring(pos).search(s);
return i === -1 ? -1 : i + pos;
}
pos = pos || 0;
do {
var cpos = text.indexOf('<!--', pos),
tpos = text.indexOf('{{', pos),
wpos = text.indexOf('<nowiki>', pos),
spos = find(str);
pos = Math.min(
cpos == -1 ? Infinity : cpos,
tpos == -1 ? Infinity : tpos,
wpos == -1 ? Infinity : wpos,
spos == -1 ? Infinity : spos
);
if (pos == Infinity)
return -1;
if (pos == spos)
return pos;
var close = pos == cpos ? '-->' : pos == wpos ? '</' + 'nowiki>' : '}}'; // split, so this page's own <nowiki> does not end here
var end = text.indexOf(close, pos);
if (end == -1)
return -1;
pos = end + close.length;
} while (true);
},
// The template starting at pos: {name, end (after "}}"), params: [{key, value}]} (key null if positional).
parseTemplate: function (text, pos) {
var depth = 0, parts = [], start = pos + 2;
for (var i = pos; i < text.length; i++) {
var two = text.substr(i, 2);
if (two === '{{' || two === '[[') {
depth++;
i++;
} else if (two === '}}' || two === ']]') {
depth--;
i++;
if (depth === 0) {
parts.push(text.substring(start, i - 1));
break;
}
} else if (text.charAt(i) === '|' && depth === 1) {
parts.push(text.substring(start, i));
start = i + 1;
}
}
return {
name: parts[0],
end: i + 1,
params: parts.slice(1).map(function (p) {
var m = /^\s*([^=\[\]{}|]+?)\s*=([\s\S]*)$/.exec(p);
return m ? { key: m[1], value: m[2] } : { key: null, value: p };
})
};
},
// The gloss (first positional parameter) of the box template starting at pos.
getWikitextGloss: function (text, pos) {
var params = util.parseTemplate(text, pos).params;
for (var i = 0; i < params.length; i++) {
if (params[i].key === null || params[i].key === '1')
return params[i].value.trim();
}
return '';
},
// The box template at pos with gloss as its first positional parameter.
setWikitextGloss: function (text, pos, gloss) {
var t = util.parseTemplate(text, pos);
var done = false;
var params = t.params.map(function (p) {
if (!done && (p.key === null || p.key === '1')) {
done = true;
return p.key === null ? gloss : '1=' + gloss;
}
return p.key === null ? p.value : p.key + '=' + p.value;
});
if (!done)
params.unshift(gloss);
return text.substring(0, pos) + '{{' + t.name + params.map(function (p) { return '|' + p; }).join('') +
'}}' + text.substring(t.end);
},
// Whether the words of the box's gloss (as the page shows it) appear, in order, in the wikitext gloss.
matchGloss: function (wikitextGloss, gloss) {
wikitextGloss = wikitextGloss.replace(/\[\[(?:[^\[\]|]*\|)?([^\[\]]*)\]\]/g, '$1').replace(/'{2,}/g, '');
if (/^\s*$/.test(gloss))
return /^\s*$/.test(wikitextGloss);
var words = gloss.split(/[^\p{L}\p{N}]+/u).filter(Boolean);
var pos = 0;
for (var i = 0; i < words.length; i++) {
pos = wikitextGloss.indexOf(words[i], pos);
if (pos == -1)
return false;
}
return true;
},
// The table.translations of a node inside a box, or of a box heading.
getTable: function (node) {
var frame = $(node).closest('.NavFrame');
var table = $(node).closest('table.translations');
return table.length ? table[0] : frame.find('table.translations')[0];
},
getTransGloss: function (node) {
var table = util.getTable(node);
return table ? table.dataset.gloss || '' : '';
},
// [start, end] of the box's list in text: from the line after the opening template to the closing one.
getTransTable: function (text, node) {
var gloss = util.getTransGloss(node);
var table = util.getTable(node);
var tables = $('table.translations').get();
var all = [], found = [];
var pos = -1;
while ((pos = util.getVanillaIndexOf(TOP, text, pos + 1)) > -1) {
all.push(pos);
if (util.matchGloss(util.getWikitextGloss(text, pos), gloss))
found.push(pos);
}
if (found.length != 1) {
var same = tables.filter(function (t) {
return (t.dataset.gloss || '') === gloss;
});
if (all.length === tables.length) {
// Every box on the page is one in the wikitext: they match by order.
found = [all[tables.indexOf(table)]];
} else if (found.length > 1 && same.length === found.length) {
// Boxes with the same gloss are told apart by their order on the page.
found = [found[same.indexOf(table)]];
}
}
if (found.length == 1 && found[0] !== undefined) {
var start = util.parseTemplate(text, found[0]).end;
start = text.indexOf('\n', start) + 1;
var end = text.substring(start).search(BOTTOM);
if (start > 0 && end > -1)
return [start, start + end, found[0]];
}
console.trace("getTransTable failure; node = %o, gloss = %o", node, gloss);
return false;
},
validateNoWikisyntax: function (field, nonempty) {
return function (txt, error) {
if (/[\{\}#]/.test(txt))
return error("Di qada " + field + " de herfên {}# bi kar neyne.");
if (nonempty && !txt)
return error(ucfirst(field) + " binivîse.");
return txt;
};
},
// Codes from [[Modul:gender and number/data]], in the order they are written.
genderList: {
"n": "nêr", "n-d": "nêr, dual", "n-p": "nêr, pirjimar",
"m": "mê", "m-d": "mê, dual", "m-p": "mê, pirjimar",
"g": "giştî", "g-d": "giştî, dual", "g-p": "giştî, pirjimar",
"nt": "nêtar", "nt-d": "nêtar, dual", "nt-p": "nêtar, pirjimar",
"y": "yekjimar", "d": "dual", "p": "pirjimar",
"împf": "împerfektîv", "pf": "perfektîv"
},
genderRows: [["n", "n-d", "n-p"], ["m", "m-d", "m-p"], ["g", "g-d", "g-p"], ["nt", "nt-d", "nt-p"],
["y", "d", "p"], ["împf", "pf"]]
};
// The language name a line starts with ("Almanî: …").
function getLangName(li) {
var guess = li.textContent || '';
guess = guess.substr(0, guess.indexOf(':'));
return guess.replace(/^\s*/, '');
}
// The language code of a line, from {{Z}}'s span.trad-xx or else its first translation's lang attribute.
// Only the line's own nodes count: a group line written as text ("Sorbî:") has no code of its own.
function getLangCode(li) {
var own = /(?:^|\s)trans-(\S+)/.exec(li.className);
if (own)
return own[1];
var lang = false;
for (var node = li.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (node.nodeType !== 1) {
continue;
}
var spans = [node].concat(Array.prototype.slice.call(node.getElementsByTagName('span')));
for (var i = 0; i < spans.length; i++) {
var m = /(?:^|\s)trad-(\S+)/.exec(spans[i].className);
if (m)
return m[1];
if (!lang && spans[i].lang)
lang = spans[i].lang;
}
}
return lang;
}
// Whether a line is the group line for code: {{Z|code}}, or the group's name written as text.
function isGroupLine(li, code) {
var own = getLangCode(li);
return own ? own === code : sameName(getLangName(li), groupName(code));
}
function TranslationAdders(editor, $root) {
// The ± link on a box heading, which edits its gloss.
function TranslationLabeller(insertDiv) {
var original_span;
var adder_form;
var initial_value;
var edit_button;
var editing = false;
var adderInterface = {
'fields': {
'gloss': function (txt, error) {
return util.validateNoWikisyntax('mane', true)(txt, error);
}
},
'createForm': function () {
var thisId = "a" + String(Math.random()).replace(".", "");
adder_form = newNode('form', {
style: 'display: inline',
width: 'auto',
click: kill_event
},
newNode('label', {
'for': thisId
}, "Mane: "),
newNode('input', {
type: 'text',
name: 'gloss',
value: initial_value,
style: 'width: 50%',
title: 'Maneya ku ev tercime jê re ne, bi kurtî',
id: thisId
}),
newNode('input', {
type: 'submit',
name: 'preview',
value: 'Pêşdîtin'
})
);
return adder_form;
},
'onsubmit': function (values, render) {
render("''" + values.gloss + "''", function (new_html) {
if (editing)
toggle_editing(false);
var old_html = original_span.innerHTML;
editor.addEdit({
'undo': function () {
original_span.innerHTML = old_html;
if (!editing) toggle_editing();
},
'redo': function () {
original_span.innerHTML = new_html;
if (editing) toggle_editing();
},
'edit': function (text) {
return perform_edit(text, values.gloss);
},
'summary': 'mane: "' + (values.gloss.length > 50 ? values.gloss.substr(0, 50) + '…"' : values.gloss + '"')
}, original_span);
});
}
};
function perform_edit(wikitext, gloss) {
var pos = util.getTransTable(wikitext, insertDiv);
if (!pos) {
editor.error("Qutiya tercimeyan nehat dîtin.");
return wikitext;
}
return util.setWikitextGloss(wikitext, pos[2], gloss);
}
// Don't open and close the box when using the form.
function kill_event(e) {
e.stopPropagation();
}
function toggle_editing() {
if (editing) {
adder_form.style.display = "none";
original_span.style.display = "inline";
editing = false;
return;
}
editing = true;
edit_button.text("Tê barkirin…");
editor.withCurrentText(function (currentText) {
var pos = util.getTransTable(currentText, insertDiv);
edit_button.text('±');
if (!pos)
return editor.error("Qutiya tercimeyan nehat dîtin.");
initial_value = util.getWikitextGloss(currentText, pos[2]);
if (!original_span) {
original_span = newNode('span', {
'class': 'wt-edit-recurse'
});
for (var i = 0; i < insertDiv.childNodes.length; i++) {
var child = insertDiv.childNodes[i];
if (child != edit_button[0] && child.className !== 'NavToggle') {
original_span.appendChild(insertDiv.removeChild(child));
i--;
}
}
insertDiv.appendChild(original_span);
new AdderWrapper(editor, adderInterface, insertDiv, original_span);
}
original_span.style.display = "none";
adder_form.style.display = "inline";
adder_form.getElementsByTagName('input')[0].focus();
});
}
edit_button = $('<a href="#" class="translation-adder-gloss">±</a>')
.attr("title", "Maneya qutiyê biguherîne").css("padding", "2px").css("margin-left", "-5px")
.on("click", function (e) {
e.preventDefault();
kill_event(e);
toggle_editing();
return false;
});
$(insertDiv).prepend(edit_button);
}
function TranslationAdder(insertUl) {
var langmetadata = new LangMetadata();
this.fields = {
lang: function (txt, error) {
var code = toCode(txt);
if (!code)
return error("Koda zimanî an navê zimanî binivîse (de, almanî, …).");
if (code == 'ku' || code == 'kmr')
return error("Zimanekî ji bilî kurmancî hilbijêre.");
return code;
},
word: function (txt, error) {
if (txt.indexOf(',') == -1 || forceComma) {
forceComma = false;
if (langmetadata.expectedCase(toCode(thiz.elements.lang.value), mw.config.get('wgTitle'), txt) || forceCase) {
forceCase = false;
return util.validateNoWikisyntax('tercime', true)(txt, error);
}
if (prefs.get('case-knowledge', 'none') == 'none') {
return error(newNode('span',
"Tercime pir caran bi herfa biçûk dest pê dikin. Heke tu bawer î ku bi herfa mezin e, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceCase = true;
inputForm.onsubmit();
prefs.set('case-knowledge', 'guru');
}
}, "ji bo berdewamkirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('case-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke tu ne bawer î ku ev tercime bi herfa mezin e.");
error(msg);
return txt;
}
}
if (prefs.get('comma-knowledge', 'none') == 'none') {
return error(newNode('span',
"Her carê tenê tercimeyekê lê zêde bike. Heke ev tercimeyek e ku virgul tê de heye, ",
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
forceComma = true;
inputForm.onsubmit();
prefs.set('comma-knowledge', 'guru');
}
}, "ji bo lêzêdekirinê li vir bitikîne.")));
} else {
var msg = newNode('span',
newNode('span', {
style: "color: var(--wikt-palette-blue, #0000FF); text-decoration: underline; cursor: pointer;",
click: function () {
prefs.set('comma-knowledge', 'none');
try {
msg.parentNode.removeChild(msg);
} catch (e) {
}
editor.undo();
}
}, "Li \"Paşve\" bitikîne"), " heke te dixwest çend tercimeyan bi hev re lê zêde bikî: ev nabe.");
error(msg);
return txt;
}
},
qual: util.validateNoWikisyntax('not'),
tr: util.validateNoWikisyntax('transliterasyon'),
lit: util.validateNoWikisyntax('maneya herfî'),
rawPageName: util.validateNoWikisyntax('navê rûpelê'),
nested: function (txt, error) {
if (!txt)
return '';
var group = toCode(txt) || (langmetadata.getGroupName(txt.trim()) ? txt.trim() : null);
return group || error("Ji bo \"di bin zimanê\" koda zimanekî an malbatekê binivîse.");
},
nclass1: util.validateNoWikisyntax('sinifa navdêrê'),
nclass2: util.validateNoWikisyntax('sinifa pirjimariyê')
};
var fields = this.fields;
Object.keys(util.genderList).forEach(function (g) {
fields[g] = 'checkbox';
});
this.createForm = function () {
function createGenderNode(name, text) {
var inp = document.createElement("input");
inp.type = "checkbox";
inp.name = name;
var lbl = document.createElement("label");
lbl.appendChild(inp);
lbl.appendChild(document.createTextNode(text));
return lbl;
}
var genderControls = {};
for (var g in util.genderList) genderControls[g] = createGenderNode(g, util.genderList[g]);
var langName = newNode('span', { 'class': 'translation-adder-langname' });
var controls = {
lang: newNode('input', {
size: 12,
type: 'text',
name: 'lang',
value: prefs.get('curlang', ''),
placeholder: 'ziman',
title: 'Koda zimanî (de, fr, …) an navê wî (almanî, fransî, …)'
}),
transliteration: newNode('p', "Transliterasyon: ",
newNode('input', {
name: "tr",
title: "Tercime bi herfên latînî."
}), " (nimûne: zìmǔ ji bo 字母)"),
literal: newNode('p', "Maneya herfî: ",
newNode('input', {
name: "lit",
title: "Maneya kelîme bi kelîme ya tercimeyê."
})),
qualifier: newNode('p', "Not: ", newNode('input', {
name: 'qual',
title: "Notek kurt li ser tercimeyê"
}), " (nimûne: kevn, devkî)"),
display: newNode('p', "Navê rûpelê: ", newNode('input', {
name: 'rawPageName',
title: "Heke tercime ne forma bingehîn be, navê rûpela ku jê re lînk bê dayîn."
}), " (nimûne: 疲れる ji bo 疲れた)"),
nested: newNode('p', "Di bin zimanê: ", newNode('input', {
name: 'nested',
title: "Zimanê ku ev ziman di bin wî de tê rêzkirin"
}), " (nimûne: zh ji bo mandarînî)"),
genders: genderControls,
nclass: newNode('p',
"Sinifa navdêrê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass1',
title: "Sinifa navdêrê di forma yekjimar de."
}),
" Sinifa pirjimariyê: ", newNode('input', {
type: 'text',
size: 4,
name: 'nclass2',
title: "Sinifa navdêrê di forma pirjimar de, heke hebe."
}))
};
var $gender = $("<p>");
util.genderRows.forEach(function (row) {
var $div = $("<div>");
row.forEach(function (code) { $div.append(controls.genders[code]); });
$gender.append($div);
});
controls.gender = $gender[0];
langInput = controls.lang;
var showButton = newNode('span', {
'click': function () {
advancedMode = !advancedMode;
showButton.textContent = advancedMode ? " Kêmtir" : " Zêdetir";
updateFieldVisibility.call(langInput, true);
},
'style': "color: var(--wikt-palette-blue,#0000FF); cursor: pointer;"
}, advancedMode ? " Kêmtir" : " Zêdetir");
function autoTransliterate() {
var rawPageName = langmetadata.computeRawPageName(toCode(thiz.elements.lang.value), thiz.elements.word.value);
if (rawPageName && rawPageName !== thiz.elements.word.value)
thiz.elements.rawPageName.value = rawPageName;
}
function updateFieldVisibility(preserve) {
preserve = (preserve === true);
function show() {
for (var i = 0; i < arguments.length; i++) {
arguments[i].style.display = arguments[i].nodeName == 'P' ? "block" : "inline";
}
}
function hide() {
for (var i = 0; i < arguments.length; i++)
arguments[i].style.display = "none";
}
// Shows the remaining arguments if the first is true, else hides them.
function toggle(condition) {
var nodes = [].slice.call(arguments, 1);
(condition ? show : hide).apply(this, nodes);
}
var lang = toCode(langInput.value) || '';
// A code in the field gets its name beside it, until leaving the field puts the name in.
var typed = langInput.value.trim();
langName.textContent = codeToName[typed] ? " (" + codeToName[typed] + ")" : "";
if (!preserve) {
autoTransliterate();
// Mandarînî goes under Çînî and so on, unless "Di bin zimanê" is changed.
thiz.elements.nested.value = lang ? langmetadata.getNested(lang) : '';
}
var scriptGuess = langmetadata.guessScript(lang);
if (!advancedMode) {
var g = lang ? langmetadata.getGenders(lang) : undefined;
if (g === undefined) {
hide(controls.gender);
} else {
for (var genderName in util.genderList) {
toggle(g.indexOf(genderName) != -1, controls.genders[genderName]);
}
}
toggle(g, controls.gender);
toggle(langmetadata.hasNounClasses(lang), controls.nclass);
var hasAutomaticTransliteration = new window.LanguageUtils().HasAutomaticTransliteration(lang);
toggle(scriptGuess && scriptGuess != 'Latn' && !hasAutomaticTransliteration, controls.transliteration);
toggle(langmetadata.needsRawPageName(lang), controls.display);
hide(controls.literal, controls.qualifier, controls.nested);
} else {
show.apply(this, [controls.gender].concat(
Object.keys(util.genderList).map(function (k) { return controls.genders[k]; }),
[controls.nclass, controls.transliteration, controls.literal, controls.qualifier, controls.display,
controls.nested]));
}
}
var langBox = languagePicker(langInput, function () {
updateFieldVisibility.call(langInput);
});
window.setTimeout(function () {
updateFieldVisibility.call(langInput);
}, 0);
inputForm = newNode('form',
newNode('p', "Tercimeyekê lê zêde bike: ",
langBox, langName, newNode('b', ': '), newNode('input', {
'name': 'word',
placeholder: 'tercime',
title: 'Tercime bi wî zimanî',
size: 20,
keyup: autoTransliterate,
change: autoTransliterate
}),
newNode('input', {
'type': 'submit',
'value': 'Pêşdîtin'
}), showButton
),
controls.gender,
controls.nclass,
controls.transliteration,
controls.literal,
controls.display,
controls.qualifier,
controls.nested
);
return inputForm;
};
this.onsubmit = function (values, render) {
var wikitext;
langmetadata.retrieveRawPageName(values.lang, values.word, values.rawPageName, function (rawPageName, needsRawPageName) {
if (needsRawPageName) {
values.rawPageName = rawPageName;
values.wordMinus = '';
} else {
values.rawPageName = '';
values.wordMinus = rawPageName;
}
langmetadata.hasWiktionaryWithEntry(values.lang, values.rawPageName || values.wordMinus, function (hasInterwiki) {
var templateBody = '|' + values.lang + '|' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) +
(values.nclass1 ? '|c' + values.nclass1 : '') +
(values.nclass2 ? '|c' + values.nclass2 : '');
templateBody += Object.keys(util.genderList).map(function (k) {
return values[k] ? '|' + k : '';
}).join("");
templateBody +=
(values.tr ? '|tr=' + values.tr : '') +
(values.lit ? '|lit=' + values.lit : '') +
(values.rawPageName ? '|cuda=' + values.word : '');
var qualifier = values.qual ? ' {{i|' + values.qual + '}}' : '';
wikitext = '{{Z|' + values.lang + '}}: {{' + (hasInterwiki ? "W+" : "W") + templateBody + "}}" + qualifier;
render(wikitext, function (html) {
registerEdits(values, wikitext, html);
});
});
});
};
var thiz = this;
var prefs = new Preferences();
var langInput;
var inputForm;
var advancedMode = prefs.get('more-display', 'none') != 'none';
var forceComma = false;
var forceCase = false;
function resetElements() {
prefs.set('more-display', advancedMode ? 'block' : 'none');
['word', 'nclass1', 'nclass2', 'lit', 'tr', 'rawPageName', 'qual'].forEach(function (f) {
thiz.elements[f].value = '';
});
Object.keys(util.genderList).forEach(function (g) {
thiz.elements[g].checked = false;
});
prefs.set('curlang', thiz.elements.lang.value);
}
// Runs after the new line has been rendered: finds where it goes and registers the edit.
function registerEdits(values, wikitext, content) {
var li = newNode('li', {
'class': 'trans-' + values.lang
});
li.innerHTML = content;
var lang = getLangName(li);
var summary = '+' + codeToName[values.lang] + ': [[' +
langmetadata.applyOrthographicalCorrections(values.lang, values.rawPageName || values.word) + ']]';
var insertBefore = null;
var nextLanguage = null;
var nestedCode = values.nested;
// A top-level line for the language already there takes the translation, and then moves under its group.
var table = util.getTable(insertUl);
var nestAfter = nestedCode && misplacedLines(table, values.lang).length ? values.lang : null;
if (nestAfter) {
nestedCode = '';
}
var nestedHeading = nestedCode ? ucfirst(groupName(nestedCode)) : '';
var nestedWikitext = "\n* {{Z|" + nestedCode + "}}:\n*: " + wikitext;
function addEdit(edit, span) {
var fields = ["lang", "word", "nclass1", "nclass2", "rawPageName", "qual", "tr", "lit"];
editor.addEdit({
'undo': function () {
edit.undo();
if (fields.slice(1).every(function (f) { return thiz.elements[f].value === ""; })) {
for (var i = 0; i < fields.length; i++) {
thiz.elements[fields[i]].value = values[fields[i]];
}
for (var genderName in util.genderList) {
thiz.elements[genderName].checked = values[genderName];
}
}
},
'redo': function () {
edit.redo();
// Editing mode, so the new translation has its ✎ and × at once.
setLineEditing(table, editor, true);
for (var i = 0; i < fields.length; i++) {
if (thiz.elements[fields[i]].value != values[fields[i]])
return;
}
resetElements();
},
'edit': edit.edit,
'summary': summary
}, span);
if (nestAfter) {
editor.addEdit(nestEdit(table, nestAfter), null);
}
}
if (lang && !nestedCode) {
// Every line in this box.
var lis = $(insertUl).parent().parent().find('li').get()
.concat($(insertUl).parent().parent().find('dd').get());
for (var j = 0; j < lis.length; j++) {
if (lis[j].getElementsByTagName('form').length > 0)
continue;
var ln = getLangName(lis[j]);
if (ln && sameName(ln, lang)) {
var span = newNode('span', { 'class': 'translation-adder-added' });
var parent = lis[j];
if (parent.getElementsByTagName('ul').length + parent.getElementsByTagName('dl').length == 0) {
span.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(parent, span);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to the language's existing line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
var node = parent.firstChild;
var hastrans = false;
while (node) {
if (node.nodeType == 1) {
var nn = node.nodeName;
if (nn == 'UL' || nn == 'DL') {
span.innerHTML = (hastrans ? ", " : " ") + afterColon(content);
addEdit({
'redo': function () {
parent.insertBefore(span, node);
},
'undo': function () {
parent.removeChild(span);
},
'edit': getEditFunction(values, wikitext, ln, values.lang, function (text, ipos) {
// Adding to a language that has nested lines under it.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + (hastrans ? "," : "") + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, span);
return resetElements();
} else {
hastrans = true;
}
}
node = node.nextSibling;
}
}
} else if (ln && compareNames(ln, lang) > 0 && (!nextLanguage || compareNames(ln, nextLanguage) < 0) && lis[j].parentNode.parentNode.nodeName != 'LI') {
nextLanguage = ln;
var before = lis[j];
insertBefore = [{
'redo': function () {
before.parentNode.insertBefore(li, before);
},
'undo': function () {
before.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, ln, getLangCode(before), function (text, ipos) {
// A new line before the next language's line.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + "\n* " + wikitext + text.substr(lineend);
})
}, li];
}
}
}
if (nestedCode) {
nextLanguage = null;
insertBefore = null;
li.innerHTML = nestedHeading + ":" + "<dl><dd class=\"trans-" + values.lang + "\">" + content + "</dd></dl>";
var tops = insertUl.parentNode.parentNode.getElementsByTagName('li');
for (var k = 0; k < tops.length; k++) {
if (tops[k].getElementsByTagName('form').length > 0)
continue;
// Only top-level lines.
if (tops[k].parentNode.parentNode.nodeName != 'TD')
continue;
var tln = getLangName(tops[k]);
if (tln && sameName(tln, nestedHeading)) {
var group = tops[k];
var sublis = group.getElementsByTagName('li');
if (!sublis.length)
sublis = group.getElementsByTagName('dd');
if (sublis.length == 0) {
var dd = newNode('dd', {
'class': 'trans-' + values.lang
});
var dl = newNode('dl', dd);
dd.innerHTML = content;
addEdit({
'redo': function () {
group.appendChild(dl);
},
'undo': function () {
group.removeChild(dl);
},
'edit': getEditFunction(values, wikitext, nestedHeading, nestedCode, function (text, ipos) {
// The first nested line under the group's line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "\n*: " + wikitext + text.substr(lineend);
})
}, dd);
return resetElements();
}
var item = newNode(sublis[0].nodeName, {
'class': 'trans-' + values.lang
});
var linestart = item.nodeName == 'DD' ? '\n*: ' : '\n** ';
item.innerHTML = content;
for (var m = 0; m < sublis.length; m++) {
var subln = getLangName(sublis[m]);
var sub = sublis[m];
if (subln && sameName(subln, lang)) {
var addSpan = newNode('span', { 'class': 'translation-adder-added' });
addSpan.innerHTML = ", " + afterColon(content);
addEdit({
'redo': function () {
appendToLine(sub, addSpan);
},
'undo': function () {
sub.removeChild(addSpan);
},
'edit': getEditFunction(values, wikitext, lang, values.lang, function (text, ipos) {
// Adding to an existing nested line.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + "," + wikitext.substr(wikitext.indexOf(':') + 1) + text.substr(lineend);
})
}, addSpan);
return resetElements();
} else if (subln && langmetadata.nestsBefore(lang, subln)) {
addEdit({
'redo': function () {
sub.parentNode.insertBefore(item, sub);
},
'undo': function () {
sub.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, subln, getLangCode(sub), function (text, ipos) {
// A nested line in order.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
}
}
var last = sublis[sublis.length - 1];
addEdit({
'redo': function () {
last.parentNode.appendChild(item);
},
'undo': function () {
last.parentNode.removeChild(item);
},
'edit': getEditFunction(values, wikitext, getLangName(last), getLangCode(last), function (text, ipos) {
// A nested line at the end of its group.
var lineend = text.indexOf('\n', ipos);
return text.substr(0, lineend) + linestart + wikitext + text.substr(lineend);
})
}, item);
return resetElements();
} else if (tln && compareNames(tln, nestedHeading) > 0 && (!nextLanguage || compareNames(tln, nextLanguage) < 0)) {
nextLanguage = tln;
var next = tops[k];
insertBefore = [{
'redo': function () {
next.parentNode.insertBefore(li, next);
},
'undo': function () {
next.parentNode.removeChild(li);
},
'edit': getEditFunction(values, wikitext, tln, getLangCode(next), function (text, ipos) {
// A new group line with the nested line under it.
var lineend = text.lastIndexOf('\n', ipos);
return text.substr(0, lineend) + nestedWikitext + text.substr(lineend);
})
}, li];
}
}
wikitext = "{{Z|" + nestedCode + "}}:\n*: " + wikitext;
}
li.className = "trans-" + values.lang;
if (insertBefore) {
addEdit(insertBefore[0], insertBefore[1]);
} else {
// After the last line.
addEdit({
'redo': function () {
insertUl.appendChild(li);
},
'undo': function () {
insertUl.removeChild(li);
},
'edit': getEditFunction(values, wikitext)
}, li);
}
return resetElements();
}
// The wikitext change for this submission. With findLanguage, callback gets the position of that
// language's line; without, the line goes at the end of the box.
function getEditFunction(values, wikitext, findLanguage, findLangCode, callback) {
return function (text) {
var p = util.getTransTable(text, insertUl);
if (!p)
return editor.error("Qutiya tercimeyan ji bo '" + values.lang + ":" + values.word +
"' nehat dîtin. Maneyên qutiyan divê ji hev cuda bin.");
var stapos = p[0];
var endpos = p[1];
if (findLanguage) {
var box = text.substring(stapos, endpos);
var sub = values.nested ? "[:*]?" : "";
var escapedName = mw.util.escapeRegExp(findLanguage);
var patterns = [
"\\*" + sub + " ?\\[\\[" + escapedName + "\\]\\]:",
"\\*" + sub + " ?" + escapedName + ":"
];
if (findLangCode)
patterns.unshift("\\*" + sub + " ?\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(findLangCode) + "\\s*(?:\\|[^{}]*)?\\}\\}\\s*:");
var ipos = -1;
for (var i = 0; i < patterns.length && ipos == -1; i++) {
ipos = box.search(new RegExp(patterns[i], "i"));
}
if (ipos > -1) {
return callback(text, ipos + stapos);
}
return editor.error("Rêza zimanê " + findLanguage + " nehat dîtin. Ji kerema xwe rêzê rast bike.");
}
return text.substr(0, endpos) + "* " + wikitext + "\n" + text.substr(endpos);
};
}
}
function createTranslationAdderRow(li) {
var $ul = $("<ul>").append(li);
return $("<tr>")
.addClass("translation-adder-row")
.append($("<td>").attr("colspan", "3").css("text-align", "left").append($ul));
}
$root.find('table.translations').each(function () {
if (this.dataset.translationAdder) {
return;
}
this.dataset.translationAdder = "1";
var td = $(this).find("td.translations-cell")[0];
if (!td) {
return;
}
var uls = $(td).children("ul").get();
if (uls.length == 0) {
uls = [td.appendChild(newNode('ul'))];
}
var li = document.createElement('li');
$(this).append(createTranslationAdderRow(li));
new AdderWrapper(editor, new TranslationAdder(uls[uls.length - 1]), li);
var head = $(this).closest('.NavFrame').children('.NavHead')[0];
if (head) {
new TranslationLabeller(head);
}
});
}
// HTML for wikitext as this page renders it, without the paragraph around it.
function render(wikitext) {
return new mw.Api().parse(wikitext, {
title: mw.config.get('wgPageName'),
pst: true,
disablelimitreport: true
}).then(function (html) {
var $out = $('<div>').html(html);
var $p = $out.find('p').first();
return ($p.length ? $p : $out.children().first()).html().trim();
});
}
// The wikitext of a translation line: {start, restStart, end, rest}, where rest is what follows
// "Language:"; null when the line is not found.
function findLine(text, line) {
var box = util.getTransTable(text, line);
if (!box) {
return null;
}
var code = getLangCode(line);
var name = getLangName(line);
var label = code ?
"\\{\\{\\s*Z\\s*\\|\\s*" + mw.util.escapeRegExp(code) + "\\s*(?:\\|[^{}\\n]*)?\\}\\}" :
"(?:\\[\\[" + mw.util.escapeRegExp(name) + "\\]\\]|" + mw.util.escapeRegExp(name) + ")";
var part = text.substring(box[0], box[1]);
var m = new RegExp("^\\*[:*]* ?" + label + "\\s*:[ \\t]*(.*)$", "im").exec(part);
if (!m) {
return null;
}
var start = box[0] + m.index;
var end = start + m[0].length;
return { start: start, restStart: end - m[1].length, end: end, rest: m[1], label: text.substring(start, end - m[1].length) };
}
// A line's own nodes: everything before the lines nested under it, without this gadget's buttons.
function ownNodes(line) {
var nodes = [];
for (var node = line.firstChild; node && !/^(UL|DL)$/.test(node.nodeName); node = node.nextSibling) {
if (!(node.nodeType === 1 && /(?:^|\s)translation-adder-(?:line-tools|term-edit)(?:\s|$)/.test(node.className))) {
nodes.push(node);
}
}
return nodes;
}
var TERM_CALL = /\{\{\s*W[+-]?(?:\/ceribandin)?\s*\|/g;
// The {{W}} calls in the wikitext after "Language:", in order: [{start, end, text}].
function termCalls(rest) {
var out = [];
var m;
TERM_CALL.lastIndex = 0;
while ((m = TERM_CALL.exec(rest))) {
var end = util.parseTemplate(rest, m.index).end;
out.push({ start: m.index, end: end, text: rest.substring(m.index, end) });
TERM_CALL.lastIndex = end;
}
return out;
}
// rest with its i-th {{W}} call replaced by text, or removed with the comma that joins it to the others.
function replaceTerm(rest, i, text) {
var call = termCalls(rest)[i];
if (!call) {
return null;
}
if (text) {
return rest.substring(0, call.start) + text + rest.substring(call.end);
}
var before = rest.substring(0, call.start);
var after = rest.substring(call.end);
if (/,\s*$/.test(before)) {
before = before.replace(/\s*,\s*$/, '');
} else {
after = after.replace(/^\s*,\s*/, '');
}
return (before + after).trim();
}
// The rendered translations of a line, in order: the element each {{W}} call starts with.
function termNodes(line) {
var nodes = [];
ownNodes(line).forEach(function (n) {
if (n.nodeType === 1 && /(?:^|\s)translation-adder-added(?:\s|$)/.test(n.className)) {
nodes.push.apply(nodes, Array.prototype.slice.call(n.childNodes));
} else {
nodes.push(n);
}
});
// A translation starts with its link's span, or with the "ttbc" span of one marked for checking.
// The second form after a "/" (traditional / simplified Chinese) belongs to the same translation.
var terms = [];
var afterSlash = false;
nodes.forEach(function (n) {
if (n.nodeType === 3) {
if (n.data.trim()) {
afterSlash = n.data.trim() === '/';
}
return;
}
if (n.nodeType !== 1) {
return;
}
// The separator is drawn in a span of its own.
if (!n.lang && n.textContent.trim() === '/') {
afterSlash = true;
return;
}
var isTerm = /(?:^|\s)ttbc(?:\s|$)/.test(n.className) ||
(n.lang && !/(?:^|\s)(?:tr|ts)(?:\s|$)/.test(n.className));
if (isTerm && !(afterSlash && terms.length)) {
terms.push(n);
}
afterSlash = false;
});
return terms;
}
// The template dialog of [[MediaWiki:Gadget-wikt.cite.js]], built from the template's TemplateData.
// Resolves with the edited call, "" when it is removed, or null when cancelled.
function templateDialog(call) {
return mw.loader.using('ext.gadget.wikt.cite').then(function (require) {
return require('ext.gadget.wikt.cite').openTemplateDialog(call);
});
}
// On one translation line: ✎ and × after each translation (its template in a dialog, and removing it).
function LineTools(editor, line) {
var name = getLangName(line);
if (!name) {
return;
}
var code = getLangCode(line);
var title = ucfirst(codeToName[code] || name);
var $tools = $('<span class="translation-adder-line-tools">');
var self = this;
this.$tools = $tools;
this.line = line;
// Whether editing mode is on; set by setLineEditing().
this.editing = true;
var nested = line.querySelector('ul, dl');
if (nested) {
$(nested).before($tools);
} else {
$(line).append($tools);
}
function notFound() {
return editor.error("Rêza " + title + " di wîkîmetnê de nehat dîtin.");
}
// The i-th translation drawn is the i-th {{W}} call only when the line holds nothing else that draws
// one ({{g}}, {{l}}, …); otherwise ✎ and × could reach the wrong one, so they refuse.
function callsMatch(found) {
if (termCalls(found.rest).length === termNodes(line).length) {
return true;
}
editor.error("Tercimeyên rêza " + title + " bi wîkîmetnê re li hev nayên; rêzê bi destan biguherîne.");
return false;
}
// Queues a new text after "Language:", drawn in place from its rendering.
function queueRest(makeRest, summary) {
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var rest = found && makeRest(found.rest);
if (rest === null || rest === undefined) {
return notFound();
}
render(found.label.replace(/^\*[:*]* ?/, '') + ' ' + rest).then(function (html) {
var before = ownNodes(line);
var holder = document.createElement('span');
holder.innerHTML = html;
var after = Array.prototype.slice.call(holder.childNodes);
editor.addEdit({
redo: function () {
before.forEach(function (n) { $(n).detach(); });
after.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
undo: function () {
after.forEach(function (n) { $(n).detach(); });
before.forEach(function (n) { line.insertBefore(n, $tools[0]); });
self.markTerms();
},
edit: function (current) {
var at = findLine(current, line);
var next = at && makeRest(at.rest);
if (next === null || next === undefined) {
return notFound();
}
return current.substring(0, at.restStart) + next + current.substring(at.end);
},
summary: summary
}, line);
});
});
}
// The pencil after each translation, put back whenever the line is redrawn.
this.markTerms = function () {
$(line).children('.translation-adder-term-edit')
.add($(line).children('.translation-adder-added').children('.translation-adder-term-edit'))
.remove();
var terms = termNodes(line);
terms.forEach(function (term, i) {
var $edit = $('<a href="#" class="translation-adder-term-edit translation-adder-term-pencil"></a>')
.attr({ title: 'Vê tercimeyê biguherîne', 'aria-label': 'Vê tercimeyê biguherîne' });
// After everything the translation draws: its interwiki link, gender and transliteration.
var last = term;
while (last.nextSibling && !/^(UL|DL)$/.test(last.nextSibling.nodeName) &&
terms.indexOf(last.nextSibling) === -1 &&
!(last.nextSibling.nodeType === 3 && /[,;]/.test(last.nextSibling.data)) &&
!(last.nextSibling.nodeType === 1 &&
/translation-adder-(?:line-tools|added)/.test(last.nextSibling.className))) {
last = last.nextSibling;
}
var $del = $('<a href="#" class="translation-adder-term-edit translation-adder-term-close"></a>')
.attr({ title: 'Vê tercimeyê rake', 'aria-label': 'Vê tercimeyê rake' });
$(last).after($edit, $del);
$edit.add($del).toggle(self.editing);
// The only translation of a line with nothing under it takes the line with it.
$del.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
if (!found || !termCalls(found.rest)[i]) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
if (termCalls(found.rest).length === 1 && !line.querySelector('ul, dl')) {
removeLine();
} else {
queueRest(function (rest) {
return replaceTerm(rest, i, '');
}, '−' + title);
}
});
});
$edit.on('click', function (e) {
e.preventDefault();
editor.withCurrentText(function (text) {
var found = findLine(text, line);
var call = found && termCalls(found.rest)[i];
if (!call) {
return notFound();
}
if (!callsMatch(found)) {
return;
}
templateDialog(call.text).then(function (edited) {
if (edited === null || edited === call.text) {
return;
}
queueRest(function (rest) {
return replaceTerm(rest, i, edited);
}, (edited ? '±' : '−') + title);
});
});
});
});
};
this.markTerms();
function removeLine() {
editor.addEdit({
redo: function () {
line.classList.add('translation-adder-removed');
},
undo: function () {
line.classList.remove('translation-adder-removed');
},
edit: function (current) {
var at = findLine(current, line);
if (!at) {
return notFound();
}
return current.substring(0, at.start) + current.substring(at.end + (current.charAt(at.end) === '\n' ? 1 : 0));
},
summary: '−' + title
}, line);
}
}
// ---- nesting: a language en.wiktionary nests (cmn) goes under its group's line ({{Z|zh}}) ----
// The language code of a wikitext line, from its {{Z}}.
function lineCode(l) {
var m = /^\*[:*]* ?\{\{\s*Z\s*\|\s*([^|}\s]+)/.exec(l);
return m ? m[1] : null;
}
// The language name of a wikitext line, for sorting.
function lineName(l) {
var code = lineCode(l);
if (code) {
return groupName(code);
}
var m = /^\*[:*]* ?(?:\[\[)?([^\]:|]+)/.exec(l);
return m ? m[1].trim() : '';
}
function isChildLine(l) {
return /^\*[:*]/.test(l) && !/^\*[:*][:*]/.test(l);
}
// text with the top-level lines of node's box whose language nests (only that language, if given) moved
// under their group line, which is added in order where there is none. A group that already has a line for
// the language gets its translations added to it.
function nestLines(text, node, only) {
var p = util.getTransTable(text, node);
if (!p) {
return null;
}
var meta = new LangMetadata();
var body = text.substring(p[0], p[1]);
var trimmed = body.replace(/\n+$/, '');
var tail = body.slice(trimmed.length);
var head = [], entries = [];
trimmed.split('\n').forEach(function (l) {
if (/^\*(?![:*])/.test(l)) {
entries.push({ line: l, code: lineCode(l), children: [] });
} else if (entries.length) {
entries[entries.length - 1].children.push(l);
} else {
head.push(l);
}
});
var moved = 0;
entries.slice().forEach(function (e) {
var parent = e.code && meta.getNested(e.code);
if (!parent || (only && e.code !== only)) {
return;
}
entries.splice(entries.indexOf(e), 1);
var group = entries.filter(function (x) {
return x.code ? x.code === parent : sameName(lineName(x.line), groupName(parent));
})[0];
if (!group) {
group = { line: '* {{Z|' + parent + '}}:', code: parent, children: [] };
var at = entries.findIndex(function (x) {
return compareNames(lineName(x.line), groupName(parent)) > 0;
});
entries.splice(at === -1 ? entries.length : at, 0, group);
}
var prefix = group.children.some(function (c) { return /^\*\*/.test(c); }) ? '**' : '*:';
var deeper = e.children.map(function (c) { return prefix + c.slice(1); });
var same = group.children.findIndex(function (c) { return isChildLine(c) && lineCode(c) === e.code; });
if (same !== -1) {
var terms = e.line.replace(/^\*\s*\{\{[^{}]*\}\}\s*:\s*/, '');
group.children[same] = group.children[same].replace(/\s*$/, '') + (terms ? ', ' + terms : '');
[].splice.apply(group.children, [same + 1, 0].concat(deeper));
} else {
var name = codeToName[e.code] || e.code;
var at2 = group.children.findIndex(function (c) {
return isChildLine(c) && compareNames(lineName(c), name) > 0;
});
[].splice.apply(group.children, [at2 === -1 ? group.children.length : at2, 0]
.concat([prefix + ' ' + e.line.replace(/^\*\s*/, '')], deeper));
}
moved++;
});
if (!moved) {
return text;
}
var out = head.slice();
entries.forEach(function (x) {
out.push(x.line);
out.push.apply(out, x.children);
});
return text.substring(0, p[0]) + out.join('\n') + tail + text.substring(p[1]);
}
// The top-level lines of a box.
function topLines(table) {
return $(table).find('td.translations-cell').first().children('ul').children('li').get();
}
// The top-level lines whose language nests: [{li, code, parent}].
function misplacedLines(table, only) {
var meta = new LangMetadata();
return topLines(table).map(function (li) {
var code = getLangCode(li);
var parent = code && meta.getNested(code);
return parent && (!only || code === only) ? { li: li, code: code, parent: parent } : null;
}).filter(Boolean);
}
// Draws nestLines() on the page: the lines move under their group, highlighted. Returns the undo.
function nestDom(table, only) {
var undos = [];
misplacedLines(table, only).forEach(function (m) {
var group = topLines(table).filter(function (li) { return isGroupLine(li, m.parent); })[0];
if (!group) {
var made = newNode('li', { 'class': 'trans-' + m.parent + ' translation-adder-new' },
ucfirst(groupName(m.parent)) + ':');
var next = topLines(table).filter(function (li) {
return compareNames(getLangName(li), getLangName(made)) > 0;
})[0];
m.li.parentNode.insertBefore(made, next || null);
group = made;
undos.push(function () { $(made).remove(); });
}
var list = $(group).children('dl, ul')[0];
if (!list) {
var newList = list = group.appendChild(newNode('dl'));
undos.push(function () { $(newList).remove(); });
}
var home = m.li.parentNode, after = m.li.nextSibling;
var item = $(list).children().filter(function () { return getLangCode(this) === m.code; })[0];
if (item) {
var span = newNode('span', { 'class': 'translation-adder-new' });
var holder = document.createElement('div');
ownNodes(m.li).forEach(function (n) { holder.appendChild(n.cloneNode(true)); });
span.innerHTML = ', ' + afterColon(holder.innerHTML);
var sub = $(item).children('dl, ul')[0];
item.insertBefore(span, sub || null);
m.li.style.display = 'none';
undos.push(function () { $(span).remove(); m.li.style.display = ''; });
} else {
var before = $(list).children().filter(function () {
return compareNames(getLangName(this), getLangName(m.li)) > 0;
})[0];
// In a "*:" list the line is drawn as a <dd>, as the saved page will have it.
var wrap = list.nodeName === 'DL' ? newNode('dd', { 'class': 'translation-adder-wrap' }) : null;
if (wrap) {
wrap.appendChild(m.li);
list.insertBefore(wrap, before || null);
} else {
list.insertBefore(m.li, before || null);
}
m.li.classList.add('translation-adder-new');
undos.push(function () {
m.li.classList.remove('translation-adder-new');
home.insertBefore(m.li, after);
$(wrap).remove();
});
}
});
return function () {
undos.reverse().forEach(function (f) { f(); });
};
}
// The change that nests the lines of table's box (only one language's, if given).
function nestEdit(table, only) {
var undo = null;
return {
redo: function () {
undo = nestDom(table, only);
},
undo: function () {
if (undo) {
undo();
undo = null;
}
},
edit: function (text) {
return nestLines(text, table, only);
},
summary: 'tercime di bin zimanên xwe de'
};
}
// Shows (on) or hides the ✎ and × on every translation line of the boxes in root, and on a box with
// lines that belong under another language a link that moves them there.
function setLineEditing(root, editor, on) {
return loadNames().then(function () {
editor = editor || new Editor();
// root may be a box itself.
var $tables = $(root).find('table.translations').addBack('table.translations');
$tables.each(function () {
var table = this;
var $row = $(table).data('translationAdderNest');
if (!$row && on) {
var count = misplacedLines(table).length;
if (!count) {
return;
}
var $nest = $('<a href="#">').text(count === 1 ?
'Rêzekê di bin zimanê wê de bi cih bike' : count + ' rêzan di bin zimanên wan de bi cih bike');
$row = $('<tr class="translation-adder-nest-row">').append($('<td colspan="3">').append('↳ ', $nest));
$(table).find('td.translations-cell').first().parent().before($row);
$(table).data('translationAdderNest', $row);
$nest.on('click', function (e) {
e.preventDefault();
editor.addEdit(nestEdit(table), null);
$row.remove();
});
}
if ($row) {
$row.toggle(on);
}
});
$tables.find('td.translations-cell').find('li, dd').each(function () {
var tools = $(this).data('translationAdderTools');
if (!tools && on) {
tools = new LineTools(editor, this);
$(this).data('translationAdderTools', tools);
}
if (tools && tools.$tools) {
tools.editing = on;
tools.$tools.toggle(on);
if (on) {
tools.markTerms();
}
$(this).find('.translation-adder-term-edit').filter(function () {
return $(this).closest('li, dd')[0] === tools.line;
}).toggle(on);
}
});
});
}
// Adds the forms to the boxes in root (an element or jQuery object). editor collects the changes: by
// default this gadget's own Editor; DefSideBoxes passes one that queues them in its save box.
function attach(root, editor) {
return loadNames().then(function () {
TranslationAdders(editor || new Editor(), $(root));
});
}
window.wikt = window.wikt || {};
window.wikt.translationAdder = {
attach: attach,
setLineEditing: setLineEditing,
compareNames: compareNames
};
module.exports = window.wikt.translationAdder;
// </nowiki>
3t4tq8t3zkef27imxi6hkpqh8slicfa
MediaWiki:Gadget-wikt.translation-adder/data.js
8
1359499
6865647
6865423
2026-10-24T08:46:03Z
Wikihez
7344
Per-language table from wikt.lang-metadata instead of an own copy; genders already in ku codes
6865647
javascript
text/javascript
// <nowiki>
/*
* Package file of [[MediaWiki:Gadget-wikt.translation-adder.js]]: per-language data for the form.
* Forked from en.wiktionary's [[en:MediaWiki:Gadget-TranslationAdder-Data.js]]. The per-language table is
* wikt.lang-metadata's, in the codes of [[Modul:gender and number/data]].
*/
/* global $, ScriptUtils, mw */
window.LangMetadata = function() {
//Singleton
if (arguments.callee.instance)
return arguments.callee.instance;
else
arguments.callee.instance = this;
// Per-language data in ku.wiktionary's codes, the same table wikt.edit reads:
// [[MediaWiki:Gadget-wikt.lang-metadata.json]].
var metadata = require("ext.gadget.wikt.lang-metadata");
var clean = {
aar: "aa",
afar: "aa",
abk: "ab",
abkhazian: "ab",
afr: "af",
afrikaans: "af",
aka: "ak",
akan: "ak",
amh: "am",
amharic: "am",
ara: "ar",
arabic: "ar",
arg: "an",
aragonese: "an",
asm: "as",
assamese: "as",
ava: "av",
avaric: "av",
ave: "ae",
avestan: "ae",
aym: "ay",
aymara: "ay",
aze: "az",
azerbaijani: "az",
bak: "ba",
bashkir: "ba",
bam: "bm",
bambara: "bm",
bel: "be",
belarusian: "be",
ben: "bn",
bengali: "bn",
bis: "bi",
bislama: "bi",
bod: "bo",
tibetan: "bo",
bs: "sh",
bos: "sh",
bosnian: "sh",
bre: "br",
breton: "br",
bul: "bg",
bulgarian: "bg",
cat: "ca",
catalan: "ca",
ces: "cs",
czech: "cs",
cha: "ch",
chamorro: "ch",
che: "ce",
chechen: "ce",
chu: "cu",
churchslavic: "cu",
chv: "cv",
chuvash: "cv",
cor: "kw",
cornish: "kw",
cos: "co",
corsican: "co",
cre: "cr",
cree: "cr",
cym: "cy",
welsh: "cy",
dan: "da",
danish: "da",
deu: "de",
german: "de",
div: "dv",
dhivehi: "dv",
dzo: "dz",
dzongkha: "dz",
ekk: "et",
ell: "el",
greek: "el",
eng: "en",
english: "en",
epo: "eo",
esperanto: "eo",
est: "et",
estonian: "et",
eus: "eu",
basque: "eu",
ewe: "ee",
fao: "fo",
faroese: "fo",
fas: "fa",
//"fa-ira": "fa",
//"fa-cls": "fa",
//prs: "fa",
persian: "fa",
fij: "fj",
fijian: "fj",
fil: "tl",
fin: "fi",
finnish: "fi",
fra: "fr",
french: "fr",
fry: "fy",
westernfrisian: "fy",
ful: "ff",
fulah: "ff",
gla: "gd",
scottishgaelic: "gd",
gle: "ga",
irish: "ga",
glg: "gl",
galician: "gl",
guj: "gu",
gujarati: "gu",
gn: "gug",
grn: "gug",
guarani: "gug",
glv: "gv",
manx: "gv",
hat: "ht",
haitian: "ht",
hau: "ha",
hausa: "ha",
heb: "he",
hebrew: "he",
her: "hz",
herero: "hz",
hin: "hi",
hindi: "hi",
hmo: "ho",
hirimotu: "ho",
hr: "sh",
hrv: "sh",
croatian: "sh",
hun: "hu",
hungarian: "hu",
hye: "hy",
armenian: "hy",
ibo: "ig",
igbo: "ig",
ido: "io",
iii: "ii",
sichuanyi: "ii",
iku: "iu",
inuktitut: "iu",
ile: "ie",
interlingue: "ie",
ina: "ia",
interlingua: "ia",
ind: "id",
indonesian: "id",
ipk: "ik",
inupiaq: "ik",
isl: "is",
icelandic: "is",
ita: "it",
italian: "it",
jav: "jv",
javanese: "jv",
jpn: "ja",
japanese: "ja",
kal: "kl",
kalaallisut: "kl",
kan: "kn",
kannada: "kn",
kas: "ks",
kashmiri: "ks",
kat: "ka",
georgian: "ka",
kau: "kr",
kanuri: "kr",
kaz: "kk",
kazakh: "kk",
khm: "km",
centralkhmer: "km",
kik: "ki",
kikuyu: "ki",
kin: "rw",
kinyarwanda: "rw",
kir: "ky",
kirghiz: "ky",
kom: "kv",
komi: "kv",
kon: "kg",
kongo: "kg",
kor: "ko",
korean: "ko",
ksh: "gmw-cfr",
kua: "kj",
kuanyama: "kj",
kv: "kpv",
lao: "lo",
lat: "la",
latin: "la",
lav: "lv",
latvian: "lv",
lim: "li",
limburgan: "li",
lin: "ln",
lingala: "ln",
lit: "lt",
lithuanian: "lt",
ltz: "lb",
luxembourgish: "lb",
lub: "lu",
lubakatanga: "lu",
lug: "lg",
ganda: "lg",
mah: "mh",
marshallese: "mh",
mal: "ml",
malayalam: "ml",
mar: "mr",
marathi: "mr",
chm: "mhr",
mari: "mhr",
mkd: "mk",
macedonian: "mk",
mlg: "mg",
malagasy: "mg",
mlt: "mt",
maltese: "mt",
mon: "mn",
mongolian: "mn",
mri: "mi",
maori: "mi",
msa: "ms",
malay: "ms",
mya: "my",
burmese: "my",
nau: "na",
nauru: "na",
hokkien: "nan-hbl",
hokkienese: "nan-hbl",
fujian: "nan-hbl",
fujianese: "nan-hbl",
hainam: "hnm",
hainan: "hnm",
hainanese: "hnm",
leizhou: "luh",
"zhx-lui": "luh",
teochew: "nan-tws",
teochewese: "nan-tws",
"zhx-teo": "nan-tws",
wep: "nds",
westphalian: "nds",
germanlowgerman: "nds",
dutchlowsaxon: "nds",
act: "nds",
achterhoeks: "nds",
drt: "nds",
drents: "nds",
gos: "nds",
gronings: "nds",
sdz: "nds",
sallands: "nds",
stl: "nds",
stellingwerfs: "nds",
twd: "nds",
twents: "nds",
vel: "nds",
veluws: "nds",
nav: "nv",
navajo: "nv",
nbl: "nr",
southndebele: "nr",
nde: "nd",
northndebele: "nd",
ndo: "ng",
ndonga: "ng",
nep: "ne",
nepali: "ne",
nld: "nl",
dutch: "nl",
flemish: "nl",
nno: "nn",
norwegiannynorsk: "nn",
nob: "nb",
norwegianbokmal: "nb",
nor: "no",
norwegian: "no",
nya: "ny",
nyanja: "ny",
oci: "oc",
occitan: "oc",
oji: "oj",
ojibwa: "oj",
ori: "or",
oriya: "or",
orm: "om",
oromo: "om",
oss: "os",
ossetian: "os",
pan: "pa",
panjabi: "pa",
pfl: "gmw-rfr",
pli: "pi",
pali: "pi",
pol: "pl",
polish: "pl",
por: "pt",
portuguese: "pt",
pus: "ps",
pushto: "ps",
que: "qu",
quechua: "qu",
roh: "rm",
romansh: "rm",
ron: "ro",
romanian: "ro",
rn: "rw",
run: "rw",
rundi: "rw",
rus: "ru",
russian: "ru",
sag: "sg",
sango: "sg",
san: "sa",
sanskrit: "sa",
sin: "si",
sinhala: "si",
slk: "sk",
slovak: "sk",
slv: "sl",
slovenian: "sl",
sme: "se",
northernsami: "se",
smo: "sm",
samoan: "sm",
sna: "sn",
shona: "sn",
snd: "sd",
sindhi: "sd",
som: "so",
somali: "so",
sot: "st",
southernsotho: "st",
spa: "es",
spanish: "es",
sqi: "sq",
albanian: "sq",
srd: "sc",
sardinian: "sc",
sr: "sh",
srp: "sh",
serbian: "sh",
ssw: "ss",
swati: "ss",
sun: "su",
sundanese: "su",
swa: "sw",
swahili: "sw",
swe: "sv",
swedish: "sv",
tah: "ty",
tahitian: "ty",
tam: "ta",
tamil: "ta",
tat: "tt",
tatar: "tt",
tel: "te",
telugu: "te",
tgk: "tg",
taishanese: "zhx-tai",
tajik: "tg",
tgl: "tl",
tagalog: "tl",
tha: "th",
thai: "th",
tir: "ti",
tigrinya: "ti",
ton: "to",
tonga: "to",
tsn: "tn",
tswana: "tn",
tso: "ts",
tsonga: "ts",
tuk: "tk",
turkmen: "tk",
tur: "tr",
turkish: "tr",
twi: "tw",
uig: "ug",
uighur: "ug",
ukr: "uk",
ukrainian: "uk",
urd: "ur",
urdu: "ur",
uzb: "uz",
uzbek: "uz",
ven: "ve",
venda: "ve",
vie: "vi",
vietnamese: "vi",
westflemish: "vls",
vol: "vo",
volapuk: "vo",
wln: "wa",
walloon: "wa",
wol: "wo",
wolof: "wo",
xho: "xh",
xhosa: "xh",
yid: "yi",
yiddish: "yi",
yor: "yo",
yoruba: "yo",
zbc: "lod",
zbe: "lod",
zbw: "lod",
zha: "za",
zhuang: "za",
zh: "cmn",
zho: "cmn",
chinese: "cmn",
zul: "zu",
zulu: "zu",
};
// }}}
var stripArabicDiacritics = { strip: "\u064B\u064C\u064D\u064E\u064F\u0650\u0651\u0652" };
var stripGraveAndAcute = { strip: "\u0300\u0301" };
var fullToHalfWidthNumbers = {
from: "0123456789",
to: "0123456789"
};
// These should reflect the replacements made in [[Module:languages]], but should not necessarily be equal.
var diacriticStrippers = {
ang: {
from: "ĀāǢǣĊċĒēĠġĪīŌōŪūȲȳ",
to: "AaÆæCcEeGgIiOoUuYy",
strip: "\u0304\u0307",
}, //macron and above dot
ar: stripArabicDiacritics,
aao: stripArabicDiacritics,
acm: stripArabicDiacritics,
acx: stripArabicDiacritics,
adf: stripArabicDiacritics,
aeb: stripArabicDiacritics,
afb: stripArabicDiacritics,
ajp: stripArabicDiacritics,
apc: stripArabicDiacritics,
apd: stripArabicDiacritics,
arq: stripArabicDiacritics,
ary: stripArabicDiacritics,
arz: stripArabicDiacritics,
fa: stripArabicDiacritics,
ps: stripArabicDiacritics,
sd: stripArabicDiacritics,
ur: stripArabicDiacritics,
chl: {
from: "ÁáÉéÍíÓóÚú",
to: "AaEeIiOoUu",
strip: "\u0304",
}, //acute accent
he: {
strip: "\u05B0\u05B1\u05B2\u05B3\u05B4\u05B5\u05B6\u05B7\u05B8\u05B9\u05BA\u05BB\u05BC\u05BD\u05BF\u05C1\u05C2",
from: "-'\"",
to: "־׳״",
},
la: {
from: "ĀāĒēĪīŌōŪūȲȳ",
to: "AaEeIiOoUuYy",
strip: "\u0304",
}, //macron
lt: {
from: "áãàéẽèìýỹñóõòúù",
to: "aaaeeeiyynooouu",
strip: "\u0340\u0301\u0303",
},
nci: {
from: "ĀāĒēĪīŌōŪūȲȳ",
to: "AaEeIiOoUu",
strip: "\u0304",
}, //macron
//strip ́ and ̀ on Cyrillic Slavic languages, Serbo-Croatian has a longer list
ru: stripGraveAndAcute,
uk: stripGraveAndAcute,
be: stripGraveAndAcute,
bg: stripGraveAndAcute,
orv: stripGraveAndAcute,
cu: stripGraveAndAcute,
rue: stripGraveAndAcute,
mk: stripGraveAndAcute,
sh: {
from: "ȀȁÀàȂȃÁáĀāȄȅÈèȆȇÉéĒēȈȉÌìȊȋÍíĪīȌȍÒòȎȏÓóŌōȐȑȒȓŔŕȔȕÙùȖȗÚúŪūѝӣ",
to: "AaAaAaAaAaEeEeEeEeEeIiIiIiIiIiOoOoOoOoOoRrRrRrUuUuUuUuUuии",
strip: "\u030F\u0300\u0311\u0301\u0304",
},
sl: {
from: "áÁàÀâÂȃȂȁȀéÉèÈêÊȇȆȅȄíÍìÌîÎȋȊȉȈóÓòÒôÔȏȎȍȌŕŔȓȒȑȐúÚùÙûÛȗȖȕȔệỆộỘẹẸọỌəł",
to: "aAaAaAaAaAeEeEeEeEeEiIiIiIiIiIoOoOoOoOoOrRrRrRuUuUuUuUuUeEoOeEoOel",
strip: "\u0301\u0300\u0302\u0311\u030f\u0323",
},
kk: stripGraveAndAcute,
ky: stripGraveAndAcute,
tg: stripGraveAndAcute,
sa: {
strip: "ः",
},
/** visarga **/
bo: {
strip: "།",
},
/** shad **/
tr: {
from: "ÂâÛû",
to: "AaUu",
strip: "\u0302",
},
ja: fullToHalfWidthNumbers,
cmn: fullToHalfWidthNumbers,
yue: fullToHalfWidthNumbers,
nan: fullToHalfWidthNumbers,
ko: fullToHalfWidthNumbers,
zu: {
strip_init_hyphen: true,
}
};
function stringOrNull(val) {
return typeof val == "string" ? val : null;
}
function makeMap(obj) {
var from = stringOrNull(obj.from);
var to = stringOrNull(obj.to);
var strip = stringOrNull(obj.strip);
var map = {};
if (from && to) {
for (var i = 0; i < from.length; i++) {
map[from.charAt(i)] = to.charAt(i);
}
}
if (strip) {
for (var ii = 0; ii < strip.length; ii++) {
map[strip.charAt(ii)] = "";
}
}
return map;
}
/** letters and number 1 are used instead of palochka **/
var palochkaCorrections = {
from: "Il1",
to: "ӏӏӏ"
};
//
var arabicYa = "ي"; // Arabic letter yāʾ (U+064A ARABIC LETTER YEH)
var arabicKaf = "ك"; // Arabic letter kāf (U+0643 ARABIC LETTER KAF)
var farsiYe = "ی"; // Persian letter ye (U+06CC ARABIC LETTER FARSI YEH)
var farsiKaf = "ک"; // Persian letter kâf (U+06A9 ARABIC LETTER KEHEH)
var alifMaqsura = "ى"; // Arabic letter ʾalif maqṣūra (U+0649 ARABIC LETTER ALEF MAKSURA)
/** commonly misspelled Persian letters (Arabic instead of Persian) (to be expanded) **/
var persianCorrections = {
from: arabicKaf + arabicYa + alifMaqsura,
to: farsiKaf + farsiYe + farsiYe,
};
// These replacements will be applied when a word is submitted.
var orthographicalCorrections = {
// Replace grave accents with acute accents for translations
// into Bulgarian per Wiktionary policy
bg: {
map: { "\u0300": "\u0301", "Ѐ": "Е́", "Ѝ": "И́", "ѐ": "е́", "ѝ": "и́" },
},
abq: palochkaCorrections,
ady: palochkaCorrections,
av: palochkaCorrections,
ce: palochkaCorrections,
dar: palochkaCorrections,
inh: palochkaCorrections,
kbd: palochkaCorrections,
lbe: palochkaCorrections,
lez: palochkaCorrections,
tab: palochkaCorrections,
/** Roman to Cyrillic**/
cv: {
from: "ĂăĔĕÇçŸÿ",
to: "ӐӑӖӗҪҫӲӳ",
},
kv: {
from: "ÖöIi",
to: "ӦӧІі",
},
koi: {
from: "ÖöIi",
to: "ӦӧІі",
},
kpv: {
from: "ÖöIi",
to: "ӦӧІі",
},
os: {
from: "Ææ",
to: "Ӕӕ",
},
/* Obsolete or incorrectly used letters */
mn: {
from: "ЄєѲѳЇїVv",
to: "ӨөӨөҮүҮү",
strip: "\u0300\u0301",
},
/* cedilla to comma below */
ro: {
from: "ŞŢşţ",
to: "ȘȚșț",
},
/** ʻ is a standard symbol in Uzbek but is often replaced with ' or ` **/
uz: {
from: "'`",
to: "ʻʻ",
},
fa: persianCorrections,
"fa-ira": persianCorrections,
"fa-cls": persianCorrections,
prs: persianCorrections,
ur: persianCorrections,
/** commonly misspelled Arabic letters (Persian instead of Arabic) (to be expanded) **/
ar: {
from: farsiKaf + farsiYe,
to: arabicKaf + arabicYa,
},
ota: {
from: farsiKaf + arabicYa + alifMaqsura,
to: arabicKaf + farsiYe + farsiYe,
},
ps: {
from: arabicKaf,
to: farsiKaf,
},
/** some letters are considered more standard (to be expanded) **/
cu: {
from: "ыѹ",
to: "ꙑу",
},
/** some letters are considered more standard (to be expanded) **/
orv: {
from: "ыѹ",
to: "ꙑу",
},
/** obsolete letters **/
ab: {
from: "ҔҕҦҧ",
to: "ӶӷԤԥ",
},
};
// Whether the language is one translations are often given in (it has an entry in the table above).
this.isCommon = function(lang) {
return Object.prototype.hasOwnProperty.call(metadata, lang);
};
//Returns true if a Wiktionary exists for the specified language
this.hasWiktionary = function(lang) {
if (metadata[lang] && (metadata[lang].haswikt || metadata[lang].wiktprefix))
return true;
};
//Returns the domain-name prefix of the Wiktionary for the specified language
this.getWiktionaryPrefix = function(lang) {
if (metadata[lang])
return metadata[lang].wiktprefix || (metadata[lang].haswikt && lang);
};
// Keep this in sync with ignore_caps in [[Modul:translations]].
this.ignoreCaps = { "ko": true };
// Calls the callback with a boolean indicating whether the specified language
// has a Wiktionary with the specified entry. The callback might be called
// synchronously, or it might be called asynchronously.
this.hasWiktionaryWithEntry = function(lang, title, callback) {
if (this.hasWiktionary(lang)) {
if (this.ignoreCaps[lang])
title = title.replace(/^\^/, "");
// Use this when we want to give up support for Internet Explorer 11.
// URL constructor and URLSearchParams are not supported in IE11.
// var url = new URL("https://" + this.getWiktionaryPrefix(lang) + ".wiktionary.org/w/api.php");
// url.search = new URLSearchParams({
// "format": "json",
// "formatversion": "2",
// "action": "query",
// "titles": title,
// "converttitles": 1,
// "origin": "*",
// });
var url = new URL("https://" + this.getWiktionaryPrefix(lang) + ".wiktionary.org/w/api.php");
url.searchParams.set("format", "json");
url.searchParams.set("formatversion", "2");
url.searchParams.set("action", "query");
url.searchParams.set("titles", title);
url.searchParams.set("converttitles", "1");
url.searchParams.set("origin", "*");
$.ajax({
type: 'GET',
url: url.toString(),
dataType: 'json',
headers: { 'Api-User-Agent': 'KuWiktTranslationAdderGadget/0.1 (https://ku.wiktionary.org/wiki/MediaWiki:Gadget-wikt.translation-adder.js)' },
}).done(function(data) {
var page = data && data.query && data.query.pages && data.query.pages[0];
callback(page instanceof Object ? !page.missing : false);
}).fail(function() {
// Unknown: the line gets {{W}}, without the interwiki link.
callback(false);
});
} else
callback(false);
};
//Given a language code return a default script code.
this.guessScript = function(lang) {
var scripts = (new ScriptUtils()).GetScriptsByLangCode(lang);
if (scripts && scripts.length > 0) return scripts[0];
else return false;
};
// In a given language, would we expect a translation of the title to have the capitalisation
// of word?
this.expectedCase = function(lang, title, word) {
if (metadata[lang] && metadata[lang].allowCaps)
return true;
if (title.substr(0, 1).toLowerCase() != title.substr(0, 1))
return true;
return word.substr(0, 1).toLowerCase() == word.substr(0, 1);
};
// en.wiktionary gender/number code -> ku.wiktionary code.
// The language's gender/number codes as ku.wiktionary writes them, or undefined.
this.getGenders = function(lang) {
if (metadata[lang] && metadata[lang].g)
return metadata[lang].g.slice();
};
//Returns a string of standard noun class numbers or an empty string
this.hasNounClasses = function(lang) {
if (metadata[lang])
return metadata[lang].nclass;
};
//Returns true if the specified lang uses optional vowels or diacritics
this.needsRawPageName = function(lang) {
if (metadata[lang])
return metadata[lang].alt && !diacriticStrippers[lang];
};
function applyMap(map, str) {
var input = str.split("");
var output = "";
for (var i = 0; i < input.length; i++) {
var char = input[i];
var repl = map[char];
output += repl ? repl : char;
}
return output;
}
this.applyOrthographicalCorrections = function(lang, word) {
word = word.replace('\xAD', ''); // remove soft hyphens
var corrections = orthographicalCorrections[lang];
if (corrections) {
var map = corrections.map ? corrections.map : makeMap(corrections);
return applyMap(map, word);
}
return word;
};
// Computes the raw page name by removing diacritics (for Latin, etc.)
this.computeRawPageName = function(lang, word) {
var stripper = diacriticStrippers[lang];
if (stripper) {
var map = makeMap(stripper);
var output = applyMap(map, word);
if (stripper.strip_init_hyphen && output.length > 0 && output.charAt(0) == '-')
output = output.substr(1);
return output;
}
};
// Calls [[Module:links]]'s 'remove_diacritics' method on word and rawPageName,
// and invokes callback with two arguments: (1) the *real* raw page name
// (superseding even rawPageName), and (2) whether this real raw page name
// needs to be explicitly specified in the wikitext (i.e., whether the raw page
// name computed from rawPageName is different from the one that would have been
// computed from just word).
this.retrieveRawPageName = function(lang, word, rawPageName, callback) {
var ents = {
'<': 'lt',
'&': 'amp',
'>': 'gt'
};
var temps = {
'|': '!',
'=': '=',
'{': '(',
'}': ')'
};
function encode(s) {
s = s || '';
s = s.replace(/[|={}]/g, function(c) {
return '{{' + temps[c] + '}}';
});
s = s.replace(/[<&>]/g, function(c) {
return '&' + ents[c] + ';';
});
s = s.replace(/\t/g, ' ');
//s = encodeURIComponent(s);
return s;
}
function removeDiacritics(s) {
return '{{#invoke:languages/templates|getByCodeAllowEtym|' + lang + '|makeEntryName|' + encode(s) + '}}';
}
var text = removeDiacritics(word) + '\t' + removeDiacritics(rawPageName || word);
$.ajax({
dataType: "json",
url: '/w/api.php',
data: {
format: 'json',
action: 'expandtemplates',
prop: 'wikitext',
text: text
},
success: function(data) {
data = data && data.expandtemplates && data.expandtemplates.wikitext;
if (/<!--/.test(data)) {
data = false;
}
if (data) {
data = data && data.split('\t');
var wordMinus = data[0];
var rawPageNameMinus = data[1];
callback(rawPageNameMinus || wordMinus, rawPageNameMinus != wordMinus);
} else {
callback(rawPageName || word, rawPageName && rawPageName != word);
}
},
error: function() {
callback(rawPageName || word, rawPageName && rawPageName != word);
}
});
};
//Given user input, return a language code. Normalises ISO 639-1 codes and names to 639-3.
this.cleanLangCode = function(lang) {
var key = lang.toLowerCase().replace(' ', '');
if (clean[key])
return clean[key];
else
return lang;
};
// Language -> the language or family its line goes under (* {{Z|zh}}: / *: {{Z|cmn}}: …), from en.wiktionary's
// nesting table with its group names turned into codes: a language's, else a family's from [[Modul:malbat]].
// Groups with neither are left out. Apache (apa), Berber (ber), Sardinian (sc),
// Guarani (gn) and Nenets (yrk) are as ku pages write them.
var nesting = {
"abh": "ar",
"abv": "ar",
"acm": "ar",
"acw": "ar",
"acx": "ar",
"acy": "ar",
"adf": "ar",
"aeb": "ar",
"aee": "inc-pas",
"afb": "ar",
"ahi": "kro-aiz",
"ahm": "kro-aiz",
"ahp": "kro-aiz",
"aii": "arc",
"aij": "arc",
"ajp": "ar",
"akb": "btk",
"amw": "arc",
"aou": "gio",
"apc": "ar",
"apd": "ar",
"apj": "apa",
"apk": "apa",
"apl": "apa",
"apm": "apa",
"apw": "apa",
"aqd": "qfa-dgn",
"arq": "ar",
"ars": "ar",
"ary": "ar",
"arz": "ar",
"atd": "mno",
"atj": "cr",
"auz": "ar",
"axm": "hy",
"ayl": "ar",
"ayn": "ar",
"ayp": "ar",
"azd": "nah",
"azn": "nah",
"azz": "nah",
"bar": "de",
"bbc": "btk",
"bca": "sit-bai",
"bet": "kro-bet",
"bev": "kro-bet",
"bfc": "sit-bai",
"bfs": "sit-bai",
"bgo": "alv-bag",
"bgx": "tr",
"bhn": "arc",
"bhv": "pdu",
"bjf": "arc",
"blk": "kar",
"bmd": "alv-bag",
"bpn": "zh",
"bpq": "ms",
"bpy": "mni",
"bqf": "alv-bag",
"bsp": "alv-bag",
"bsv": "alv-bag",
"btd": "btk",
"btg": "kro-bet",
"btj": "ms",
"btm": "btk",
"bts": "btk",
"btx": "btk",
"btz": "btk",
"bve": "ms",
"bvu": "ms",
"bwe": "kar",
"bya": "btk",
"caq": "aav-nic",
"cbj": "alv-ede",
"cdo": "zh",
"cib": "alv-gbe",
"cjy": "zh",
"cld": "arc",
"cly": "omq-cha",
"cmg": "mn",
"cmn": "zh",
"cnp": "zh",
"cnx": "kw",
"cpg": "el",
"cpx": "zh",
"crj": "cr",
"crk": "cr",
"crl": "cr",
"crm": "cr",
"crp-mpp": "pt",
"csp": "zh",
"csw": "cr",
"cta": "omq-cha",
"ctp": "omq-cha",
"ctz": "omq-cha",
"cut": "omq-cui",
"cux": "omq-cui",
"cwd": "cr",
"cya": "omq-cha",
"czh": "zh",
"czn": "omq-cha",
"czo": "zh",
"daq": "mds",
"dbg": "qfa-dgn",
"dbt": "qfa-dgn",
"dbu": "qfa-dgn",
"dbw": "qfa-dgn",
"dds": "qfa-dgn",
"dgb": "qfa-dgn",
"dib": "din",
"dic": "kro-did",
"dik": "din",
"dip": "din",
"diw": "din",
"djm": "qfa-dgn",
"dks": "din",
"dmb": "qfa-dgn",
"dng": "zh",
"doc": "kdx",
"dra-mkn": "kn",
"dra-okn": "kn",
"dra-ote": "te",
"dsb": "wen",
"dti": "qfa-dgn",
"dtk": "qfa-dgn",
"dtm": "qfa-dgn",
"dto": "qfa-dgn",
"dts": "qfa-dgn",
"dtt": "qfa-dgn",
"dtu": "qfa-dgn",
"dum": "nl",
"dym": "qfa-dgn",
"egx-dem": "egy",
"emu": "dra-mur",
"enc": "qfa-buy",
"fal": "fli",
"fll": "fli",
"fmu": "dra-mur",
"frm": "fr",
"fro": "fr",
"frr": "gmw-fri",
"fy": "gmw-fri",
"gan": "zh",
"gbh": "alv-gbe",
"gbo": "kro-grb",
"gbs": "alv-gbe",
"gbx": "alv-gbe",
"gec": "kro-grb",
"ggn": "gvr",
"ghk": "kar",
"gie": "kro-did",
"giq": "gio",
"gir": "gio",
"giu": "gio",
"giw": "gio",
"gkp": "kpe",
"glh": "inc-pas",
"gmh": "de",
"gml": "nds",
"gmq-mno": "no",
"gmq-oda": "da",
"gmq-ogt": "gmq-gut",
"gmq-osw": "sv",
"gmw-cfr": "de",
"gmw-ecg": "de",
"gmw-jdt": "nl",
"gmw-msc": "sco",
"gmw-rfr": "de",
"gmy": "el",
"gn-cls": "gn",
"gnw": "gn",
"goh": "de",
"gqu": "gio",
"grc": "el",
"grj": "kro-grb",
"grk-cal": "el",
"grk-ita": "el",
"grk-mar": "el",
"grv": "kro-grb",
"gry": "kro-grb",
"gsw": "de",
"gud": "kro-did",
"gug": "gn",
"gui": "gn",
"gun": "gn",
"gxx": "kro-wee",
"hak": "zh",
"hax": "hai",
"hdn": "hai",
"hnm": "zh",
"hrt": "arc",
"hsb": "wen",
"hsn": "zh",
"huy": "arc",
"ica": "alv-ede",
"idd": "alv-ede",
"ife": "alv-ede",
"ijj": "alv-ede",
"ike": "iu",
"inc-ash": "pra",
"inc-kam": "pra",
"inc-mas": "as",
"inc-mbn": "bn",
"inc-mgu": "gu",
"inc-mor": "or",
"inc-oas": "as",
"inc-oaw": "awa",
"inc-obn": "bn",
"inc-ogu": "gu",
"inc-ohi": "hi",
"inc-oor": "or",
"inc-opa": "pa",
"jas": "jv",
"jax": "ms",
"jiu": "tbq-jin",
"jiy": "tbq-jin",
"jkp": "kar",
"jmx": "omq-mxt",
"jvn": "jv",
"kab": "ber",
"kaw": "jv",
"kca-eas": "kca",
"kca-nor": "kca",
"kca-sou": "kca",
"kfd": "dra-kor",
"kgj": "sit-kha",
"kif": "sit-kha",
"kip": "sit-kha",
"kjl": "sit-kha",
"klg": "kqe",
"kll": "kqe",
"kmc": "kdx",
"kmz": "tr",
"ko-ear": "ko",
"kqd": "arc",
"kqk": "alv-gbe",
"kqs": "alv-kis",
"kss": "alv-kis",
"ksw": "kar",
"kvl": "kar",
"kvq": "kar",
"kvt": "kar",
"kvu": "kar",
"kvy": "kar",
"kxd": "ms",
"kxf": "kar",
"kxk": "kar",
"kxm": "km",
"kza": "alv-krb",
"kzc": "alv-kul",
"lay": "sit-bai",
"lhs": "arc",
"lia": "alv-lim",
"lma": "alv-lim",
"lrt": "ms",
"lsd": "arc",
"ltc": "zh",
"luh": "zh",
"lzh": "zh",
"maa": "omq-maz",
"mab": "omq-mxt",
"maj": "omq-maz",
"maq": "omq-maz",
"mau": "omq-maz",
"mbb": "mno",
"mbd": "mno",
"mbi": "mno",
"mbs": "mno",
"mbt": "mno",
"mbz": "omq-mxt",
"mce": "omq-mxt",
"mdv": "omq-mxt",
"meh": "omq-mxt",
"meo": "ms",
"mey": "ar",
"mfa": "ms",
"mga": "ga",
"mhp": "ms",
"mhr": "chm",
"mib": "omq-mxt",
"mid": "arc",
"mie": "omq-mxt",
"mig": "omq-mxt",
"mih": "omq-mxt",
"mii": "omq-mxt",
"mil": "omq-mxt",
"mim": "omq-mxt",
"mio": "omq-mxt",
"mip": "omq-mxt",
"mis-hkl": "zh",
"mis-tnw": "zh",
"mit": "omq-mxt",
"miu": "omq-mxt",
"mix": "omq-mxt",
"miy": "omq-mxt",
"miz": "omq-mxt",
"mjc": "omq-mxt",
"mkh-mmn": "mnw",
"mkh-mvi": "vi",
"mkn": "ms",
"mks": "omq-mxt",
"mku": "man",
"mkx": "mno",
"mnk": "man",
"mnp": "zh",
"mns-cen": "mns",
"mns-nor": "mns",
"mns-sou": "mns",
"mnw-tha": "mnw",
"moe": "cr",
"mpm": "omq-mxt",
"mqg": "ms",
"mqh": "omq-mxt",
"mqk": "mno",
"mrj": "chm",
"mrr": "mds",
"msc": "man",
"msi": "ms",
"msm": "mno",
"mta": "mno",
"mtu": "omq-mxt",
"mtx": "omq-mxt",
"mut": "dra-mur",
"mvg": "omq-mxt",
"mxa": "omq-mxt",
"mxb": "omq-mxt",
"mxl": "alv-gbe",
"mxs": "omq-mxt",
"mxt": "omq-mxt",
"mxv": "omq-mxt",
"mxy": "omq-mxt",
"myz": "arc",
"mza": "omq-mxt",
"mzi": "omq-maz",
"nan": "zh",
"nan-dat": "zh",
"nan-hbl": "zh",
"nan-hlh": "zh",
"nan-lnx": "zh",
"nan-tws": "zh",
"nan-zhe": "zh",
"nan-zsh": "zh",
"naz": "nah",
"nb": "no",
"nbw": "nic-ngd",
"ncb": "aav-nic",
"nch": "nah",
"nci": "nah",
"ncj": "nah",
"ncl": "nah",
"ncx": "nah",
"nds-de": "nds",
"nds-nl": "nds",
"ngb": "nic-ngd",
"ngu": "nah",
"nhc": "nah",
"nhe": "nah",
"nhg": "nah",
"nhi": "nah",
"nhk": "nah",
"nhm": "nah",
"nhn": "nah",
"nhp": "nah",
"nhq": "nah",
"nht": "nah",
"nhv": "nah",
"nhw": "nah",
"nhx": "nah",
"nhy": "nah",
"nhz": "nah",
"nik": "aav-nic",
"nku": "alv-kul",
"nlv": "nah",
"nn": "no",
"nod": "th",
"nos": "tbq-nis",
"npl": "nah",
"nqg": "alv-ede",
"nqk": "alv-ede",
"nrt": "kyl",
"nsd": "tbq-nis",
"nsk": "cr",
"nsl": "no",
"nsu": "nah",
"nsv": "tbq-nis",
"nuz": "nah",
"nwc": "new",
"nwx": "new",
"nzz": "qfa-dgn",
"oav": "av",
"obo": "mno",
"obr": "my",
"obt": "br",
"och": "zh",
"oco": "kw",
"odt": "nl",
"ofs": "gmw-fri",
"oge": "ka",
"ohu": "hu",
"ojp": "ja",
"okm": "ko",
"oko": "ko",
"okz": "km",
"olt": "lt",
"omp": "mni",
"omq-sjq": "omq-cha",
"omq-tel": "omq-mxt",
"omq-teo": "omq-cha",
"omr": "mr",
"omx": "mnw",
"ort": "or",
"osn": "su",
"osp": "es",
"ota": "tr",
"otb": "bo",
"ote": "oto-otm",
"otl": "oto-otm",
"otm": "oto-otm",
"otn": "oto-otm",
"otq": "oto-otm",
"ots": "oto-otm",
"ott": "oto-otm",
"otx": "oto-otm",
"oty": "ta",
"otz": "oto-otm",
"owl": "cy",
"pal": "fa",
"pbm": "omq-maz",
"peo": "fa",
"pga": "ar",
"pgl": "ga",
"pht": "th",
"pmu": "pa",
"pmy": "ms",
"pnt": "el",
"poz-sml": "ms",
"poz-ter": "ms",
"pra-niy": "pra",
"pro": "oc",
"pse": "ms",
"psh": "inc-pas",
"psi": "inc-pas",
"qwc": "qu",
"rif": "ber",
"rmc": "rom",
"rmf": "rom",
"rml": "rom",
"rmn": "rom",
"rmo": "rom",
"rmu": "rom",
"rmw": "rom",
"rmy": "rom",
"roa-oca": "ca",
"roa-ole": "roa-leo",
"sam": "arc",
"sbd": "smq",
"scs": "den",
"sdc": "sc",
"sdn": "sc",
"se": "smi",
"sel-nor": "sel",
"sel-sou": "sel",
"sga": "ga",
"shi": "ber",
"shu": "ar",
"shy": "ber",
"sia": "smi",
"sjc": "zh",
"sjd": "smi",
"sje": "smi",
"sjk": "smi",
"sjt": "smi",
"sju": "smi",
"slm": "smd",
"sma": "smi",
"smj": "smi",
"sml": "smd",
"smn": "smi",
"smp": "he",
"sms": "smi",
"sou": "th",
"ssb": "smd",
"ssh": "ar",
"stj": "smq",
"stq": "gmw-fri",
"swb": "bnt-com",
"swg": "de",
"sxk": "kyl",
"sxw": "alv-gbe",
"syc": "arc",
"syd-fne": "yrk",
"sym": "smq",
"syn": "arc",
"tde": "qfa-dgn",
"tfi": "alv-gbe",
"the": "inc-tha",
"thl": "inc-tha",
"thq": "inc-tha",
"thr": "inc-tha",
"tji": "sit-tja",
"tjs": "sit-tja",
"tkt": "inc-tha",
"trc": "omq-tri",
"trg": "arc",
"trk-oat": "tr",
"trq": "omq-tri",
"trs": "omq-tri",
"tru": "arc",
"tsd": "el",
"tzm": "ber",
"vkp": "pt",
"vkt": "ms",
"vmc": "omq-mxt",
"vmd": "dra-kor",
"vmf": "de",
"vmj": "omq-mxt",
"vmm": "omq-mxt",
"vmp": "omq-maz",
"vmq": "omq-mxt",
"vmx": "omq-mxt",
"vmy": "omq-maz",
"vmz": "omq-maz",
"wci": "alv-gbe",
"wec": "kro-wee",
"wem": "alv-gbe",
"wlc": "bnt-com",
"wlm": "cy",
"wni": "bnt-com",
"wuu": "zh",
"wxa": "zh",
"xaa": "ar",
"xbm": "br",
"xcl": "hy",
"xct": "bo",
"xhm": "km",
"xkb": "alv-ede",
"xme-mid": "xme",
"xme-old": "xme",
"xmm": "ms",
"xpe": "kpe",
"xrb": "alv-krb",
"xsl": "den",
"xta": "omq-mxt",
"xtb": "omq-mxt",
"xtd": "omq-mxt",
"xti": "omq-mxt",
"xtj": "omq-mxt",
"xtl": "omq-mxt",
"xtm": "omq-mxt",
"xtn": "omq-mxt",
"xtp": "omq-mxt",
"xts": "omq-mxt",
"xtt": "omq-mxt",
"xtu": "omq-mxt",
"xty": "omq-mxt",
"xwe": "alv-gbe",
"xwl": "alv-gbe",
"xzp": "zap",
"yej": "el",
"yhl": "tbq-phw",
"yik": "tbq-lal",
"yit": "tbq-lal",
"yiv": "tbq-nis",
"ykg": "qfa-yuk",
"yln": "qfa-buy",
"ymc": "tbq-muj",
"ymq": "tbq-muj",
"ymx": "tbq-muj",
"yok-bvy": "yok",
"yok-dly": "yok",
"yok-gsy": "yok",
"yok-kry": "yok",
"yok-nvy": "yok",
"yok-ply": "yok",
"yok-svy": "yok",
"yok-tky": "yok",
"ypb": "tbq-phw",
"ypn": "tbq-phw",
"yrn": "qfa-buy",
"yue": "zh",
"yux": "qfa-yuk",
"ywl": "tbq-lal",
"ywt": "tbq-lal",
"yzg": "qfa-buy",
"zaa": "zap",
"zab": "zap",
"zac": "zap",
"zad": "zap",
"zae": "zap",
"zaf": "zap",
"zai": "zap",
"zam": "zap",
"zao": "zap",
"zaq": "zap",
"zar": "zap",
"zas": "zap",
"zat": "zap",
"zav": "zap",
"zaw": "zap",
"zax": "zap",
"zca": "zap",
"zdj": "bnt-com",
"zhx-sht": "zh",
"zhx-sic": "zh",
"zhx-tai": "zh",
"zls-chs": "cu",
"zlw-ocs": "cs",
"zlw-opl": "pl",
"zlw-osk": "sk",
"zmi": "ms",
"zoo": "zap",
"zpa": "zap",
"zpb": "zap",
"zpc": "zap",
"zpd": "zap",
"zpe": "zap",
"zpf": "zap",
"zpg": "zap",
"zph": "zap",
"zpi": "zap",
"zpj": "zap",
"zpk": "zap",
"zpl": "zap",
"zpm": "zap",
"zpn": "zap",
"zpo": "zap",
"zpp": "zap",
"zpq": "zap",
"zpr": "zap",
"zps": "zap",
"zpt": "zap",
"zpu": "zap",
"zpv": "zap",
"zpw": "zap",
"zpx": "zap",
"zpy": "zap",
"zpz": "zap",
"zsr": "zap",
"zte": "zap",
"ztg": "zap",
"ztl": "zap",
"ztm": "zap",
"ztn": "zap",
"ztp": "zap",
"ztq": "zap",
"zts": "zap",
"ztt": "zap",
"ztu": "zap",
"ztx": "zap",
"zty": "zap"
};
// Kurmancî names of the families in nesting, from [[Modul:malbat/code to canonical name]].
var groupNames = {
"aav-nic": "nîkobarî",
"alv-bag": "baga",
"alv-ede": "ede",
"alv-gbe": "gbe",
"alv-kis": "kissî",
"alv-krb": "karaboro",
"alv-kul": "kulango",
"alv-lim": "lîmba",
"apa": "apaçî",
"ber": "berberî",
"bnt-com": "komorî",
"btk": "batakî",
"chm": "marî",
"den": "slavey",
"dra-kor": "koraga",
"dra-mur": "muriya",
"gio": "gelaoyî",
"gmw-fri": "frîsî",
"inc-pas": "paşayî",
"inc-tha": "tharu",
"kar": "karenî",
"kca": "xantî",
"kro-aiz": "ayizî",
"kro-bet": "bétéyî",
"kro-did": "didayî",
"kro-grb": "greboyî",
"kro-wee": "wiyî",
"mno": "manoboyî",
"mns": "mansî",
"nic-ngd": "nîgbandî",
"omq-cha": "çatînoyî",
"omq-cui": "kuyikatekî",
"omq-maz": "mazatekanî",
"omq-mxt": "mîkstekî",
"omq-tri": "trîkiyî",
"oto-otm": "otomî",
"qfa-buy": "buyangî",
"qfa-dgn": "dogonî",
"qfa-yuk": "yukaghirî",
"sel": "selkupî",
"sit-bai": "bayî",
"sit-kha": "khamî",
"sit-tja": "tujya",
"smi": "samîkî",
"tbq-jin": "jîno",
"tbq-lal": "laloyî",
"tbq-muj": "mujî",
"tbq-nis": "nîsu",
"tbq-phw": "phowa",
"wen": "sorbî",
"xme": "medî",
"yok": "yokutsanî"
};
// The code of the language this one is nested under, or "".
this.getNested = function(lang) {
return nesting[lang] || "";
};
// The Kurmancî name of a family that languages are nested under, or undefined.
this.getGroupName = function(code) {
return groupNames[code];
};
// For enforcing an ordering on nested languages.
this.nestsBefore = function(a, b) {
return window.wikt.translationAdder.compareNames(a, b) < 0;
};
this.getScripts = function(lang) {
var scripts = (new window.ScriptUtils()).GetScriptsByLangCode(lang) || [];
return scripts;
};
};
//</nowiki>
583yv4atokp7e9eesz3o1eoasi61ojp
MediaWiki:Gadget-wikt.translation-adder.css
8
1359500
6865550
6865465
2026-10-23T20:09:05Z
Wikihez
7344
Language field: wikt.edit's shared dropdown (same matching and names as before)
6865550
css
text/css
/* Page: [[MediaWiki:Gadget-wikt.translation-adder.css]] */
.translation-adder-row label {
margin-right: 0.8em;
white-space: nowrap;
}
.translation-adder-row input[type="checkbox"] {
margin-right: 0.2em;
}
.translation-adder-langname {
color: var(--color-subtle, #54595d);
}
.translation-adder-langbox {
display: inline-block;
}
/* Lines added or changed since the last save. */
.translation-adder-new {
background-color: var(--background-color-success-subtle, #dff2eb);
outline: 2px dashed var(--border-color-success, #14866d);
outline-offset: 1px;
}
/* Lines that the next save removes. */
.translation-adder-removed {
text-decoration: line-through;
background-color: var(--background-color-error-subtle, #ffe9e5);
outline-color: var(--border-color-error, #d73333);
}
.translation-adder-line-tools a {
padding: 0 2px;
font-size: 90%;
}
.translation-adder-term-edit {
padding: 0 2px;
font-size: 90%;
}
/* Codex icons from Commons (Category:Wikimedia Codex icons), in the progressive colour of the links. */
.translation-adder-term-pencil,
.translation-adder-term-close {
display: inline-block;
width: 0.8em;
height: 0.8em;
margin: 0 2px;
padding: 0;
vertical-align: -0.05em;
background-repeat: no-repeat;
background-position: center;
background-size: contain;
}
.translation-adder-term-pencil {
background-image: url(//upload.wikimedia.org/wikipedia/commons/1/1c/Codex_icon_edit_color-progressive.svg);
}
.translation-adder-term-close {
background-image: url(//upload.wikimedia.org/wikipedia/commons/3/39/Codex_icon_close_color-progressive.svg);
}
.translation-adder-wrap > li {
list-style: none;
}
hnfq4mfi9xgi68rvga5n3ihh86aqzsq
Şablon:tabloya IPA ya kurmancî/styles.css
10
1359504
6865501
2026-10-23T13:19:30Z
Wikihez
7344
Interactive Kurmancî IPA chart (data from Wîkîferheng:IPA kurmancî)
6865501
sanitized-css
text/css
/* Kurmancî IPA chart: vowel trapezoid, consonant table and Phonos buttons */
.ku-ipa {
max-width: 56em;
margin: 0.5em 0 1em;
font-size: 95%;
}
.ku-ipa-help,
.ku-ipa-part {
margin-bottom: 1em;
padding: 0.5em 0.8em;
border: 1px solid var( --border-color-success, #14866d );
background-color: var( --background-color-success-subtle, #dff2eb );
color: var( --color-base, #202122 );
}
.ku-ipa-part {
background-color: transparent;
border: 1px solid var( --border-color-subtle, #c8ccd1 );
}
.ku-ipa-title {
font-size: 125%;
text-align: center;
}
.ku-ipa-sub {
font-size: 85%;
text-align: center;
color: var( --color-subtle, #54595d );
margin-bottom: 0.5em;
}
.ku-ipa-scroll {
overflow-x: auto;
}
/* Sound buttons */
.ku-ipa .ext-phonos {
white-space: nowrap;
}
.ku-ipa .ext-phonos-attribution,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-iconElement-icon,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-indicatorElement-indicator {
display: none;
}
.ku-ipa .ext-phonos-PhonosButton {
margin: 1px;
vertical-align: middle;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-nofile {
display: inline-flex;
flex-direction: column;
align-items: center;
min-width: 1.7em;
min-height: 0 !important;
padding: 1px 3px !important;
border: 1px solid var( --border-color-success, #14866d ) !important;
border-radius: 2px;
background-color: var( --background-color-success-subtle, #dff2eb ) !important;
color: var( --color-base, #202122 ) !important;
line-height: 1.15;
font-weight: normal;
box-sizing: border-box;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:hover,
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:focus {
background-color: var( --background-color-success, #14866d ) !important;
color: var( --color-inverted-fixed, #fff ) !important;
}
.ku-ipa .ext-phonos-PhonosButton .oo-ui-labelElement-label {
margin: 0 !important;
display: flex;
flex-direction: column;
align-items: center;
}
.ku-ipa-nofile {
margin: 1px;
vertical-align: middle;
border-style: dashed !important;
background-color: transparent !important;
}
.ku-ipa-sym {
font-size: 125%;
}
.ku-ipa-let {
font-size: 70%;
opacity: 0.8;
}
.ku-ipa-let:empty {
display: none;
}
.ku-ipa-sdh .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-sdh .ku-ipa-nofile {
border-style: dashed !important;
}
/* Vowel trapezoid: 4 units wide, 3 units high, 70px a unit */
.ku-ipa-trap {
position: relative;
width: 280px;
height: 210px;
margin: 40px 50px 30px 140px;
}
.ku-ipa-line {
position: absolute;
height: 0;
border-top: 1px solid var( --color-base, #202122 );
transform-origin: 0 0;
}
.ku-ipa-h0 { left: 0; top: 0; width: 280px; }
.ku-ipa-h1 { left: 46.67px; top: 70px; width: 233.33px; }
.ku-ipa-h2 { left: 93.33px; top: 140px; width: 186.67px; }
.ku-ipa-h3 { left: 140px; top: 210px; width: 140px; }
.ku-ipa-back { left: 280px; top: 0; width: 210px; transform: rotate( 90deg ); }
.ku-ipa-front { left: 0; top: 0; width: 252.39px; transform: rotate( 56.31deg ); }
.ku-ipa-central { left: 140px; top: 0; width: 221.36px; transform: rotate( 71.57deg ); }
.ku-ipa-col,
.ku-ipa-row {
position: absolute;
font-size: 85%;
color: var( --color-subtle, #54595d );
white-space: nowrap;
}
.ku-ipa-col { top: -36px; transform: translateX( -50% ); }
.ku-ipa-x0 { left: 0; }
.ku-ipa-x2 { left: 140px; }
.ku-ipa-x4 { left: 280px; }
.ku-ipa-row { left: -135px; transform: translateY( -50% ); }
.ku-ipa-y0 { top: 0; }
.ku-ipa-y1 { top: 70px; }
.ku-ipa-y2 { top: 140px; }
.ku-ipa-y3 { top: 210px; }
.ku-ipa-pt {
position: absolute;
width: 0;
height: 0;
}
.ku-ipa-pt::before {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var( --color-base, #202122 );
}
.ku-ipa-near-close-back::before {
display: none;
}
.ku-ipa-l,
.ku-ipa-r,
.ku-ipa-c {
position: absolute;
top: 0;
display: flex;
white-space: nowrap;
z-index: 1;
}
.ku-ipa-l { right: 4px; transform: translateY( -50% ); }
.ku-ipa-r { left: 4px; transform: translateY( -50% ); }
.ku-ipa-c { left: 0; transform: translate( -50%, -50% ); }
.ku-ipa-close-front { left: 0; top: 0; }
.ku-ipa-close-back { left: 280px; top: 0; }
.ku-ipa-near-close-back { left: 236px; top: 35px; }
.ku-ipa-close-mid-front { left: 46.67px; top: 70px; }
.ku-ipa-close-mid-central { left: 163.33px; top: 70px; }
.ku-ipa-close-mid-back { left: 280px; top: 70px; }
.ku-ipa-open-mid-front { left: 93.33px; top: 140px; }
.ku-ipa-open-back { left: 280px; top: 210px; }
/* Consonant table */
.ku-ipa-cons {
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.ku-ipa-cons th,
.ku-ipa-cons td {
border: 1px solid var( --border-color-subtle, #c8ccd1 );
padding: 2px;
}
.ku-ipa-cons th {
font-size: 80%;
font-weight: normal;
background-color: var( --background-color-success-subtle, #dff2eb );
}
.ku-ipa-cons tr > th:first-child {
text-align: right;
white-space: nowrap;
}
.ku-ipa-cons td {
min-width: 4.5em;
vertical-align: middle;
}
.ku-ipa-cons td::after {
content: '';
display: table;
clear: both;
}
.ku-ipa-vl { float: left; }
.ku-ipa-vd { float: right; }
.ku-ipa-cons td.ku-ipa-no {
background-color: var( --background-color-disabled, #c8ccd1 );
}
/* Affricates, other symbols and notes */
.ku-ipa-boxes {
display: flex;
flex-wrap: wrap;
gap: 0 1em;
}
.ku-ipa-boxes > .ku-ipa-part {
flex: 1 1 18em;
}
.ku-ipa-list {
margin: 0 auto;
}
.ku-ipa-list td {
padding: 2px 6px;
vertical-align: middle;
}
.ku-ipa-mark {
display: inline-block;
min-width: 1.7em;
text-align: center;
font-size: 125%;
}
.ku-ipa-notes {
font-size: 85%;
}
.ku-ipa-key {
margin-right: 0.3em;
}
royqymwayzlzcqbez44kk5ki6nvyksp
6865504
6865501
2026-10-23T13:21:54Z
Wikihez
7344
IPA chart: hide Phonos icons, stack letters under symbols
6865504
sanitized-css
text/css
/* Kurmancî IPA chart: vowel trapezoid, consonant table and Phonos buttons */
.ku-ipa {
max-width: 56em;
margin: 0.5em 0 1em;
font-size: 95%;
}
.ku-ipa-help,
.ku-ipa-part {
margin-bottom: 1em;
padding: 0.5em 0.8em;
border: 1px solid var( --border-color-success, #14866d );
background-color: var( --background-color-success-subtle, #dff2eb );
color: var( --color-base, #202122 );
}
.ku-ipa-part {
background-color: transparent;
border: 1px solid var( --border-color-subtle, #c8ccd1 );
}
.ku-ipa-title {
font-size: 125%;
text-align: center;
}
.ku-ipa-sub {
font-size: 85%;
text-align: center;
color: var( --color-subtle, #54595d );
margin-bottom: 0.5em;
}
.ku-ipa-scroll {
overflow-x: auto;
}
/* Sound buttons */
.ku-ipa .ext-phonos {
white-space: nowrap;
}
.ku-ipa .ext-phonos-attribution,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-iconElement-icon,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-indicatorElement-indicator {
/* Phonos sets display:inline !important on everything inside it */
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton {
margin: 1px;
vertical-align: middle;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-nofile {
display: inline-flex !important;
flex-direction: column;
align-items: center;
min-width: 1.7em;
min-height: 0 !important;
padding: 1px 3px !important;
border: 1px solid var( --border-color-success, #14866d ) !important;
border-radius: 2px;
background-color: var( --background-color-success-subtle, #dff2eb ) !important;
color: var( --color-base, #202122 ) !important;
line-height: 1.15;
font-weight: normal;
box-sizing: border-box;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:hover,
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:focus {
background-color: var( --background-color-success, #14866d ) !important;
color: var( --color-inverted-fixed, #fff ) !important;
}
.ku-ipa .ext-phonos-PhonosButton .oo-ui-labelElement-label {
margin: 0 !important;
display: flex !important;
flex-direction: column;
align-items: center;
}
.ku-ipa-nofile {
margin: 1px;
vertical-align: middle;
border-style: dashed !important;
background-color: transparent !important;
}
.ku-ipa-sym {
font-size: 125%;
}
.ku-ipa-let {
font-size: 70%;
opacity: 0.8;
}
.ku-ipa-let:empty {
display: none !important;
}
.ku-ipa-sdh .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-sdh .ku-ipa-nofile {
border-style: dashed !important;
}
/* Vowel trapezoid: 4 units wide, 3 units high, 70px a unit */
.ku-ipa-trap {
position: relative;
width: 280px;
height: 210px;
margin: 40px 50px 30px 140px;
}
.ku-ipa-line {
position: absolute;
height: 0;
border-top: 1px solid var( --color-base, #202122 );
transform-origin: 0 0;
}
.ku-ipa-h0 { left: 0; top: 0; width: 280px; }
.ku-ipa-h1 { left: 46.67px; top: 70px; width: 233.33px; }
.ku-ipa-h2 { left: 93.33px; top: 140px; width: 186.67px; }
.ku-ipa-h3 { left: 140px; top: 210px; width: 140px; }
.ku-ipa-back { left: 280px; top: 0; width: 210px; transform: rotate( 90deg ); }
.ku-ipa-front { left: 0; top: 0; width: 252.39px; transform: rotate( 56.31deg ); }
.ku-ipa-central { left: 140px; top: 0; width: 221.36px; transform: rotate( 71.57deg ); }
.ku-ipa-col,
.ku-ipa-row {
position: absolute;
font-size: 85%;
color: var( --color-subtle, #54595d );
white-space: nowrap;
}
.ku-ipa-col { top: -36px; transform: translateX( -50% ); }
.ku-ipa-x0 { left: 0; }
.ku-ipa-x2 { left: 140px; }
.ku-ipa-x4 { left: 280px; }
.ku-ipa-row { left: -135px; transform: translateY( -50% ); }
.ku-ipa-y0 { top: 0; }
.ku-ipa-y1 { top: 70px; }
.ku-ipa-y2 { top: 140px; }
.ku-ipa-y3 { top: 210px; }
.ku-ipa-pt {
position: absolute;
width: 0;
height: 0;
}
.ku-ipa-pt::before {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var( --color-base, #202122 );
}
.ku-ipa-near-close-back::before {
display: none;
}
.ku-ipa-l,
.ku-ipa-r,
.ku-ipa-c {
position: absolute;
top: 0;
display: flex;
white-space: nowrap;
z-index: 1;
}
.ku-ipa-l { right: 4px; transform: translateY( -50% ); }
.ku-ipa-r { left: 4px; transform: translateY( -50% ); }
.ku-ipa-c { left: 0; transform: translate( -50%, -50% ); }
.ku-ipa-close-front { left: 0; top: 0; }
.ku-ipa-close-back { left: 280px; top: 0; }
.ku-ipa-near-close-back { left: 236px; top: 35px; }
.ku-ipa-close-mid-front { left: 46.67px; top: 70px; }
.ku-ipa-close-mid-central { left: 163.33px; top: 70px; }
.ku-ipa-close-mid-back { left: 280px; top: 70px; }
.ku-ipa-open-mid-front { left: 93.33px; top: 140px; }
.ku-ipa-open-back { left: 280px; top: 210px; }
/* Consonant table */
.ku-ipa-cons {
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.ku-ipa-cons th,
.ku-ipa-cons td {
border: 1px solid var( --border-color-subtle, #c8ccd1 );
padding: 2px;
}
.ku-ipa-cons th {
font-size: 80%;
font-weight: normal;
background-color: var( --background-color-success-subtle, #dff2eb );
}
.ku-ipa-cons tr > th:first-child {
text-align: right;
white-space: nowrap;
}
.ku-ipa-cons td {
min-width: 4.5em;
vertical-align: middle;
}
.ku-ipa-cons td::after {
content: '';
display: table;
clear: both;
}
.ku-ipa-vl { float: left; }
.ku-ipa-vd { float: right; }
.ku-ipa-cons td.ku-ipa-no {
background-color: var( --background-color-disabled, #c8ccd1 );
}
/* Affricates, other symbols and notes */
.ku-ipa-boxes {
display: flex;
flex-wrap: wrap;
gap: 0 1em;
}
.ku-ipa-boxes > .ku-ipa-part {
flex: 1 1 18em;
}
.ku-ipa-list {
margin: 0 auto;
}
.ku-ipa-list td {
padding: 2px 6px;
vertical-align: middle;
}
.ku-ipa-mark {
display: inline-block;
min-width: 1.7em;
text-align: center;
font-size: 125%;
}
.ku-ipa-notes {
font-size: 85%;
}
.ku-ipa-key {
margin-right: 0.3em;
}
p4s36y3nobbbjb4zu4eg9u2cstu8ov0
6865505
6865504
2026-10-23T13:23:17Z
Wikihez
7344
IPA chart: hide the site speaker mark, keep voiced symbols on the right
6865505
sanitized-css
text/css
/* Kurmancî IPA chart: vowel trapezoid, consonant table and Phonos buttons */
.ku-ipa {
margin: 0.5em 0 1em;
font-size: 95%;
}
.ku-ipa-help,
.ku-ipa-part {
margin-bottom: 1em;
padding: 0.5em 0.8em;
border: 1px solid var( --border-color-success, #14866d );
background-color: var( --background-color-success-subtle, #dff2eb );
color: var( --color-base, #202122 );
}
.ku-ipa-part {
background-color: transparent;
border: 1px solid var( --border-color-subtle, #c8ccd1 );
}
.ku-ipa-title {
font-size: 125%;
text-align: center;
}
.ku-ipa-sub {
font-size: 85%;
text-align: center;
color: var( --color-subtle, #54595d );
margin-bottom: 0.5em;
}
.ku-ipa-scroll {
overflow-x: auto;
}
/* Sound buttons */
.ku-ipa .ext-phonos {
white-space: nowrap;
}
.ku-ipa .ext-phonos-attribution,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-iconElement-icon,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-indicatorElement-indicator {
/* Phonos sets display:inline !important on everything inside it */
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton {
margin: 1px;
vertical-align: middle;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-nofile {
display: inline-flex !important;
flex-direction: column;
align-items: center;
min-width: 1.7em;
min-height: 0 !important;
padding: 1px 3px !important;
border: 1px solid var( --border-color-success, #14866d ) !important;
border-radius: 2px;
background-color: var( --background-color-success-subtle, #dff2eb ) !important;
color: var( --color-base, #202122 ) !important;
line-height: 1.15;
font-weight: normal;
box-sizing: border-box;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:hover,
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:focus {
background-color: var( --background-color-success, #14866d ) !important;
color: var( --color-inverted-fixed, #fff ) !important;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button::after {
/* Site CSS adds a speaker mark after audio links */
content: none !important;
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton .oo-ui-labelElement-label {
margin: 0 !important;
display: flex !important;
flex-direction: column;
align-items: center;
}
.ku-ipa-nofile {
margin: 1px;
vertical-align: middle;
border-style: dashed !important;
background-color: transparent !important;
}
.ku-ipa-sym {
font-size: 125%;
}
.ku-ipa-let {
font-size: 70%;
opacity: 0.8;
}
.ku-ipa-let:empty {
display: none !important;
}
.ku-ipa-sdh .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-sdh .ku-ipa-nofile {
border-style: dashed !important;
}
/* Vowel trapezoid: 4 units wide, 3 units high, 70px a unit */
.ku-ipa-trap {
position: relative;
width: 280px;
height: 210px;
margin: 70px 50px 40px 140px;
}
.ku-ipa-line {
position: absolute;
height: 0;
border-top: 1px solid var( --color-base, #202122 );
transform-origin: 0 0;
}
.ku-ipa-h0 { left: 0; top: 0; width: 280px; }
.ku-ipa-h1 { left: 46.67px; top: 70px; width: 233.33px; }
.ku-ipa-h2 { left: 93.33px; top: 140px; width: 186.67px; }
.ku-ipa-h3 { left: 140px; top: 210px; width: 140px; }
.ku-ipa-back { left: 280px; top: 0; width: 210px; transform: rotate( 90deg ); }
.ku-ipa-front { left: 0; top: 0; width: 252.39px; transform: rotate( 56.31deg ); }
.ku-ipa-central { left: 140px; top: 0; width: 221.36px; transform: rotate( 71.57deg ); }
.ku-ipa-col,
.ku-ipa-row {
position: absolute;
font-size: 85%;
color: var( --color-subtle, #54595d );
white-space: nowrap;
}
.ku-ipa-col { top: -62px; transform: translateX( -50% ); }
.ku-ipa-x0 { left: 0; }
.ku-ipa-x2 { left: 140px; }
.ku-ipa-x4 { left: 280px; }
.ku-ipa-row { left: -135px; transform: translateY( -50% ); }
.ku-ipa-y0 { top: 0; }
.ku-ipa-y1 { top: 70px; }
.ku-ipa-y2 { top: 140px; }
.ku-ipa-y3 { top: 210px; }
.ku-ipa-pt {
position: absolute;
width: 0;
height: 0;
}
.ku-ipa-pt::before {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var( --color-base, #202122 );
}
.ku-ipa-near-close-back::before {
display: none;
}
.ku-ipa-l,
.ku-ipa-r,
.ku-ipa-c {
position: absolute;
top: 0;
display: flex;
white-space: nowrap;
z-index: 1;
}
.ku-ipa-l { right: 4px; transform: translateY( -50% ); }
.ku-ipa-r { left: 4px; transform: translateY( -50% ); }
.ku-ipa-c { left: 0; transform: translate( -50%, -50% ); }
.ku-ipa-close-front { left: 0; top: 0; }
.ku-ipa-close-back { left: 280px; top: 0; }
.ku-ipa-near-close-back { left: 236px; top: 35px; }
.ku-ipa-close-mid-front { left: 46.67px; top: 70px; }
.ku-ipa-close-mid-central { left: 163.33px; top: 70px; }
.ku-ipa-close-mid-back { left: 280px; top: 70px; }
.ku-ipa-open-mid-front { left: 93.33px; top: 140px; }
.ku-ipa-open-back { left: 280px; top: 210px; }
/* Consonant table */
.ku-ipa-cons {
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.ku-ipa-cons th,
.ku-ipa-cons td {
border: 1px solid var( --border-color-subtle, #c8ccd1 );
padding: 2px;
}
.ku-ipa-cons th {
font-size: 80%;
font-weight: normal;
background-color: var( --background-color-success-subtle, #dff2eb );
}
.ku-ipa-cons tr > th:first-child {
text-align: right;
white-space: nowrap;
}
.ku-ipa-cons td {
min-width: 4.5em;
vertical-align: middle;
}
.ku-ipa-cell {
display: flex;
align-items: center;
gap: 4px;
}
.ku-ipa-vl,
.ku-ipa-vd {
display: flex;
}
.ku-ipa-vd {
margin-left: auto;
}
.ku-ipa-cons td.ku-ipa-no {
background-color: var( --background-color-disabled, #c8ccd1 );
}
/* Affricates, other symbols and notes */
.ku-ipa-boxes {
display: flex;
flex-wrap: wrap;
gap: 0 1em;
}
.ku-ipa-boxes > .ku-ipa-part {
flex: 1 1 18em;
}
.ku-ipa-list {
margin: 0 auto;
}
.ku-ipa-list td {
padding: 2px 6px;
vertical-align: middle;
}
.ku-ipa-mark {
display: inline-block;
min-width: 1.7em;
text-align: center;
font-size: 125%;
}
.ku-ipa-notes {
font-size: 85%;
}
.ku-ipa-key {
margin-right: 0.3em;
}
7ac3unwtz76zpkyf06a4wvf8pwlztbj
6865507
6865505
2026-10-23T13:23:53Z
Wikihez
7344
IPA chart: center the vowel trapezoid
6865507
sanitized-css
text/css
/* Kurmancî IPA chart: vowel trapezoid, consonant table and Phonos buttons */
.ku-ipa {
margin: 0.5em 0 1em;
font-size: 95%;
}
.ku-ipa-help,
.ku-ipa-part {
margin-bottom: 1em;
padding: 0.5em 0.8em;
border: 1px solid var( --border-color-success, #14866d );
background-color: var( --background-color-success-subtle, #dff2eb );
color: var( --color-base, #202122 );
}
.ku-ipa-part {
background-color: transparent;
border: 1px solid var( --border-color-subtle, #c8ccd1 );
}
.ku-ipa-title {
font-size: 125%;
text-align: center;
}
.ku-ipa-sub {
font-size: 85%;
text-align: center;
color: var( --color-subtle, #54595d );
margin-bottom: 0.5em;
}
.ku-ipa-scroll {
overflow-x: auto;
}
/* Sound buttons */
.ku-ipa .ext-phonos {
white-space: nowrap;
}
.ku-ipa .ext-phonos-attribution,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-iconElement-icon,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-indicatorElement-indicator {
/* Phonos sets display:inline !important on everything inside it */
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton {
margin: 1px;
vertical-align: middle;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-nofile {
display: inline-flex !important;
flex-direction: column;
align-items: center;
min-width: 1.7em;
min-height: 0 !important;
padding: 1px 3px !important;
border: 1px solid var( --border-color-success, #14866d ) !important;
border-radius: 2px;
background-color: var( --background-color-success-subtle, #dff2eb ) !important;
color: var( --color-base, #202122 ) !important;
line-height: 1.15;
font-weight: normal;
box-sizing: border-box;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:hover,
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:focus {
background-color: var( --background-color-success, #14866d ) !important;
color: var( --color-inverted-fixed, #fff ) !important;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button::after {
/* Site CSS adds a speaker mark after audio links */
content: none !important;
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton .oo-ui-labelElement-label {
margin: 0 !important;
display: flex !important;
flex-direction: column;
align-items: center;
}
.ku-ipa-nofile {
margin: 1px;
vertical-align: middle;
border-style: dashed !important;
background-color: transparent !important;
}
.ku-ipa-sym {
font-size: 125%;
}
.ku-ipa-let {
font-size: 70%;
opacity: 0.8;
}
.ku-ipa-let:empty {
display: none !important;
}
.ku-ipa-sdh .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-sdh .ku-ipa-nofile {
border-style: dashed !important;
}
/* Vowel trapezoid: 4 units wide, 3 units high, 70px a unit */
.ku-ipa-trap {
position: relative;
width: 280px;
height: 210px;
margin: 70px auto 40px;
/* Transparent borders leave room for labels and buttons without moving the origin */
border-left: 140px solid transparent;
border-right: 50px solid transparent;
}
.ku-ipa-line {
position: absolute;
height: 0;
border-top: 1px solid var( --color-base, #202122 );
transform-origin: 0 0;
}
.ku-ipa-h0 { left: 0; top: 0; width: 280px; }
.ku-ipa-h1 { left: 46.67px; top: 70px; width: 233.33px; }
.ku-ipa-h2 { left: 93.33px; top: 140px; width: 186.67px; }
.ku-ipa-h3 { left: 140px; top: 210px; width: 140px; }
.ku-ipa-back { left: 280px; top: 0; width: 210px; transform: rotate( 90deg ); }
.ku-ipa-front { left: 0; top: 0; width: 252.39px; transform: rotate( 56.31deg ); }
.ku-ipa-central { left: 140px; top: 0; width: 221.36px; transform: rotate( 71.57deg ); }
.ku-ipa-col,
.ku-ipa-row {
position: absolute;
font-size: 85%;
color: var( --color-subtle, #54595d );
white-space: nowrap;
}
.ku-ipa-col { top: -62px; transform: translateX( -50% ); }
.ku-ipa-x0 { left: 0; }
.ku-ipa-x2 { left: 140px; }
.ku-ipa-x4 { left: 280px; }
.ku-ipa-row { left: -135px; transform: translateY( -50% ); }
.ku-ipa-y0 { top: 0; }
.ku-ipa-y1 { top: 70px; }
.ku-ipa-y2 { top: 140px; }
.ku-ipa-y3 { top: 210px; }
.ku-ipa-pt {
position: absolute;
width: 0;
height: 0;
}
.ku-ipa-pt::before {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var( --color-base, #202122 );
}
.ku-ipa-near-close-back::before {
display: none;
}
.ku-ipa-l,
.ku-ipa-r,
.ku-ipa-c {
position: absolute;
top: 0;
display: flex;
white-space: nowrap;
z-index: 1;
}
.ku-ipa-l { right: 4px; transform: translateY( -50% ); }
.ku-ipa-r { left: 4px; transform: translateY( -50% ); }
.ku-ipa-c { left: 0; transform: translate( -50%, -50% ); }
.ku-ipa-close-front { left: 0; top: 0; }
.ku-ipa-close-back { left: 280px; top: 0; }
.ku-ipa-near-close-back { left: 236px; top: 35px; }
.ku-ipa-close-mid-front { left: 46.67px; top: 70px; }
.ku-ipa-close-mid-central { left: 163.33px; top: 70px; }
.ku-ipa-close-mid-back { left: 280px; top: 70px; }
.ku-ipa-open-mid-front { left: 93.33px; top: 140px; }
.ku-ipa-open-back { left: 280px; top: 210px; }
/* Consonant table */
.ku-ipa-cons {
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.ku-ipa-cons th,
.ku-ipa-cons td {
border: 1px solid var( --border-color-subtle, #c8ccd1 );
padding: 2px;
}
.ku-ipa-cons th {
font-size: 80%;
font-weight: normal;
background-color: var( --background-color-success-subtle, #dff2eb );
}
.ku-ipa-cons tr > th:first-child {
text-align: right;
white-space: nowrap;
}
.ku-ipa-cons td {
min-width: 4.5em;
vertical-align: middle;
}
.ku-ipa-cell {
display: flex;
align-items: center;
gap: 4px;
}
.ku-ipa-vl,
.ku-ipa-vd {
display: flex;
}
.ku-ipa-vd {
margin-left: auto;
}
.ku-ipa-cons td.ku-ipa-no {
background-color: var( --background-color-disabled, #c8ccd1 );
}
/* Affricates, other symbols and notes */
.ku-ipa-boxes {
display: flex;
flex-wrap: wrap;
gap: 0 1em;
}
.ku-ipa-boxes > .ku-ipa-part {
flex: 1 1 18em;
}
.ku-ipa-list {
margin: 0 auto;
}
.ku-ipa-list td {
padding: 2px 6px;
vertical-align: middle;
}
.ku-ipa-mark {
display: inline-block;
min-width: 1.7em;
text-align: center;
font-size: 125%;
}
.ku-ipa-notes {
font-size: 85%;
}
.ku-ipa-key {
margin-right: 0.3em;
}
2lqd8y7qdkluuj2a0hlwbyi1z2tlu2y
6865517
6865507
2026-10-23T14:53:49Z
Wikihez
7344
IPA chart: add xʷ, ŋ as n/ň, sˤ ṭ zˤ; unrecorded sounds get a plain box
6865517
sanitized-css
text/css
/* Kurmancî IPA chart: vowel trapezoid, consonant table and Phonos buttons */
.ku-ipa {
margin: 0.5em 0 1em;
font-size: 95%;
}
.ku-ipa-help,
.ku-ipa-part {
margin-bottom: 1em;
padding: 0.5em 0.8em;
border: 1px solid var( --border-color-success, #14866d );
background-color: var( --background-color-success-subtle, #dff2eb );
color: var( --color-base, #202122 );
}
.ku-ipa-part {
background-color: transparent;
border: 1px solid var( --border-color-subtle, #c8ccd1 );
}
.ku-ipa-title {
font-size: 125%;
text-align: center;
}
.ku-ipa-sub {
font-size: 85%;
text-align: center;
color: var( --color-subtle, #54595d );
margin-bottom: 0.5em;
}
.ku-ipa-scroll {
overflow-x: auto;
}
/* Sound buttons */
.ku-ipa .ext-phonos {
white-space: nowrap;
}
.ku-ipa .ext-phonos-attribution,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-iconElement-icon,
.ku-ipa .ext-phonos-PhonosButton .oo-ui-indicatorElement-indicator {
/* Phonos sets display:inline !important on everything inside it */
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton {
margin: 1px;
vertical-align: middle;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-nofile {
display: inline-flex !important;
flex-direction: column;
align-items: center;
min-width: 1.7em;
min-height: 0 !important;
padding: 1px 3px !important;
border: 1px solid var( --border-color-success, #14866d ) !important;
border-radius: 2px;
background-color: var( --background-color-success-subtle, #dff2eb ) !important;
color: var( --color-base, #202122 ) !important;
line-height: 1.15;
font-weight: normal;
box-sizing: border-box;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:hover,
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button:focus {
background-color: var( --background-color-success, #14866d ) !important;
color: var( --color-inverted-fixed, #fff ) !important;
}
.ku-ipa .ext-phonos-PhonosButton > .oo-ui-buttonElement-button::after {
/* Site CSS adds a speaker mark after audio links */
content: none !important;
display: none !important;
}
.ku-ipa .ext-phonos-PhonosButton .oo-ui-labelElement-label {
margin: 0 !important;
display: flex !important;
flex-direction: column;
align-items: center;
}
.ku-ipa-nofile {
margin: 1px;
vertical-align: middle;
background-color: transparent !important;
}
.ku-ipa-sym {
font-size: 125%;
}
.ku-ipa-let {
font-size: 70%;
opacity: 0.8;
}
.ku-ipa-let:empty {
display: none !important;
}
.ku-ipa-sdh .ext-phonos-PhonosButton > .oo-ui-buttonElement-button,
.ku-ipa-sdh .ku-ipa-nofile {
border-style: dashed !important;
}
/* Vowel trapezoid: 4 units wide, 3 units high, 70px a unit */
.ku-ipa-trap {
position: relative;
width: 280px;
height: 210px;
margin: 70px auto 40px;
/* Transparent borders leave room for labels and buttons without moving the origin */
border-left: 140px solid transparent;
border-right: 50px solid transparent;
}
.ku-ipa-line {
position: absolute;
height: 0;
border-top: 1px solid var( --color-base, #202122 );
transform-origin: 0 0;
}
.ku-ipa-h0 { left: 0; top: 0; width: 280px; }
.ku-ipa-h1 { left: 46.67px; top: 70px; width: 233.33px; }
.ku-ipa-h2 { left: 93.33px; top: 140px; width: 186.67px; }
.ku-ipa-h3 { left: 140px; top: 210px; width: 140px; }
.ku-ipa-back { left: 280px; top: 0; width: 210px; transform: rotate( 90deg ); }
.ku-ipa-front { left: 0; top: 0; width: 252.39px; transform: rotate( 56.31deg ); }
.ku-ipa-central { left: 140px; top: 0; width: 221.36px; transform: rotate( 71.57deg ); }
.ku-ipa-col,
.ku-ipa-row {
position: absolute;
font-size: 85%;
color: var( --color-subtle, #54595d );
white-space: nowrap;
}
.ku-ipa-col { top: -62px; transform: translateX( -50% ); }
.ku-ipa-x0 { left: 0; }
.ku-ipa-x2 { left: 140px; }
.ku-ipa-x4 { left: 280px; }
.ku-ipa-row { left: -135px; transform: translateY( -50% ); }
.ku-ipa-y0 { top: 0; }
.ku-ipa-y1 { top: 70px; }
.ku-ipa-y2 { top: 140px; }
.ku-ipa-y3 { top: 210px; }
.ku-ipa-pt {
position: absolute;
width: 0;
height: 0;
}
.ku-ipa-pt::before {
content: '';
position: absolute;
left: -3px;
top: -3px;
width: 6px;
height: 6px;
border-radius: 50%;
background-color: var( --color-base, #202122 );
}
.ku-ipa-near-close-back::before {
display: none;
}
.ku-ipa-l,
.ku-ipa-r,
.ku-ipa-c {
position: absolute;
top: 0;
display: flex;
white-space: nowrap;
z-index: 1;
}
.ku-ipa-l { right: 4px; transform: translateY( -50% ); }
.ku-ipa-r { left: 4px; transform: translateY( -50% ); }
.ku-ipa-c { left: 0; transform: translate( -50%, -50% ); }
.ku-ipa-close-front { left: 0; top: 0; }
.ku-ipa-close-back { left: 280px; top: 0; }
.ku-ipa-near-close-back { left: 236px; top: 35px; }
.ku-ipa-close-mid-front { left: 46.67px; top: 70px; }
.ku-ipa-close-mid-central { left: 163.33px; top: 70px; }
.ku-ipa-close-mid-back { left: 280px; top: 70px; }
.ku-ipa-open-mid-front { left: 93.33px; top: 140px; }
.ku-ipa-open-back { left: 280px; top: 210px; }
/* Consonant table */
.ku-ipa-cons {
border-collapse: collapse;
margin: 0 auto;
text-align: center;
}
.ku-ipa-cons th,
.ku-ipa-cons td {
border: 1px solid var( --border-color-subtle, #c8ccd1 );
padding: 2px;
}
.ku-ipa-cons th {
font-size: 80%;
font-weight: normal;
background-color: var( --background-color-success-subtle, #dff2eb );
}
.ku-ipa-cons tr > th:first-child {
text-align: right;
white-space: nowrap;
}
.ku-ipa-cons td {
min-width: 4.5em;
vertical-align: middle;
}
.ku-ipa-cell {
display: flex;
align-items: center;
gap: 4px;
}
.ku-ipa-vl,
.ku-ipa-vd {
display: flex;
}
.ku-ipa-vd {
margin-left: auto;
}
.ku-ipa-cons td.ku-ipa-no {
background-color: var( --background-color-disabled, #c8ccd1 );
}
/* Affricates, other symbols and notes */
.ku-ipa-boxes {
display: flex;
flex-wrap: wrap;
gap: 0 1em;
}
.ku-ipa-boxes > .ku-ipa-part {
flex: 1 1 18em;
}
.ku-ipa-list {
margin: 0 auto;
}
.ku-ipa-list td {
padding: 2px 6px;
vertical-align: middle;
}
.ku-ipa-mark {
display: inline-block;
min-width: 1.7em;
text-align: center;
font-size: 125%;
}
.ku-ipa-notes {
font-size: 85%;
}
.ku-ipa-key {
margin-right: 0.3em;
}
8sb34f40v4sgi9lp3vjxyrdmf3z26fk
Şablon:tabloya IPA ya kurmancî/deng
10
1359505
6865502
2026-10-23T13:19:35Z
Wikihez
7344
Interactive Kurmancî IPA chart (data from Wîkîferheng:IPA kurmancî)
6865502
wikitext
text/x-wiki
<includeonly>{{#if:{{{file|}}}|{{#tag:phonos|<span class="ku-ipa-sym IPA">-{<!---->{{{1}}}<!---->}-</span><span class="ku-ipa-let">{{{2|}}}</span>|file={{{file}}}}}|<span class="ku-ipa-nofile"><span class="ku-ipa-sym IPA">-{<!---->{{{1}}}<!---->}-</span><span class="ku-ipa-let">{{{2|}}}</span></span>}}</includeonly><noinclude>
Bişkoka dengekî ya [[Şablon:tabloya IPA ya kurmancî]]: <code><nowiki>{{tabloya IPA ya kurmancî/deng|IPA|tîp|file=Navê dosyeyê}}</nowiki></code>
</noinclude>
hdnz4vvkkxtzghecq89izmz05u3yjjo
6865508
6865502
2026-10-23T13:25:32Z
Wikihez
7344
IPA chart: keep IPA symbols unconverted in ku-arab
6865508
wikitext
text/x-wiki
<includeonly>{{#if:{{{file|}}}|{{#tag:phonos|<span class="ku-ipa-sym IPA">{{{1}}}</span><span class="ku-ipa-let">{{{2|}}}</span>|file={{{file}}}}}|<span class="ku-ipa-nofile"><span class="ku-ipa-sym IPA">{{{1}}}</span><span class="ku-ipa-let">{{{2|}}}</span></span>}}</includeonly><noinclude>
Bişkoka dengekî ya [[Şablon:tabloya IPA ya kurmancî]]: <code><nowiki>{{tabloya IPA ya kurmancî/deng|IPA|tîp|file=Navê dosyeyê}}</nowiki></code>
</noinclude>
hq1jsxrrupxi1utdujvc0txk8yx3wed
6865513
6865508
2026-10-23T13:28:07Z
Wikihez
7344
IPA chart: tîp → herf
6865513
wikitext
text/x-wiki
<includeonly>{{#if:{{{file|}}}|{{#tag:phonos|<span class="ku-ipa-sym IPA">{{{1}}}</span><span class="ku-ipa-let">{{{2|}}}</span>|file={{{file}}}}}|<span class="ku-ipa-nofile"><span class="ku-ipa-sym IPA">{{{1}}}</span><span class="ku-ipa-let">{{{2|}}}</span></span>}}</includeonly><noinclude>
Bişkoka dengekî ya [[Şablon:tabloya IPA ya kurmancî]]: <code><nowiki>{{tabloya IPA ya kurmancî/deng|IPA|herf|file=Navê dosyeyê}}</nowiki></code>
</noinclude>
sl2q5jq8drkw233wrmdm0heqacnnk91
Şablon:tabloya IPA ya kurmancî
10
1359506
6865503
2026-10-23T13:19:40Z
Wikihez
7344
Interactive Kurmancî IPA chart (data from Wîkîferheng:IPA kurmancî)
6865503
wikitext
text/x-wiki
<templatestyles src="tabloya IPA ya kurmancî/styles.css" /><div class="ku-ipa" dir="ltr">
<div class="ku-ipa-help">Li ser sembolekê bitikîne da ku dengê wê bibihîzî. Di bin her sembolê de tîpa kurmancî heye.</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[vokal|Vokal]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lêvên girover tê gotin.</div>
<div class="ku-ipa-scroll"><div class="ku-ipa-trap">
<span class="ku-ipa-col ku-ipa-x0">Pêş</span><span class="ku-ipa-col ku-ipa-x2">Navîn</span><span class="ku-ipa-col ku-ipa-x4">Paş</span>
<span class="ku-ipa-row ku-ipa-y0">Girtî</span><span class="ku-ipa-row ku-ipa-y1">Nîvgirtî</span><span class="ku-ipa-row ku-ipa-y2">Nîvvekirî</span><span class="ku-ipa-row ku-ipa-y3">Vekirî</span>
<span class="ku-ipa-line ku-ipa-h0"></span><span class="ku-ipa-line ku-ipa-h1"></span><span class="ku-ipa-line ku-ipa-h2"></span><span class="ku-ipa-line ku-ipa-h3"></span><span class="ku-ipa-line ku-ipa-back"></span><span class="ku-ipa-line ku-ipa-front"></span><span class="ku-ipa-line ku-ipa-central"></span>
<span class="ku-ipa-pt ku-ipa-close-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|i|i|file=Close front unrounded vowel.ogg}}{{tabloya IPA ya kurmancî/deng|iː|î|file=LL-Q36368 (kur)-Pispor-î.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|yː|ü|file=Close front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|uː|û|file=LL-Q36368 (kur)-Pispor-û.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-near-close-back"><span class="ku-ipa-c">{{tabloya IPA ya kurmancî/deng|ʊ|u|file=LL-Q36368 (kur)-Pispor-u.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|eː|ê|file=LL-Q36368 (kur)-Pispor-ê.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|øː|ö|file=Close-mid front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-central"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɘ|i|file=LL-Q36368 (kur)-Pispor-i.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|oː|o|file=LL-Q36368 (kur)-Pispor-o.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɛ|e|file=LL-Q36368 (kur)-Pispor-e.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-back"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɑː|a|file=LL-Q36368 (kur)-Pispor-a.wav}}</span></span>
</div></div>
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[konsonant|Konsonant]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lerizîna têlên dengî tê gotin. Xaneyên bi rengê gewr ne mumkin in.</div>
<div class="ku-ipa-scroll">
{| class="ku-ipa-cons"
!
! Bîlabiyal !! Labiyodental !! Alveolar !! Postalveolar !! Palatal !! Velar !! Uvular !! Farîngal !! Glotal
|-
! Plozîv
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|p|p|file=LL-Q36368 (kur)-Pispor-p (nerm).wav}}{{tabloya IPA ya kurmancî/deng|pʰ|p|file=LL-Q36368 (kur)-Pispor-p (req).wav}}{{tabloya IPA ya kurmancî/deng|pˤ|p|file=LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|b|b|file=LL-Q36368 (kur)-Pispor-b.wav}}</span>
|
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|t|t|file=LL-Q36368 (kur)-Pispor-t (nerm).wav}}{{tabloya IPA ya kurmancî/deng|tʰ|t|file=LL-Q36368 (kur)-Pispor-t (req).wav}}{{tabloya IPA ya kurmancî/deng|tˤ|t|file=LL-Q36368 (kur)-Pispor-t (qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|d|d|file=LL-Q36368 (kur)-Pispor-d.wav}}</span>
|
|
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|k|k|file=LL-Q36368 (kur)-Pispor-k (nerm).wav}}{{tabloya IPA ya kurmancî/deng|kʰ|k|file=LL-Q36368 (kur)-Pispor-k (req).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɡ|g|file=LL-Q36368 (kur)-Pispor-g.wav}}</span>
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|q|q|file=LL-Q36368 (kur)-Pispor-q.wav}}</span>
|
|
|-
! Nazal
| <span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|m|m|file=LL-Q36368 (kur)-Pispor-m.wav}}</span>
|
| <span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|n|n|file=LL-Q36368 (kur)-Pispor-n.wav}}</span>
|
|
| <span class="ku-ipa-vd ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|ŋ|ň|file=Velar nasal.ogg}}</span>
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|-
! Trîl
|
|
| <span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|r|ř|file=LL-Q36368 (kur)-Pispor-r (qelew).wav}}</span>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Tap
|
|
| <span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɾ|r|file=LL-Q36368 (kur)-Pispor-r (nerm).wav}}</span>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Frîkatîv
|
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|f|f|file=LL-Q36368 (kur)-Pispor-f.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|v|v|file=LL-Q36368 (kur)-Pispor-v.wav}}</span>
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|s|s|file=LL-Q36368 (kur)-Pispor-s.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|z|z|file=LL-Q36368 (kur)-Pispor-z (zirav).wav}}</span>
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ʃ|ş|file=LL-Q36368 (kur)-Pispor-ş.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʒ|j|file=LL-Q36368 (kur)-Pispor-j.wav}}</span>
|
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|x|x|file=LL-Q36368 (kur)-Pispor-x.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɣ|ẍ|file=LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav}}</span>
|
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ħ|ḧ|file=LL-Q36368 (kur)-Pispor-ḧ.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʕ|ʿ|file=Voiced pharyngeal fricative.ogg}}</span>
| <span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|h|h|file=LL-Q36368 (kur)-Pispor-h.wav}}</span>
|-
! Aproksîmant
|
|
|
|
| <span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|j|y|file=LL-Q36368 (kur)-Pispor-y.wav}}</span>
|
|
|
| class="ku-ipa-no" |
|-
! Aproksîmanta lateral
| class="ku-ipa-no" |
| class="ku-ipa-no" |
| <span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|l|l|file=LL-Q36368 (kur)-Pispor-l (zirav).wav}}{{tabloya IPA ya kurmancî/deng|ɫ|l|file=LL-Q36368 (kur)-Pispor-l (qelew).wav}}</span>
|
|
|
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|}
</div>
</div>
<div class="ku-ipa-boxes">
<div class="ku-ipa-part">
<div class="ku-ipa-title">Afrîkat</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|t͡ʃ|ç|file=LL-Q36368 (kur)-Pispor-ç (nerm).wav}} || '''ç''' (nerm): '''ç'''av, '''ç'''îrok
|-
| {{tabloya IPA ya kurmancî/deng|t͡ʃʰ|ç|file=LL-Q36368 (kur)-Pispor-ç (req).wav}} || '''ç''' (req): '''ç'''adir, '''ç'''end
|-
| {{tabloya IPA ya kurmancî/deng|d͡ʒ|c|file=LL-Q36368 (kur)-Pispor-c.wav}} || '''c''': '''c'''erg
|}
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">Sembolên din</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|w|w|file=LL-Q36368 (kur)-Pispor-w.wav}} || '''w''': aproksîmanta labiyal-velar, '''w'''erz
|-
| <span class="ku-ipa-mark">◌ʰ</span> || req (bi pif): '''k'''ar, '''p'''ere, '''t'''a
|-
| <span class="ku-ipa-mark">◌ˤ</span> || qelew (stûr): '''p'''ez, '''t'''eng
|-
| <span class="ku-ipa-mark">ɫ</span> || '''l'''-ya qelew: we'''l'''at, sa'''l'''
|}
</div>
</div>
<div class="ku-ipa-notes">
* <span class="ku-ipa-sdh ku-ipa-key">{{tabloya IPA ya kurmancî/deng|ŋ|ň}}</span> Bi xeta xalxalî: bi piranî di [[kurdiya başûrî]] û hin zaravayên din de.
* Dengên {{IPAchar|i}}, {{IPAchar|yː}}, {{IPAchar|øː}}, {{IPAchar|ŋ}} û {{IPAchar|ʕ}} ji tabloya giştî ya IPAyê ne; yên din ji [[c:Commons:Lingua Libre|Lingua Libre]]yê ne, bi dengê [[c:User:Pispor|Pispor]] û [[c:User:Ebulfîda|Ebulfîda]] (CC BY-SA 4.0). Ji bo xwedî û lîsansê binêre: [[c:File:Close front unrounded vowel.ogg|i]], [[c:File:Close front rounded vowel.ogg|yː]], [[c:File:Close-mid front rounded vowel.ogg|øː]], [[c:File:Velar nasal.ogg|ŋ]], [[c:File:Voiced pharyngeal fricative.ogg|ʕ]] (CC BY-SA 3.0).
</div>
</div><noinclude>
Tabloya IPAyê ya kurmancî ya ku bi tikandinê deng dide. Bikaranîn: <code><nowiki>{{tabloya IPA ya kurmancî}}</nowiki></code>. Agahî ji [[Wîkîferheng:IPA kurmancî]] hatine girtin.
[[Kategorî:Dengnasî]]
</noinclude>
3tnt4k3x18tzatizuyen2qmjkwgc7dx
6865506
6865503
2026-10-23T13:23:22Z
Wikihez
7344
IPA chart: hide the site speaker mark, keep voiced symbols on the right
6865506
wikitext
text/x-wiki
<templatestyles src="tabloya IPA ya kurmancî/styles.css" /><div class="ku-ipa" dir="ltr">
<div class="ku-ipa-help">Li ser sembolekê bitikîne da ku dengê wê bibihîzî. Di bin her sembolê de tîpa kurmancî heye.</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[vokal|Vokal]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lêvên girover tê gotin.</div>
<div class="ku-ipa-scroll"><div class="ku-ipa-trap">
<span class="ku-ipa-col ku-ipa-x0">Pêş</span><span class="ku-ipa-col ku-ipa-x2">Navîn</span><span class="ku-ipa-col ku-ipa-x4">Paş</span>
<span class="ku-ipa-row ku-ipa-y0">Girtî</span><span class="ku-ipa-row ku-ipa-y1">Nîvgirtî</span><span class="ku-ipa-row ku-ipa-y2">Nîvvekirî</span><span class="ku-ipa-row ku-ipa-y3">Vekirî</span>
<span class="ku-ipa-line ku-ipa-h0"></span><span class="ku-ipa-line ku-ipa-h1"></span><span class="ku-ipa-line ku-ipa-h2"></span><span class="ku-ipa-line ku-ipa-h3"></span><span class="ku-ipa-line ku-ipa-back"></span><span class="ku-ipa-line ku-ipa-front"></span><span class="ku-ipa-line ku-ipa-central"></span>
<span class="ku-ipa-pt ku-ipa-close-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|i|i|file=Close front unrounded vowel.ogg}}{{tabloya IPA ya kurmancî/deng|iː|î|file=LL-Q36368 (kur)-Pispor-î.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|yː|ü|file=Close front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|uː|û|file=LL-Q36368 (kur)-Pispor-û.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-near-close-back"><span class="ku-ipa-c">{{tabloya IPA ya kurmancî/deng|ʊ|u|file=LL-Q36368 (kur)-Pispor-u.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|eː|ê|file=LL-Q36368 (kur)-Pispor-ê.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|øː|ö|file=Close-mid front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-central"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɘ|i|file=LL-Q36368 (kur)-Pispor-i.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|oː|o|file=LL-Q36368 (kur)-Pispor-o.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɛ|e|file=LL-Q36368 (kur)-Pispor-e.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-back"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɑː|a|file=LL-Q36368 (kur)-Pispor-a.wav}}</span></span>
</div></div>
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[konsonant|Konsonant]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lerizîna têlên dengî tê gotin. Xaneyên bi rengê gewr ne mumkin in.</div>
<div class="ku-ipa-scroll">
{| class="ku-ipa-cons"
!
! Bîlabiyal !! Labiyodental !! Alveolar !! Postalveolar !! Palatal !! Velar !! Uvular !! Farîngal !! Glotal
|-
! Plozîv
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|p|p|file=LL-Q36368 (kur)-Pispor-p (nerm).wav}}{{tabloya IPA ya kurmancî/deng|pʰ|p|file=LL-Q36368 (kur)-Pispor-p (req).wav}}{{tabloya IPA ya kurmancî/deng|pˤ|p|file=LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|b|b|file=LL-Q36368 (kur)-Pispor-b.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|t|t|file=LL-Q36368 (kur)-Pispor-t (nerm).wav}}{{tabloya IPA ya kurmancî/deng|tʰ|t|file=LL-Q36368 (kur)-Pispor-t (req).wav}}{{tabloya IPA ya kurmancî/deng|tˤ|t|file=LL-Q36368 (kur)-Pispor-t (qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|d|d|file=LL-Q36368 (kur)-Pispor-d.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|k|k|file=LL-Q36368 (kur)-Pispor-k (nerm).wav}}{{tabloya IPA ya kurmancî/deng|kʰ|k|file=LL-Q36368 (kur)-Pispor-k (req).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɡ|g|file=LL-Q36368 (kur)-Pispor-g.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|q|q|file=LL-Q36368 (kur)-Pispor-q.wav}}</span></div>
|
|
|-
! Nazal
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|m|m|file=LL-Q36368 (kur)-Pispor-m.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|n|n|file=LL-Q36368 (kur)-Pispor-n.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|ŋ|ň|file=Velar nasal.ogg}}</span></div>
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|-
! Trîl
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|r|ř|file=LL-Q36368 (kur)-Pispor-r (qelew).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Tap
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɾ|r|file=LL-Q36368 (kur)-Pispor-r (nerm).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Frîkatîv
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|f|f|file=LL-Q36368 (kur)-Pispor-f.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|v|v|file=LL-Q36368 (kur)-Pispor-v.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|s|s|file=LL-Q36368 (kur)-Pispor-s.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|z|z|file=LL-Q36368 (kur)-Pispor-z (zirav).wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ʃ|ş|file=LL-Q36368 (kur)-Pispor-ş.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʒ|j|file=LL-Q36368 (kur)-Pispor-j.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|x|x|file=LL-Q36368 (kur)-Pispor-x.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɣ|ẍ|file=LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ħ|ḧ|file=LL-Q36368 (kur)-Pispor-ḧ.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʕ|ʿ|file=Voiced pharyngeal fricative.ogg}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|h|h|file=LL-Q36368 (kur)-Pispor-h.wav}}</span></div>
|-
! Aproksîmant
|
|
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|j|y|file=LL-Q36368 (kur)-Pispor-y.wav}}</span></div>
|
|
|
| class="ku-ipa-no" |
|-
! Aproksîmanta lateral
| class="ku-ipa-no" |
| class="ku-ipa-no" |
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|l|l|file=LL-Q36368 (kur)-Pispor-l (zirav).wav}}{{tabloya IPA ya kurmancî/deng|ɫ|l|file=LL-Q36368 (kur)-Pispor-l (qelew).wav}}</span></div>
|
|
|
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|}
</div>
</div>
<div class="ku-ipa-boxes">
<div class="ku-ipa-part">
<div class="ku-ipa-title">Afrîkat</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|t͡ʃ|ç|file=LL-Q36368 (kur)-Pispor-ç (nerm).wav}} || '''ç''' (nerm): '''ç'''av, '''ç'''îrok
|-
| {{tabloya IPA ya kurmancî/deng|t͡ʃʰ|ç|file=LL-Q36368 (kur)-Pispor-ç (req).wav}} || '''ç''' (req): '''ç'''adir, '''ç'''end
|-
| {{tabloya IPA ya kurmancî/deng|d͡ʒ|c|file=LL-Q36368 (kur)-Pispor-c.wav}} || '''c''': '''c'''erg
|}
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">Sembolên din</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|w|w|file=LL-Q36368 (kur)-Pispor-w.wav}} || '''w''': aproksîmanta labiyal-velar, '''w'''erz
|-
| <span class="ku-ipa-mark">◌ʰ</span> || req (bi pif): '''k'''ar, '''p'''ere, '''t'''a
|-
| <span class="ku-ipa-mark">◌ˤ</span> || qelew (stûr): '''p'''ez, '''t'''eng
|-
| <span class="ku-ipa-mark">ɫ</span> || '''l'''-ya qelew: we'''l'''at, sa'''l'''
|}
</div>
</div>
<div class="ku-ipa-notes">
* <span class="ku-ipa-sdh ku-ipa-key">{{tabloya IPA ya kurmancî/deng|ŋ|ň}}</span> Bi xeta xalxalî: bi piranî di [[kurdiya başûrî]] û hin zaravayên din de.
* Dengên {{IPAchar|i}}, {{IPAchar|yː}}, {{IPAchar|øː}}, {{IPAchar|ŋ}} û {{IPAchar|ʕ}} ji tabloya giştî ya IPAyê ne; yên din ji [[c:Commons:Lingua Libre|Lingua Libre]]yê ne, bi dengê [[c:User:Pispor|Pispor]] û [[c:User:Ebulfîda|Ebulfîda]] (CC BY-SA 4.0). Ji bo xwedî û lîsansê binêre: [[c:File:Close front unrounded vowel.ogg|i]], [[c:File:Close front rounded vowel.ogg|yː]], [[c:File:Close-mid front rounded vowel.ogg|øː]], [[c:File:Velar nasal.ogg|ŋ]], [[c:File:Voiced pharyngeal fricative.ogg|ʕ]] (CC BY-SA 3.0).
</div>
</div><noinclude>
Tabloya IPAyê ya kurmancî ya ku bi tikandinê deng dide. Bikaranîn: <code><nowiki>{{tabloya IPA ya kurmancî}}</nowiki></code>. Agahî ji [[Wîkîferheng:IPA kurmancî]] hatine girtin.
[[Kategorî:Dengnasî]]
</noinclude>
lgpjxq8amons33crdwts07mlx40w1fj
6865509
6865506
2026-10-23T13:25:37Z
Wikihez
7344
IPA chart: keep IPA symbols unconverted in ku-arab
6865509
wikitext
text/x-wiki
<templatestyles src="tabloya IPA ya kurmancî/styles.css" /><div class="ku-ipa" dir="ltr">
<div class="ku-ipa-help">Li ser sembolekê bitikîne da ku dengê wê bibihîzî. Di bin her sembolê de tîpa kurmancî heye.</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[vokal|Vokal]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lêvên girover tê gotin.</div>
<div class="ku-ipa-scroll"><div class="ku-ipa-trap">
<span class="ku-ipa-col ku-ipa-x0">Pêş</span><span class="ku-ipa-col ku-ipa-x2">Navîn</span><span class="ku-ipa-col ku-ipa-x4">Paş</span>
<span class="ku-ipa-row ku-ipa-y0">Girtî</span><span class="ku-ipa-row ku-ipa-y1">Nîvgirtî</span><span class="ku-ipa-row ku-ipa-y2">Nîvvekirî</span><span class="ku-ipa-row ku-ipa-y3">Vekirî</span>
<span class="ku-ipa-line ku-ipa-h0"></span><span class="ku-ipa-line ku-ipa-h1"></span><span class="ku-ipa-line ku-ipa-h2"></span><span class="ku-ipa-line ku-ipa-h3"></span><span class="ku-ipa-line ku-ipa-back"></span><span class="ku-ipa-line ku-ipa-front"></span><span class="ku-ipa-line ku-ipa-central"></span>
<span class="ku-ipa-pt ku-ipa-close-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|i|i|file=Close front unrounded vowel.ogg}}{{tabloya IPA ya kurmancî/deng|iː|î|file=LL-Q36368 (kur)-Pispor-î.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|yː|ü|file=Close front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|uː|û|file=LL-Q36368 (kur)-Pispor-û.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-near-close-back"><span class="ku-ipa-c">{{tabloya IPA ya kurmancî/deng|ʊ|u|file=LL-Q36368 (kur)-Pispor-u.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|eː|ê|file=LL-Q36368 (kur)-Pispor-ê.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|øː|ö|file=Close-mid front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-central"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɘ|i|file=LL-Q36368 (kur)-Pispor-i.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|oː|o|file=LL-Q36368 (kur)-Pispor-o.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɛ|e|file=LL-Q36368 (kur)-Pispor-e.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-back"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɑː|a|file=LL-Q36368 (kur)-Pispor-a.wav}}</span></span>
</div></div>
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[konsonant|Konsonant]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lerizîna têlên dengî tê gotin. Xaneyên bi rengê gewr ne mumkin in.</div>
<div class="ku-ipa-scroll">
{| class="ku-ipa-cons"
!
! Bîlabiyal !! Labiyodental !! Alveolar !! Postalveolar !! Palatal !! Velar !! Uvular !! Farîngal !! Glotal
|-
! Plozîv
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|p|p|file=LL-Q36368 (kur)-Pispor-p (nerm).wav}}{{tabloya IPA ya kurmancî/deng|pʰ|p|file=LL-Q36368 (kur)-Pispor-p (req).wav}}{{tabloya IPA ya kurmancî/deng|pˤ|p|file=LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|b|b|file=LL-Q36368 (kur)-Pispor-b.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|t|t|file=LL-Q36368 (kur)-Pispor-t (nerm).wav}}{{tabloya IPA ya kurmancî/deng|tʰ|t|file=LL-Q36368 (kur)-Pispor-t (req).wav}}{{tabloya IPA ya kurmancî/deng|tˤ|t|file=LL-Q36368 (kur)-Pispor-t (qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|d|d|file=LL-Q36368 (kur)-Pispor-d.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|k|k|file=LL-Q36368 (kur)-Pispor-k (nerm).wav}}{{tabloya IPA ya kurmancî/deng|kʰ|k|file=LL-Q36368 (kur)-Pispor-k (req).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɡ|g|file=LL-Q36368 (kur)-Pispor-g.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|q|q|file=LL-Q36368 (kur)-Pispor-q.wav}}</span></div>
|
|
|-
! Nazal
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|m|m|file=LL-Q36368 (kur)-Pispor-m.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|n|n|file=LL-Q36368 (kur)-Pispor-n.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|ŋ|ň|file=Velar nasal.ogg}}</span></div>
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|-
! Trîl
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|r|ř|file=LL-Q36368 (kur)-Pispor-r (qelew).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Tap
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɾ|r|file=LL-Q36368 (kur)-Pispor-r (nerm).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Frîkatîv
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|f|f|file=LL-Q36368 (kur)-Pispor-f.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|v|v|file=LL-Q36368 (kur)-Pispor-v.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|s|s|file=LL-Q36368 (kur)-Pispor-s.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|z|z|file=LL-Q36368 (kur)-Pispor-z (zirav).wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ʃ|ş|file=LL-Q36368 (kur)-Pispor-ş.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʒ|j|file=LL-Q36368 (kur)-Pispor-j.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|x|x|file=LL-Q36368 (kur)-Pispor-x.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɣ|ẍ|file=LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ħ|ḧ|file=LL-Q36368 (kur)-Pispor-ḧ.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʕ|ʿ|file=Voiced pharyngeal fricative.ogg}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|h|h|file=LL-Q36368 (kur)-Pispor-h.wav}}</span></div>
|-
! Aproksîmant
|
|
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|j|y|file=LL-Q36368 (kur)-Pispor-y.wav}}</span></div>
|
|
|
| class="ku-ipa-no" |
|-
! Aproksîmanta lateral
| class="ku-ipa-no" |
| class="ku-ipa-no" |
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|l|l|file=LL-Q36368 (kur)-Pispor-l (zirav).wav}}{{tabloya IPA ya kurmancî/deng|ɫ|l|file=LL-Q36368 (kur)-Pispor-l (qelew).wav}}</span></div>
|
|
|
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|}
</div>
</div>
<div class="ku-ipa-boxes">
<div class="ku-ipa-part">
<div class="ku-ipa-title">Afrîkat</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|t͡ʃ|ç|file=LL-Q36368 (kur)-Pispor-ç (nerm).wav}} || '''ç''' (nerm): '''ç'''av, '''ç'''îrok
|-
| {{tabloya IPA ya kurmancî/deng|t͡ʃʰ|ç|file=LL-Q36368 (kur)-Pispor-ç (req).wav}} || '''ç''' (req): '''ç'''adir, '''ç'''end
|-
| {{tabloya IPA ya kurmancî/deng|d͡ʒ|c|file=LL-Q36368 (kur)-Pispor-c.wav}} || '''c''': '''c'''erg
|}
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">Sembolên din</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|w|w|file=LL-Q36368 (kur)-Pispor-w.wav}} || '''w''': aproksîmanta labiyal-velar, '''w'''erz
|-
| <span class="ku-ipa-mark">◌ʰ</span> || req (bi pif): '''k'''ar, '''p'''ere, '''t'''a
|-
| <span class="ku-ipa-mark">◌ˤ</span> || qelew (stûr): '''p'''ez, '''t'''eng
|-
| <span class="ku-ipa-mark">ɫ</span> || '''l'''-ya qelew: we'''l'''at, sa'''l'''
|}
</div>
</div>
<div class="ku-ipa-notes">
* <span class="ku-ipa-sdh ku-ipa-key">{{tabloya IPA ya kurmancî/deng|ŋ|ň}}</span> Bi xeta xalxalî: bi piranî di [[kurdiya başûrî]] û hin zaravayên din de.
* Dengên {{IPAchar|i}}, {{IPAchar|yː}}, {{IPAchar|øː}}, {{IPAchar|ŋ}} û {{IPAchar|ʕ}} ji tabloya giştî ya IPAyê ne; yên din ji [[c:Commons:Lingua Libre|Lingua Libre]]yê ne, bi dengê [[c:User:Pispor|Pispor]] û [[c:User:Ebulfîda|Ebulfîda]] (CC BY-SA 4.0). Ji bo xwedî û lîsansê binêre: [[c:File:Close front unrounded vowel.ogg|i]], [[c:File:Close front rounded vowel.ogg|yː]], [[c:File:Close-mid front rounded vowel.ogg|øː]], [[c:File:Velar nasal.ogg|ŋ]], [[c:File:Voiced pharyngeal fricative.ogg|ʕ]] (CC BY-SA 3.0).
</div>
</div><noinclude>
Tabloya IPAyê ya kurmancî ya ku bi tikandinê deng dide. Tîpên latînî yên di sembolên IPAyê de bi <code>&#NNN;</code> hatine nivîsîn da ku guhertina ku-arab wan neguherîne. Bikaranîn: <code><nowiki>{{tabloya IPA ya kurmancî}}</nowiki></code>. Agahî ji [[Wîkîferheng:IPA kurmancî]] hatine girtin.
[[Kategorî:Dengnasî]]
</noinclude>
dlkwqpx8qokwxxm7v5aplpc8g9m9plu
6865510
6865509
2026-10-23T13:26:13Z
Wikihez
7344
IPA chart: keep only the trapezoid and table left-to-right
6865510
wikitext
text/x-wiki
<templatestyles src="tabloya IPA ya kurmancî/styles.css" /><div class="ku-ipa">
<div class="ku-ipa-help">Li ser sembolekê bitikîne da ku dengê wê bibihîzî. Di bin her sembolê de tîpa kurmancî heye.</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[vokal|Vokal]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lêvên girover tê gotin.</div>
<div class="ku-ipa-scroll"><div class="ku-ipa-trap" dir="ltr">
<span class="ku-ipa-col ku-ipa-x0">Pêş</span><span class="ku-ipa-col ku-ipa-x2">Navîn</span><span class="ku-ipa-col ku-ipa-x4">Paş</span>
<span class="ku-ipa-row ku-ipa-y0">Girtî</span><span class="ku-ipa-row ku-ipa-y1">Nîvgirtî</span><span class="ku-ipa-row ku-ipa-y2">Nîvvekirî</span><span class="ku-ipa-row ku-ipa-y3">Vekirî</span>
<span class="ku-ipa-line ku-ipa-h0"></span><span class="ku-ipa-line ku-ipa-h1"></span><span class="ku-ipa-line ku-ipa-h2"></span><span class="ku-ipa-line ku-ipa-h3"></span><span class="ku-ipa-line ku-ipa-back"></span><span class="ku-ipa-line ku-ipa-front"></span><span class="ku-ipa-line ku-ipa-central"></span>
<span class="ku-ipa-pt ku-ipa-close-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|i|i|file=Close front unrounded vowel.ogg}}{{tabloya IPA ya kurmancî/deng|iː|î|file=LL-Q36368 (kur)-Pispor-î.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|yː|ü|file=Close front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|uː|û|file=LL-Q36368 (kur)-Pispor-û.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-near-close-back"><span class="ku-ipa-c">{{tabloya IPA ya kurmancî/deng|ʊ|u|file=LL-Q36368 (kur)-Pispor-u.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|eː|ê|file=LL-Q36368 (kur)-Pispor-ê.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|øː|ö|file=Close-mid front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-central"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɘ|i|file=LL-Q36368 (kur)-Pispor-i.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|oː|o|file=LL-Q36368 (kur)-Pispor-o.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɛ|e|file=LL-Q36368 (kur)-Pispor-e.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-back"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɑː|a|file=LL-Q36368 (kur)-Pispor-a.wav}}</span></span>
</div></div>
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[konsonant|Konsonant]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lerizîna têlên dengî tê gotin. Xaneyên bi rengê gewr ne mumkin in.</div>
<div class="ku-ipa-scroll">
{| class="ku-ipa-cons" dir="ltr"
!
! Bîlabiyal !! Labiyodental !! Alveolar !! Postalveolar !! Palatal !! Velar !! Uvular !! Farîngal !! Glotal
|-
! Plozîv
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|p|p|file=LL-Q36368 (kur)-Pispor-p (nerm).wav}}{{tabloya IPA ya kurmancî/deng|pʰ|p|file=LL-Q36368 (kur)-Pispor-p (req).wav}}{{tabloya IPA ya kurmancî/deng|pˤ|p|file=LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|b|b|file=LL-Q36368 (kur)-Pispor-b.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|t|t|file=LL-Q36368 (kur)-Pispor-t (nerm).wav}}{{tabloya IPA ya kurmancî/deng|tʰ|t|file=LL-Q36368 (kur)-Pispor-t (req).wav}}{{tabloya IPA ya kurmancî/deng|tˤ|t|file=LL-Q36368 (kur)-Pispor-t (qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|d|d|file=LL-Q36368 (kur)-Pispor-d.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|k|k|file=LL-Q36368 (kur)-Pispor-k (nerm).wav}}{{tabloya IPA ya kurmancî/deng|kʰ|k|file=LL-Q36368 (kur)-Pispor-k (req).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɡ|g|file=LL-Q36368 (kur)-Pispor-g.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|q|q|file=LL-Q36368 (kur)-Pispor-q.wav}}</span></div>
|
|
|-
! Nazal
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|m|m|file=LL-Q36368 (kur)-Pispor-m.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|n|n|file=LL-Q36368 (kur)-Pispor-n.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|ŋ|ň|file=Velar nasal.ogg}}</span></div>
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|-
! Trîl
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|r|ř|file=LL-Q36368 (kur)-Pispor-r (qelew).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Tap
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɾ|r|file=LL-Q36368 (kur)-Pispor-r (nerm).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Frîkatîv
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|f|f|file=LL-Q36368 (kur)-Pispor-f.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|v|v|file=LL-Q36368 (kur)-Pispor-v.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|s|s|file=LL-Q36368 (kur)-Pispor-s.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|z|z|file=LL-Q36368 (kur)-Pispor-z (zirav).wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ʃ|ş|file=LL-Q36368 (kur)-Pispor-ş.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʒ|j|file=LL-Q36368 (kur)-Pispor-j.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|x|x|file=LL-Q36368 (kur)-Pispor-x.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɣ|ẍ|file=LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ħ|ḧ|file=LL-Q36368 (kur)-Pispor-ḧ.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʕ|ʿ|file=Voiced pharyngeal fricative.ogg}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|h|h|file=LL-Q36368 (kur)-Pispor-h.wav}}</span></div>
|-
! Aproksîmant
|
|
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|j|y|file=LL-Q36368 (kur)-Pispor-y.wav}}</span></div>
|
|
|
| class="ku-ipa-no" |
|-
! Aproksîmanta lateral
| class="ku-ipa-no" |
| class="ku-ipa-no" |
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|l|l|file=LL-Q36368 (kur)-Pispor-l (zirav).wav}}{{tabloya IPA ya kurmancî/deng|ɫ|l|file=LL-Q36368 (kur)-Pispor-l (qelew).wav}}</span></div>
|
|
|
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|}
</div>
</div>
<div class="ku-ipa-boxes">
<div class="ku-ipa-part">
<div class="ku-ipa-title">Afrîkat</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|t͡ʃ|ç|file=LL-Q36368 (kur)-Pispor-ç (nerm).wav}} || '''ç''' (nerm): '''ç'''av, '''ç'''îrok
|-
| {{tabloya IPA ya kurmancî/deng|t͡ʃʰ|ç|file=LL-Q36368 (kur)-Pispor-ç (req).wav}} || '''ç''' (req): '''ç'''adir, '''ç'''end
|-
| {{tabloya IPA ya kurmancî/deng|d͡ʒ|c|file=LL-Q36368 (kur)-Pispor-c.wav}} || '''c''': '''c'''erg
|}
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">Sembolên din</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|w|w|file=LL-Q36368 (kur)-Pispor-w.wav}} || '''w''': aproksîmanta labiyal-velar, '''w'''erz
|-
| <span class="ku-ipa-mark">◌ʰ</span> || req (bi pif): '''k'''ar, '''p'''ere, '''t'''a
|-
| <span class="ku-ipa-mark">◌ˤ</span> || qelew (stûr): '''p'''ez, '''t'''eng
|-
| <span class="ku-ipa-mark">ɫ</span> || '''l'''-ya qelew: we'''l'''at, sa'''l'''
|}
</div>
</div>
<div class="ku-ipa-notes">
* <span class="ku-ipa-sdh ku-ipa-key">{{tabloya IPA ya kurmancî/deng|ŋ|ň}}</span> Bi xeta xalxalî: bi piranî di [[kurdiya başûrî]] û hin zaravayên din de.
* Dengên {{IPAchar|i}}, {{IPAchar|yː}}, {{IPAchar|øː}}, {{IPAchar|ŋ}} û {{IPAchar|ʕ}} ji tabloya giştî ya IPAyê ne; yên din ji [[c:Commons:Lingua Libre|Lingua Libre]]yê ne, bi dengê [[c:User:Pispor|Pispor]] û [[c:User:Ebulfîda|Ebulfîda]] (CC BY-SA 4.0). Ji bo xwedî û lîsansê binêre: [[c:File:Close front unrounded vowel.ogg|i]], [[c:File:Close front rounded vowel.ogg|yː]], [[c:File:Close-mid front rounded vowel.ogg|øː]], [[c:File:Velar nasal.ogg|ŋ]], [[c:File:Voiced pharyngeal fricative.ogg|ʕ]] (CC BY-SA 3.0).
</div>
</div><noinclude>
Tabloya IPAyê ya kurmancî ya ku bi tikandinê deng dide. Tîpên latînî yên di sembolên IPAyê de bi <code>&#NNN;</code> hatine nivîsîn da ku guhertina ku-arab wan neguherîne. Bikaranîn: <code><nowiki>{{tabloya IPA ya kurmancî}}</nowiki></code>. Agahî ji [[Wîkîferheng:IPA kurmancî]] hatine girtin.
[[Kategorî:Dengnasî]]
</noinclude>
ak6o21zcqfn6wo4rjrliqpti993pkg1
6865512
6865510
2026-10-23T13:28:02Z
Wikihez
7344
IPA chart: tîp → herf
6865512
wikitext
text/x-wiki
<templatestyles src="tabloya IPA ya kurmancî/styles.css" /><div class="ku-ipa">
<div class="ku-ipa-help">Li ser sembolekê bitikîne da ku dengê wê bibihîzî. Di bin her sembolê de herfa kurmancî heye.</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[vokal|Vokal]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lêvên girover tê gotin.</div>
<div class="ku-ipa-scroll"><div class="ku-ipa-trap" dir="ltr">
<span class="ku-ipa-col ku-ipa-x0">Pêş</span><span class="ku-ipa-col ku-ipa-x2">Navîn</span><span class="ku-ipa-col ku-ipa-x4">Paş</span>
<span class="ku-ipa-row ku-ipa-y0">Girtî</span><span class="ku-ipa-row ku-ipa-y1">Nîvgirtî</span><span class="ku-ipa-row ku-ipa-y2">Nîvvekirî</span><span class="ku-ipa-row ku-ipa-y3">Vekirî</span>
<span class="ku-ipa-line ku-ipa-h0"></span><span class="ku-ipa-line ku-ipa-h1"></span><span class="ku-ipa-line ku-ipa-h2"></span><span class="ku-ipa-line ku-ipa-h3"></span><span class="ku-ipa-line ku-ipa-back"></span><span class="ku-ipa-line ku-ipa-front"></span><span class="ku-ipa-line ku-ipa-central"></span>
<span class="ku-ipa-pt ku-ipa-close-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|i|i|file=Close front unrounded vowel.ogg}}{{tabloya IPA ya kurmancî/deng|iː|î|file=LL-Q36368 (kur)-Pispor-î.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|yː|ü|file=Close front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|uː|û|file=LL-Q36368 (kur)-Pispor-û.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-near-close-back"><span class="ku-ipa-c">{{tabloya IPA ya kurmancî/deng|ʊ|u|file=LL-Q36368 (kur)-Pispor-u.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|eː|ê|file=LL-Q36368 (kur)-Pispor-ê.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|øː|ö|file=Close-mid front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-central"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɘ|i|file=LL-Q36368 (kur)-Pispor-i.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|oː|o|file=LL-Q36368 (kur)-Pispor-o.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɛ|e|file=LL-Q36368 (kur)-Pispor-e.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-back"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɑː|a|file=LL-Q36368 (kur)-Pispor-a.wav}}</span></span>
</div></div>
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[konsonant|Konsonant]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lerizîna têlên dengî tê gotin. Xaneyên bi rengê gewr ne mumkin in.</div>
<div class="ku-ipa-scroll">
{| class="ku-ipa-cons" dir="ltr"
!
! Bîlabiyal !! Labiyodental !! Alveolar !! Postalveolar !! Palatal !! Velar !! Uvular !! Farîngal !! Glotal
|-
! Plozîv
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|p|p|file=LL-Q36368 (kur)-Pispor-p (nerm).wav}}{{tabloya IPA ya kurmancî/deng|pʰ|p|file=LL-Q36368 (kur)-Pispor-p (req).wav}}{{tabloya IPA ya kurmancî/deng|pˤ|p|file=LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|b|b|file=LL-Q36368 (kur)-Pispor-b.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|t|t|file=LL-Q36368 (kur)-Pispor-t (nerm).wav}}{{tabloya IPA ya kurmancî/deng|tʰ|t|file=LL-Q36368 (kur)-Pispor-t (req).wav}}{{tabloya IPA ya kurmancî/deng|tˤ|t|file=LL-Q36368 (kur)-Pispor-t (qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|d|d|file=LL-Q36368 (kur)-Pispor-d.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|k|k|file=LL-Q36368 (kur)-Pispor-k (nerm).wav}}{{tabloya IPA ya kurmancî/deng|kʰ|k|file=LL-Q36368 (kur)-Pispor-k (req).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɡ|g|file=LL-Q36368 (kur)-Pispor-g.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|q|q|file=LL-Q36368 (kur)-Pispor-q.wav}}</span></div>
|
|
|-
! Nazal
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|m|m|file=LL-Q36368 (kur)-Pispor-m.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|n|n|file=LL-Q36368 (kur)-Pispor-n.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|ŋ|ň|file=Velar nasal.ogg}}</span></div>
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|-
! Trîl
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|r|ř|file=LL-Q36368 (kur)-Pispor-r (qelew).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Tap
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɾ|r|file=LL-Q36368 (kur)-Pispor-r (nerm).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Frîkatîv
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|f|f|file=LL-Q36368 (kur)-Pispor-f.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|v|v|file=LL-Q36368 (kur)-Pispor-v.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|s|s|file=LL-Q36368 (kur)-Pispor-s.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|z|z|file=LL-Q36368 (kur)-Pispor-z (zirav).wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ʃ|ş|file=LL-Q36368 (kur)-Pispor-ş.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʒ|j|file=LL-Q36368 (kur)-Pispor-j.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|x|x|file=LL-Q36368 (kur)-Pispor-x.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɣ|ẍ|file=LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ħ|ḧ|file=LL-Q36368 (kur)-Pispor-ḧ.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʕ|ʿ|file=Voiced pharyngeal fricative.ogg}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|h|h|file=LL-Q36368 (kur)-Pispor-h.wav}}</span></div>
|-
! Aproksîmant
|
|
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|j|y|file=LL-Q36368 (kur)-Pispor-y.wav}}</span></div>
|
|
|
| class="ku-ipa-no" |
|-
! Aproksîmanta lateral
| class="ku-ipa-no" |
| class="ku-ipa-no" |
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|l|l|file=LL-Q36368 (kur)-Pispor-l (zirav).wav}}{{tabloya IPA ya kurmancî/deng|ɫ|l|file=LL-Q36368 (kur)-Pispor-l (qelew).wav}}</span></div>
|
|
|
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|}
</div>
</div>
<div class="ku-ipa-boxes">
<div class="ku-ipa-part">
<div class="ku-ipa-title">Afrîkat</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|t͡ʃ|ç|file=LL-Q36368 (kur)-Pispor-ç (nerm).wav}} || '''ç''' (nerm): '''ç'''av, '''ç'''îrok
|-
| {{tabloya IPA ya kurmancî/deng|t͡ʃʰ|ç|file=LL-Q36368 (kur)-Pispor-ç (req).wav}} || '''ç''' (req): '''ç'''adir, '''ç'''end
|-
| {{tabloya IPA ya kurmancî/deng|d͡ʒ|c|file=LL-Q36368 (kur)-Pispor-c.wav}} || '''c''': '''c'''erg
|}
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">Sembolên din</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|w|w|file=LL-Q36368 (kur)-Pispor-w.wav}} || '''w''': aproksîmanta labiyal-velar, '''w'''erz
|-
| <span class="ku-ipa-mark">◌ʰ</span> || req (bi pif): '''k'''ar, '''p'''ere, '''t'''a
|-
| <span class="ku-ipa-mark">◌ˤ</span> || qelew (stûr): '''p'''ez, '''t'''eng
|-
| <span class="ku-ipa-mark">ɫ</span> || '''l'''-ya qelew: we'''l'''at, sa'''l'''
|}
</div>
</div>
<div class="ku-ipa-notes">
* <span class="ku-ipa-sdh ku-ipa-key">{{tabloya IPA ya kurmancî/deng|ŋ|ň}}</span> Bi xeta xalxalî: bi piranî di [[kurdiya başûrî]] û hin zaravayên din de.
* Dengên {{IPAchar|i}}, {{IPAchar|yː}}, {{IPAchar|øː}}, {{IPAchar|ŋ}} û {{IPAchar|ʕ}} ji tabloya giştî ya IPAyê ne; yên din ji [[c:Commons:Lingua Libre|Lingua Libre]]yê ne, bi dengê [[c:User:Pispor|Pispor]] û [[c:User:Ebulfîda|Ebulfîda]] (CC BY-SA 4.0). Ji bo xwedî û lîsansê binêre: [[c:File:Close front unrounded vowel.ogg|i]], [[c:File:Close front rounded vowel.ogg|yː]], [[c:File:Close-mid front rounded vowel.ogg|øː]], [[c:File:Velar nasal.ogg|ŋ]], [[c:File:Voiced pharyngeal fricative.ogg|ʕ]] (CC BY-SA 3.0).
</div>
</div><noinclude>
Tabloya IPAyê ya kurmancî ya ku bi tikandinê deng dide. Herfên latînî yên di sembolên IPAyê de bi <code>&#NNN;</code> hatine nivîsîn da ku guhertina ku-arab wan neguherîne. Bikaranîn: <code><nowiki>{{tabloya IPA ya kurmancî}}</nowiki></code>. Agahî ji [[Wîkîferheng:IPA kurmancî]] hatine girtin.
[[Kategorî:Dengnasî]]
</noinclude>
meo41ircu7ehqf9ymhbychkfv4g2f6e
6865518
6865512
2026-10-23T14:53:54Z
Wikihez
7344
IPA chart: add xʷ, ŋ as n/ň, sˤ ṭ zˤ; unrecorded sounds get a plain box
6865518
wikitext
text/x-wiki
<templatestyles src="tabloya IPA ya kurmancî/styles.css" /><div class="ku-ipa">
<div class="ku-ipa-help">Li ser sembolekê bitikîne da ku dengê wê bibihîzî. Di bin her sembolê de herfa kurmancî heye.</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[vokal|Vokal]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lêvên girover tê gotin.</div>
<div class="ku-ipa-scroll"><div class="ku-ipa-trap" dir="ltr">
<span class="ku-ipa-col ku-ipa-x0">Pêş</span><span class="ku-ipa-col ku-ipa-x2">Navîn</span><span class="ku-ipa-col ku-ipa-x4">Paş</span>
<span class="ku-ipa-row ku-ipa-y0">Girtî</span><span class="ku-ipa-row ku-ipa-y1">Nîvgirtî</span><span class="ku-ipa-row ku-ipa-y2">Nîvvekirî</span><span class="ku-ipa-row ku-ipa-y3">Vekirî</span>
<span class="ku-ipa-line ku-ipa-h0"></span><span class="ku-ipa-line ku-ipa-h1"></span><span class="ku-ipa-line ku-ipa-h2"></span><span class="ku-ipa-line ku-ipa-h3"></span><span class="ku-ipa-line ku-ipa-back"></span><span class="ku-ipa-line ku-ipa-front"></span><span class="ku-ipa-line ku-ipa-central"></span>
<span class="ku-ipa-pt ku-ipa-close-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|i|i|file=Close front unrounded vowel.ogg}}{{tabloya IPA ya kurmancî/deng|iː|î|file=LL-Q36368 (kur)-Pispor-î.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|yː|ü|file=Close front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|uː|û|file=LL-Q36368 (kur)-Pispor-û.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-near-close-back"><span class="ku-ipa-c">{{tabloya IPA ya kurmancî/deng|ʊ|u|file=LL-Q36368 (kur)-Pispor-u.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|eː|ê|file=LL-Q36368 (kur)-Pispor-ê.wav}}</span><span class="ku-ipa-r ku-ipa-sdh">{{tabloya IPA ya kurmancî/deng|øː|ö|file=Close-mid front rounded vowel.ogg}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-central"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɘ|i|file=LL-Q36368 (kur)-Pispor-i.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-close-mid-back"><span class="ku-ipa-r">{{tabloya IPA ya kurmancî/deng|oː|o|file=LL-Q36368 (kur)-Pispor-o.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-mid-front"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɛ|e|file=LL-Q36368 (kur)-Pispor-e.wav}}</span></span>
<span class="ku-ipa-pt ku-ipa-open-back"><span class="ku-ipa-l">{{tabloya IPA ya kurmancî/deng|ɑː|a|file=LL-Q36368 (kur)-Pispor-a.wav}}</span></span>
</div></div>
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">[[konsonant|Konsonant]]</div>
<div class="ku-ipa-sub">Li cihê ku sembol cot in, ya rastê bi lerizîna têlên dengî tê gotin. Xaneyên bi rengê gewr ne mumkin in.</div>
<div class="ku-ipa-scroll">
{| class="ku-ipa-cons" dir="ltr"
!
! Bîlabiyal !! Labiyodental !! Alveolar !! Postalveolar !! Palatal !! Velar !! Uvular !! Farîngal !! Glotal
|-
! Plozîv
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|p|p|file=LL-Q36368 (kur)-Pispor-p (nerm).wav}}{{tabloya IPA ya kurmancî/deng|pʰ|p|file=LL-Q36368 (kur)-Pispor-p (req).wav}}{{tabloya IPA ya kurmancî/deng|pˤ|p|file=LL-Q36163 (kmr)-Ebulfîda-P (Qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|b|b|file=LL-Q36368 (kur)-Pispor-b.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|t|t|file=LL-Q36368 (kur)-Pispor-t (nerm).wav}}{{tabloya IPA ya kurmancî/deng|tʰ|t|file=LL-Q36368 (kur)-Pispor-t (req).wav}}{{tabloya IPA ya kurmancî/deng|tˤ|t|file=LL-Q36368 (kur)-Pispor-t (qelew).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|d|d|file=LL-Q36368 (kur)-Pispor-d.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|k|k|file=LL-Q36368 (kur)-Pispor-k (nerm).wav}}{{tabloya IPA ya kurmancî/deng|kʰ|k|file=LL-Q36368 (kur)-Pispor-k (req).wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɡ|g|file=LL-Q36368 (kur)-Pispor-g.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|q|q|file=LL-Q36368 (kur)-Pispor-q.wav}}</span></div>
|
|
|-
! Nazal
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|m|m|file=LL-Q36368 (kur)-Pispor-m.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|n|n|file=LL-Q36368 (kur)-Pispor-n.wav}}</span></div>
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ŋ|n, ň|file=Velar nasal.ogg}}</span></div>
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|-
! Trîl
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|r|ř|file=LL-Q36368 (kur)-Pispor-r (qelew).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Tap
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɾ|r|file=LL-Q36368 (kur)-Pispor-r (nerm).wav}}</span></div>
|
|
| class="ku-ipa-no" |
|
|
| class="ku-ipa-no" |
|-
! Frîkatîv
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|f|f|file=LL-Q36368 (kur)-Pispor-f.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|v|v|file=LL-Q36368 (kur)-Pispor-v.wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|s|s|file=LL-Q36368 (kur)-Pispor-s.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|z|z|file=LL-Q36368 (kur)-Pispor-z (zirav).wav}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ʃ|ş|file=LL-Q36368 (kur)-Pispor-ş.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʒ|j|file=LL-Q36368 (kur)-Pispor-j.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|x|x|file=LL-Q36368 (kur)-Pispor-x.wav}}{{tabloya IPA ya kurmancî/deng|xʷ|xw}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ɣ|ẍ|file=LL-Q36163 (kmr)-Ebulfîda-Ẍ.wav}}</span></div>
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|ħ|ḧ|file=LL-Q36368 (kur)-Pispor-ḧ.wav}}</span><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|ʕ|ʿ|file=Voiced pharyngeal fricative.ogg}}</span></div>
| <div class="ku-ipa-cell"><span class="ku-ipa-vl">{{tabloya IPA ya kurmancî/deng|h|h|file=LL-Q36368 (kur)-Pispor-h.wav}}</span></div>
|-
! Aproksîmant
|
|
|
|
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|j|y|file=LL-Q36368 (kur)-Pispor-y.wav}}</span></div>
|
|
|
| class="ku-ipa-no" |
|-
! Aproksîmanta lateral
| class="ku-ipa-no" |
| class="ku-ipa-no" |
| <div class="ku-ipa-cell"><span class="ku-ipa-vd">{{tabloya IPA ya kurmancî/deng|l|l|file=LL-Q36368 (kur)-Pispor-l (zirav).wav}}{{tabloya IPA ya kurmancî/deng|ɫ|l|file=LL-Q36368 (kur)-Pispor-l (qelew).wav}}</span></div>
|
|
|
|
| class="ku-ipa-no" |
| class="ku-ipa-no" |
|}
</div>
</div>
<div class="ku-ipa-boxes">
<div class="ku-ipa-part">
<div class="ku-ipa-title">Afrîkat</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|t͡ʃ|ç|file=LL-Q36368 (kur)-Pispor-ç (nerm).wav}} || '''ç''' (nerm): '''ç'''av, '''ç'''îrok
|-
| {{tabloya IPA ya kurmancî/deng|t͡ʃʰ|ç|file=LL-Q36368 (kur)-Pispor-ç (req).wav}} || '''ç''' (req): '''ç'''adir, '''ç'''end
|-
| {{tabloya IPA ya kurmancî/deng|d͡ʒ|c|file=LL-Q36368 (kur)-Pispor-c.wav}} || '''c''': '''c'''erg
|}
</div>
<div class="ku-ipa-part">
<div class="ku-ipa-title">Sembolên din</div>
{| class="ku-ipa-list"
| {{tabloya IPA ya kurmancî/deng|w|w|file=LL-Q36368 (kur)-Pispor-w.wav}} || '''w''': aproksîmanta labiyal-velar, '''w'''erz
|-
| <span class="ku-ipa-mark">◌ʰ</span> || req (bi pif): '''k'''ar, '''p'''ere, '''t'''a
|-
| <span class="ku-ipa-mark">◌ˤ</span> || qelew (stûr): '''p'''ez, '''t'''eng; {{IPAchar|sˤ}} '''ṣ''', {{IPAchar|tˤ}} '''ṭ''', {{IPAchar|zˤ}} '''ẓ''' di kelîmeyên ji erebî de
|-
| <span class="ku-ipa-mark">ɫ</span> || '''l'''-ya qelew: we'''l'''at, sa'''l'''
|}
</div>
</div>
<div class="ku-ipa-notes">
* <span class="ku-ipa-sdh ku-ipa-key">{{tabloya IPA ya kurmancî/deng|yː|ü}}</span> Bi xeta xalxalî: bi piranî di [[kurdiya başûrî]] û hin zaravayên din de.
* <span class="ku-ipa-key">{{tabloya IPA ya kurmancî/deng|xʷ|xw}}</span> Bê reng: dengê wê hêj nehatiye qeydkirin.
* {{IPAchar|ŋ}} di kurmancî de berî {{IPAchar|k}} û {{IPAchar|ɡ}} tê gotin (ferhe'''n'''g, ba'''n'''k); di kurdiya başûrî de bi herfa ň tê nivîsîn.
* Dengên {{IPAchar|i}}, {{IPAchar|yː}}, {{IPAchar|øː}}, {{IPAchar|ŋ}} û {{IPAchar|ʕ}} ji tabloya giştî ya IPAyê ne; yên din ji [[c:Commons:Lingua Libre|Lingua Libre]]yê ne, bi dengê [[c:User:Pispor|Pispor]] û [[c:User:Ebulfîda|Ebulfîda]] (CC BY-SA 4.0). Ji bo xwedî û lîsansê binêre: [[c:File:Close front unrounded vowel.ogg|i]], [[c:File:Close front rounded vowel.ogg|yː]], [[c:File:Close-mid front rounded vowel.ogg|øː]], [[c:File:Velar nasal.ogg|ŋ]], [[c:File:Voiced pharyngeal fricative.ogg|ʕ]] (CC BY-SA 3.0).
</div>
</div><noinclude>
Tabloya IPAyê ya kurmancî ya ku bi tikandinê deng dide. Herfên latînî yên di sembolên IPAyê de bi <code>&#NNN;</code> hatine nivîsîn da ku guhertina ku-arab wan neguherîne. Bikaranîn: <code><nowiki>{{tabloya IPA ya kurmancî}}</nowiki></code>. Agahî ji [[Wîkîferheng:IPA kurmancî]] hatine girtin.
[[Kategorî:Dengnasî]]
</noinclude>
a2421lathp1w03s4375e9hp7xrrbiyr
MediaWiki:Gadget-wikt.suggest.js
8
1359507
6865553
2026-10-23T20:16:05Z
Wikihez
7344
New hidden gadget wikt.suggest: the shared dropdown for language and script fields
6865553
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.suggest.js]]
*
* The dropdown under a text box that the language and script fields share: the translation adder's,
* wikt.edit's langs.picker (etimon, cite) and the part-of-speech card's sc. Its own small module, so the
* translation adder, which every reader of a page with translation boxes loads, does not pull in wikt.edit.
*
* require( "ext.gadget.wikt.suggest" )( input, { items: function ( term ) { ... }, onPick: ... } )
*/
( function () {
"use strict";
// One menu for every box on the page, since only the focused box shows it.
var $menu = null;
function sharedMenu() {
if ( !$menu ) {
$menu = $( "<ul class='wiktku-langmenu' role='listbox'></ul>" ).hide().appendTo( document.body );
// A scroll elsewhere would leave the menu floating away from its box.
window.addEventListener( "scroll", function ( e ) {
if ( e.target !== $menu[ 0 ] ) {
$menu.hide();
}
}, true );
}
return $menu;
}
// The dropdown on any text box. opts:
// items( term ) [ { value, label, code } ] to offer for what was typed: value is written into the
// box, label (or value) is shown, with code after it in grey
// onPick( value ) runs after a pick writes value into the box
// onFocus also open the menu when the box gets focus, as a short list like scripts wants
// Returns { search } to refresh the menu, e.g. once more items have arrived. The menu is fixed to the
// viewport outside the form, so a panel that clips its content does not cut it off.
function suggest( input, opts ) {
input = $( input )[ 0 ];
var $menu = sharedMenu();
var items = [];
var active = -1;
input.setAttribute( "autocomplete", "off" );
function close() {
$menu.hide();
active = -1;
}
function choose( value ) {
input.value = value;
close();
if ( opts.onPick ) {
opts.onPick( value );
}
}
function render() {
$menu.empty();
items.forEach( function ( item, i ) {
var $li = $( "<li role='option'></li>" ).toggleClass( "wiktku-langmenu-active", i === active );
$li.text( item.label || item.value );
if ( item.code ) {
$li.append( $( "<span class='wiktku-langmenu-code'></span>" ).text( " " + item.code ) );
}
// Keeps the focus in the input, so its blur does not close the menu first.
$li.on( "mousedown", function ( e ) {
e.preventDefault();
choose( item.value );
} ).appendTo( $menu );
} );
// Inside an OOUI dialog the menu has to sit in its window manager, or the modal covers it.
var container = $( input ).closest( ".oo-ui-windowManager" )[ 0 ] || document.body;
if ( $menu[ 0 ].parentNode !== container ) {
// Opening a modal makes the rest of the body inert, the menu with it if it was there.
$menu.appendTo( container ).removeAttr( "inert aria-hidden" );
}
var rect = input.getBoundingClientRect();
var below = window.innerHeight - rect.bottom - 4;
var above = rect.top - 4;
$menu.css( { left: rect.left, top: rect.bottom, maxHeight: "" } ).toggle( items.length > 0 );
var height = $menu.outerHeight();
// Fixed to the window, the menu cannot be scrolled to: near the bottom it opens upwards
// if there is more room there, and either way it is cut to the room it has.
if ( height > below && above > below ) {
height = Math.min( height, above );
$menu.css( { top: rect.top - height, maxHeight: height } );
} else if ( height > below ) {
$menu.css( { maxHeight: below } );
}
var current = $menu.children()[ active ];
if ( current ) {
current.scrollIntoView( { block: "nearest" } );
}
}
function search() {
if ( document.activeElement !== input ) {
return;
}
items = opts.items( input.value );
active = items.length ? 0 : -1;
render();
}
$( input ).on( "input", search ).on( "keydown", function ( e ) {
if ( !$menu.is( ":visible" ) || !items.length ) {
return;
}
if ( e.key === "ArrowDown" || e.key === "ArrowUp" ) {
e.preventDefault();
active = ( active + ( e.key === "ArrowDown" ? 1 : items.length - 1 ) ) % items.length;
render();
} else if ( e.key === "Enter" && active >= 0 ) {
e.preventDefault();
choose( items[ active ].value );
} else if ( e.key === "Escape" ) {
e.stopPropagation();
close();
}
} ).on( "blur", close );
if ( opts.onFocus ) {
$( input ).on( "focus", search );
}
return { search: search };
}
module.exports = suggest;
}() );
// </nowiki>
2dhobejfm3enkvnepu5fxg1qw7fkawk
6865572
6865553
2026-10-23T20:36:01Z
Wikihez
7344
Shared dropdown: Tab takes the highlighted item after typing; one language list (Modul:ziman's export) for every language box
6865572
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.suggest.js]]
*
* The dropdown under a text box that the language and script fields share: the translation adder's,
* wikt.edit's langs.picker (etimon, cite) and the part-of-speech card's sc. Its own small module, so the
* translation adder, which every reader of a page with translation boxes loads, does not pull in wikt.edit.
*
* require( "ext.gadget.wikt.suggest" )( input, { items: function ( term ) { ... }, onPick: ... } )
* require( "ext.gadget.wikt.suggest" ).languages() promise of the {code: name} language list
*/
( function () {
"use strict";
// One menu for every box on the page, since only the focused box shows it.
var $menu = null;
function sharedMenu() {
if ( !$menu ) {
$menu = $( "<ul class='wiktku-langmenu' role='listbox'></ul>" ).hide().appendTo( document.body );
// A scroll elsewhere would leave the menu floating away from its box.
window.addEventListener( "scroll", function ( e ) {
if ( e.target !== $menu[ 0 ] ) {
$menu.hide();
}
}, true );
}
return $menu;
}
// The dropdown on any text box. opts:
// items( term ) [ { value, label, code } ] to offer for what was typed: value is written into the
// box, label (or value) is shown, with code after it in grey
// onPick( value ) runs after a pick writes value into the box
// onFocus also open the menu when the box gets focus, as a short list like scripts wants
// Returns { search } to refresh the menu, e.g. once more items have arrived. The menu is fixed to the
// viewport outside the form, so a panel that clips its content does not cut it off.
function suggest( input, opts ) {
input = $( input )[ 0 ];
var $menu = sharedMenu();
var items = [];
var active = -1;
// Typed in or moved through since the box got focus: only then does Tab take the highlighted item,
// so tabbing past a box whose menu opened on focus leaves it as it was.
var engaged = false;
input.setAttribute( "autocomplete", "off" );
function close() {
$menu.hide();
active = -1;
}
function choose( value ) {
input.value = value;
close();
if ( opts.onPick ) {
opts.onPick( value );
}
}
function render() {
$menu.empty();
items.forEach( function ( item, i ) {
var $li = $( "<li role='option'></li>" ).toggleClass( "wiktku-langmenu-active", i === active );
$li.text( item.label || item.value );
if ( item.code ) {
$li.append( $( "<span class='wiktku-langmenu-code'></span>" ).text( " " + item.code ) );
}
// Keeps the focus in the input, so its blur does not close the menu first.
$li.on( "mousedown", function ( e ) {
e.preventDefault();
choose( item.value );
} ).appendTo( $menu );
} );
// Inside an OOUI dialog the menu has to sit in its window manager, or the modal covers it.
var container = $( input ).closest( ".oo-ui-windowManager" )[ 0 ] || document.body;
if ( $menu[ 0 ].parentNode !== container ) {
// Opening a modal makes the rest of the body inert, the menu with it if it was there.
$menu.appendTo( container ).removeAttr( "inert aria-hidden" );
}
var rect = input.getBoundingClientRect();
var below = window.innerHeight - rect.bottom - 4;
var above = rect.top - 4;
$menu.css( { left: rect.left, top: rect.bottom, maxHeight: "" } ).toggle( items.length > 0 );
var height = $menu.outerHeight();
// Fixed to the window, the menu cannot be scrolled to: near the bottom it opens upwards
// if there is more room there, and either way it is cut to the room it has.
if ( height > below && above > below ) {
height = Math.min( height, above );
$menu.css( { top: rect.top - height, maxHeight: height } );
} else if ( height > below ) {
$menu.css( { maxHeight: below } );
}
var current = $menu.children()[ active ];
if ( current ) {
current.scrollIntoView( { block: "nearest" } );
}
}
function search() {
if ( document.activeElement !== input ) {
return;
}
items = opts.items( input.value );
active = items.length ? 0 : -1;
render();
}
$( input ).on( "input", function () {
engaged = true;
search();
} ).on( "focus", function () {
engaged = false;
} ).on( "keydown", function ( e ) {
if ( !$menu.is( ":visible" ) || !items.length ) {
return;
}
if ( e.key === "ArrowDown" || e.key === "ArrowUp" ) {
e.preventDefault();
engaged = true;
active = ( active + ( e.key === "ArrowDown" ? 1 : items.length - 1 ) ) % items.length;
render();
} else if ( e.key === "Enter" && active >= 0 ) {
e.preventDefault();
choose( items[ active ].value );
} else if ( e.key === "Tab" && active >= 0 && engaged ) {
// The focus still moves on to the next box.
choose( items[ active ].value );
} else if ( e.key === "Escape" ) {
e.stopPropagation();
close();
}
} ).on( "blur", close );
if ( opts.onFocus ) {
$( input ).on( "focus", search );
}
return { search: search };
}
// The language list every language box uses: code -> Kurmancî name, from Modul:ziman's export.
// Fetched once per page, and cached by the browser for a day.
var NAMES_PAGE = "Modul:ziman/code to canonical name.json";
var namesPromise = null;
function languages() {
if ( !namesPromise ) {
namesPromise = $.get( {
url: mw.util.getUrl( NAMES_PAGE, { action: "raw", maxage: 86400, smaxage: 86400 } ),
dataType: "json"
} );
// A failed fetch is tried again next time instead of failing for good.
namesPromise.fail( function () {
namesPromise = null;
} );
}
return namesPromise;
}
suggest.languages = languages;
module.exports = suggest;
}() );
// </nowiki>
0gpqremrkqxtesk5nut6aibmogahsxs
MediaWiki:Gadget-wikt.suggest.css
8
1359508
6865554
2026-10-23T20:16:10Z
Wikihez
7344
New hidden gadget wikt.suggest: the shared dropdown for language and script fields
6865554
css
text/css
/* The dropdown from wikt.suggest.js under language and script boxes. */
.wiktku-langmenu {
position: fixed;
box-sizing: border-box;
z-index: 1100;
min-width: 14em;
max-height: 20em;
overflow-y: auto;
margin: 0;
padding: 2px 0;
list-style: none;
background: var(--background-color-base, #fff);
color: var(--color-base, #202122);
border: 1px solid var(--border-color-base, #a2a9b1);
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
}
.wiktku-langmenu li {
margin: 0;
padding: 2px 8px;
cursor: pointer;
white-space: nowrap;
}
.wiktku-langmenu li:hover,
.wiktku-langmenu .wiktku-langmenu-active {
background: var(--background-color-progressive-subtle, #eaf3ff);
}
.wiktku-langmenu-code {
color: var(--color-subtle, #54595d);
font-size: 85%;
}
mbgav7jbmfvcp5q8nsdq0zkpit8h37h
MediaWiki:Gadget-wikt.dsb-sense.js
8
1359509
6865612
2026-10-23T22:08:21Z
Wikihez
7344
DefSideBoxes: the sense panel as a module of its own, loaded with its pencil
6865612
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-sense.js]]
*
* The per-sense panel and the example editor of DefSideBoxes, loaded by wikt.dsb from a sense's or an
* example's pencil. The queue, previews and shared helpers are in [[MediaWiki:Gadget-wikt.dsb-panels.js]].
*/
(function () {
"use strict";
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var wt = edit.wt;
var startData = edit.data;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var aliasPattern = wt.aliasPattern;
var splitLeadingCalls = wt.splitLeadingCalls;
var parseTemplateCalls = wt.parseTemplateCalls;
var fetchTemplateData = wt.fetchTemplateData;
var shared = require("ext.gadget.wikt.dsb-panels").shared;
var SubmitAbort = shared.SubmitAbort;
var announceContent = shared.announceContent;
var apiErrorMessage = shared.apiErrorMessage;
var buildWordListField = shared.buildWordListField;
var dropQueueEntry = shared.dropQueueEntry;
var dsbQueue = shared.dsbQueue;
var fetchPageContent = shared.fetchPageContent;
var hideQueueIfIdle = shared.hideQueueIfIdle;
var locateSection = shared.locateSection;
var openQueueBox = shared.openQueueBox;
var placeLabel = shared.placeLabel;
var queueChange = shared.queueChange;
var queuedContent = shared.queuedContent;
var redrawPreviews = shared.redrawPreviews;
var renderWikitext = shared.renderWikitext;
// The six per-sense groups start.js writes as "#: {{tpl|lang|word1|word2}}" sub-lines under a "#" definition.
// Array order controls where a new group line lands relative to existing ones. Every group lives behind the
// quick row's "Hevmane û Bêhtir" dropdown; only the gloss and Nimûne/jêgirtin links sit in the row itself.
var SENSE_GROUPS = startData.senseGroups;
var GLOSS_LABEL = "Maneyê biguhêre";
var NEWLINE = String.fromCharCode(10);
// A gloss is one field on the page: the sense, then {{îzah|…}} explaining it when there is one.
function joinGloss(mane, izah) {
return izah ? $.trim(mane + " {{îzah|" + izah + "}}") : $.trim(mane);
}
// The two halves are linked separately, never joined first: linkify leaves template calls alone by
// design, so anything inside {{îzah|…}} would never be looked at.
//
// A half is skipped when there is nothing in it worth looking up, and -- when onlyUnlinked is set --
// when it already carries a link. Someone who has linked a word by hand has made that half theirs, and
// a save must not go on linking the rest of it behind their back; "Oto-lînk bike" leaves the flag off
// and still links on demand.
function linkifyGlossParts(mane, izah, done, onlyUnlinked) {
function needsLinking(text) {
if (!$.trim(text) || !util.hasLinkableText(text)) {
return false;
}
return !(onlyUnlinked && text.indexOf("[[") !== -1);
}
function step(text, next) {
if (!needsLinking(text)) {
next(text, null);
return;
}
util.linkifyText(text, function (linked, error) {
// An empty answer would wipe what the user typed, so only real text is taken.
next(typeof linked === "string" && $.trim(linked) ? $.trim(linked) : text, error || null);
});
}
step(mane, function (linkedMane, maneError) {
step(izah, function (linkedIzah, izahError) {
done(linkedMane, linkedIzah, maneError || izahError);
});
});
}
/**
* Puts a rendered sense in place of the one on the page, keeping this gadget's own furniture (the
* pencil's toolbar, the quick row, any open panel) where it is. The page's own nodes are detached
* rather than dropped, so the returned function can put them straight back when the change is
* cancelled or superseded.
*
* Nothing labels the result: the change is shown by the sense simply reading the way it will read,
* and the queue box is what says nothing is saved yet.
*/
function replaceSenseInPlace($li, item) {
var html = item.html;
var $anchor = $li.children(".dsb-toolbar").last();
// class/id/data-* off the parsed <li>, so a queued maneid shows its pill here exactly as it will
// once saved. The old values are kept for the restore below.
var node = $li.get(0);
var before = {};
$.each(node.attributes, function (_, attr) {
before[attr.name] = attr.value;
});
$.each(item.attrs, function (name, value) {
if (name !== "class") {
$li.attr(name, value);
}
});
var addedClasses = (item.attrs["class"] || "").split(/\s+/).filter(function (c) {
return c && !$li.hasClass(c);
});
$li.addClass(addedClasses.join(" "));
// add-examples hangs its own <ul> off the sense and opens its form as that <ul>'s next sibling.
// Detaching those leaves the form to open into a tree nobody can see -- "+ Nimune/jegirtin" takes
// the click and shows nothing -- so they stay where they are. Only the rows already in that <ul>
// are hidden, since the preview draws the examples itself; the tool's own title line stays.
var $keep = $li.children("ul, .add-example-form");
var $hiddenRows = $keep.filter("ul").children("li").filter(function () {
return !$(this).find(".add-example-form-title").length;
});
$hiddenRows.addClass("dsb-preview-hidden");
var originals = $li.contents().filter(function () {
return !$(this).is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-preview-block, .dsb-sense-preview");
}).not($keep).get();
// Where the sense's own nodes sat, so cancelling puts them back there rather than above the quick
// row -- the row belongs between the sense and its examples.
var backAfter = originals.length ? originals[0].previousSibling : null;
var $rendered = $("<span class='dsb-sense-preview'></span>").html(html);
if ($anchor.length) {
$anchor.after($rendered);
} else {
$li.prepend($rendered);
}
$(originals).detach();
announceContent($rendered);
// The id pill is drawn by the same code a page load uses, so a queued maneid looks like a saved
// one rather than like something this gadget invented.
if (window.wikt && wikt.showIDs && wikt.showIDs.process) {
wikt.showIDs.process($li);
}
// dsb.js puts the example pencils back on the redrawn examples. The originals keep theirs.
$li.triggerHandler("dsbSenseRedrawn");
return function () {
$hiddenRows.removeClass("dsb-preview-hidden");
$li.removeClass(addedClasses.join(" "));
$.each(item.attrs, function (name) {
if (name === "class") {
return;
}
if (Object.prototype.hasOwnProperty.call(before, name)) {
$li.attr(name, before[name]);
} else {
$li.removeAttr(name);
}
});
$li.children(".senseid-displayed, .etymid-displayed, .etymonid-displayed").remove();
if (backAfter && backAfter.parentNode === node) {
$(backAfter).after(originals);
} else if ($anchor.length) {
$anchor.after(originals);
} else {
$li.prepend(originals);
}
$rendered.remove();
};
}
/**
* The <li> out of a parsed "#" block, and the attributes it carries.
*
* The parse wraps it in its own <ol>, which must not reach the page: the sense already has a number
* from the list it lives in. A leading {{maneid}} also makes the parser emit an empty
* "li.mw-empty-elt" ahead of the real one, so the first <li> is not necessarily the sense.
*
* The attributes matter as much as the content: {{maneid|ku|x}} renders as class="senseid"
* data-id="x" on the <li> itself, which is what wikt.showids draws its pill from.
*/
function parsedSenseItem(html, depth) {
var $item = $("<div></div>").html(html).find("ol > li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
// A sub-sense parsed on its own comes back one <ol> deeper per "#": "##" is a list inside a list.
// Descend as far as the sense's own depth, or its number would be drawn inside a second list --
// "2." with a "1." under it.
for (var level = 1; level < (depth || 1) && $item.length; level++) {
var $inner = $item.children("ol").children("li").filter(function () {
return !$(this).hasClass("mw-empty-elt");
}).first();
if (!$inner.length) {
break;
}
$item = $inner;
}
if (!$item.length) {
return { html: html, attrs: {} };
}
var attrs = {};
$.each($item.get(0).attributes, function (_, attr) {
attrs[attr.name] = attr.value;
});
return { html: $item.html(), attrs: attrs };
}
/**
* Where a sense sits in the page the queue would save. A queued removal of an earlier sense moves
* everything below it up one, while the path was read off the page before that. Returns null when the
* sense itself, or a sense it hangs under, is the one being removed.
*/
function queuedSensePath(language, definitionLevel) {
var path = definitionLevel.slice();
var gone = false;
dsbQueue.entries.forEach(function (entry) {
var other = entry.removesSense && entry.sensePath;
if (!other || entry.senseLanguage !== language || other[0] !== path[0]) {
return;
}
var depth = other.length - 1;
if (depth >= path.length) {
return; // a sub-sense of something else; it moves nothing here
}
for (var i = 1; i < depth; i++) {
if (other[i] !== path[i]) {
return; // another branch of the list
}
}
if (other[depth] < path[depth]) {
path[depth] -= 1;
} else if (other[depth] === path[depth]) {
gone = true;
}
});
return gone ? null : path;
}
/**
* Redraws one sense where it sits, from the page the queue would save -- so a queued gloss edit, a
* queued example and everything else pending on that sense are shown together, rendered by the parser
* rather than described in a box. Returns the queue's own "take the preview down" function.
*
* One preview per sense: a second queued change takes the first drawing down and draws the queue.
*/
function previewSense($li, language, sectionType, sectionOrdinal, definitionLevel) {
var cancelled = false;
var restore = null;
function undo() {
cancelled = true;
if (restore) {
restore();
restore = null;
}
if ($li.data("dsbPreview") === undo) {
$li.removeData("dsbPreview");
}
}
var previous = $li.data("dsbPreview");
if (typeof previous === "function") {
previous();
}
$li.data("dsbPreview", undo);
fetchPageContent().then(function (pageContent) {
if (cancelled) {
return;
}
var path = queuedSensePath(language, definitionLevel);
if (!path) {
return;
}
var wikitext;
try {
wikitext = senseBlockWikitext(queuedContent(pageContent), language, sectionType,
sectionOrdinal, path);
} catch (e) {
return;
}
renderWikitext(wikitext).then(function (html) {
if (cancelled || $li.data("dsbPreview") !== undo || !$li.closest("body").length) {
return;
}
restore = replaceSenseInPlace($li, parsedSenseItem(html, definitionLevel.length - 1));
});
});
return undo;
}
// The parts of a sense the form actually changed, so the summary lists them instead of saying nothing.
// A save on a sense supersedes the one queued for it (they share a key), so it has to write that one's
// fields too. newer over older: a field the newer save touched wins, the rest stays as the older save
// left it. A gloss is only ever present when its box was open, so a missing one means untouched.
function mergeSenseChanges(older, newer) {
if (!older) {
return newer;
}
var merged = {
gloss: newer.gloss || older.gloss,
maneid: newer.changed.maneid ? newer.maneid : older.maneid,
etiket: newer.changed.etiket ? newer.etiket : older.etiket,
kategori: newer.changed.kategori ? newer.kategori : older.kategori,
words: {},
changed: $.extend({}, older.changed, newer.changed)
};
SENSE_GROUPS.forEach(function (group) {
merged.words[group.key] = newer.changed["word:" + group.key]
? newer.words[group.key]
: (older.words[group.key] || []);
});
return merged;
}
// The sense's fields as the page has them, with a queued change written over the top: what the boxes
// prefill from and what collectChanges() diffs against, so reopening a box shows the value that is
// waiting to be saved rather than the one it is about to replace.
function overlaySenseFields(fields, queued) {
if (!queued) {
return fields;
}
var out = {
gloss: queued.gloss || fields.gloss,
maneid: queued.changed.maneid ? queued.maneid : fields.maneid,
etiket: queued.changed.etiket ? queued.etiket : fields.etiket,
kategori: queued.changed.kategori ? queued.kategori : fields.kategori,
words: {}
};
SENSE_GROUPS.forEach(function (group) {
out.words[group.key] = queued.changed["word:" + group.key]
? queued.words[group.key]
: fields.words[group.key];
});
return out;
}
function senseChangeLabels(changes) {
var parts = [];
var changed = changes.changed || {};
if (changes.gloss) {
parts.push("mane");
}
if (changes.newSense) {
parts.push("maneyeke nû");
}
SENSE_GROUPS.forEach(function (group) {
if (changed["word:" + group.key]) {
parts.push(group.label.toLowerCase());
}
});
if (changed.maneid) {
parts.push("id");
}
if (changed.etiket) {
parts.push("etîket");
}
if (changed.kategori) {
parts.push("kategorî");
}
return parts;
}
var findDefinitionLine = wt.findDefinitionLine;
/**
* The wikitext of one sense's own block: its "#" line plus everything nested under it -- the "#:" word
* lines, the "#*" citations and any sub-senses -- stopping at the next line of the same depth that
* starts a new sense. Parsed on its own that block renders as exactly the <li> the page shows.
*/
function senseBlockWikitext(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var depth = definitionLevel.length - 1;
var out = [lines[defLine]];
for (var i = defLine + 1; i < lines.length; i++) {
var m = /^(#+)([:*]?)/.exec(lines[i]);
if (!m || m[1].length < depth) {
break;
}
// Same depth and no ":" or "*" after the hashes is the next sense, not part of this one.
if (m[1].length === depth && !m[2]) {
break;
}
out.push(lines[i]);
}
return out.join("\n");
}
// The word-list call a "#:" line under a sense is made of, as {name, words}: name as the page spells it,
// words its parameters after the language, each whole even when it holds a template or a piped link.
// Null when the line is not one call of keys (canonical names), or carries anything beside the call.
function senseGroupCall(line, marker, keys) {
var lead = new RegExp("^" + marker + ":\\s*").exec(line);
if (!lead) {
return null;
}
var parsed = parseTemplateCalls(line.slice(lead[0].length));
if (parsed.otherContent || parsed.calls.length !== 1) {
return null;
}
var call = parsed.calls[0];
if (keys.indexOf(canonicalTemplateKey(call.name)) === -1) {
return null;
}
return { name: call.name, words: call.params.slice(1).map(function (p) { return $.trim(p); }) };
}
// Inserts or extends a "#: {{tplKey|lang|word1|word2}}" sub-line for one sense (start.js's wordLine()
// format). If a line for this tplKey already exists under the definition, missing words are appended to
// it; otherwise a new line lands after the last existing group line, always before any "#*" citation lines.
function insertSenseWords(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, words, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = new Array(definitionLevel.length).join("#"); // definitionLevel.length - 1 "#" characters
// An existing {{hevwate}} stays {{hevwate}} rather than being silently renamed to {{hevmane}}.
var tplLineStart = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + aliasPattern(tplKey) + ")\\s*\\|");
var groupKeysPattern = SENSE_GROUPS.map(function (g) { return aliasPattern(g.key); }).join("|");
var groupLineRegex = new RegExp("^" + marker + ":\\s*\\{\\{\\s*(" + groupKeysPattern + ")\\s*\\|");
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var lastGroupLine = -1;
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var found = senseGroupCall(lines[i], marker, [tplKey]);
// A line of this list that is more than its one call cannot be rewritten without losing what
// else it holds, nor left beside a second one.
if (!found && tplLineStart.test(lines[i])) {
throw new SubmitAbort("Rêza «" + tplKey + "» ya vê maneyê nayê xwendin; wê bi destan biguhêre.");
}
if (found) {
var foundKey = found.name;
if (opts.replace) {
// Editing an existing list: an empty result means the user cleared it, so drop the line.
if (words.length) {
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + words.join("|") + "}}";
} else {
lines.splice(i, 1);
}
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
var existingWords = found.words;
words.forEach(function (w) {
if (existingWords.indexOf(w) === -1) {
existingWords.push(w);
}
});
lines[i] = marker + ": {{" + foundKey + "|" + language + "|" + existingWords.join("|") + "}}";
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
if (groupLineRegex.test(lines[i])) {
lastGroupLine = i;
}
}
// Nothing to write and no line to edit: a call with no words is a module error ("Parametreya 2
// lazim e"), so an empty list leaves the page alone rather than writing one.
if (!words.length) {
return pageContent;
}
var insertAt = lastGroupLine >= 0 ? lastGroupLine + 1 : defLine + 1;
lines.splice(insertAt, 0, marker + ": {{" + tplKey + "|" + language + "|" + words.join("|") + "}}");
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Canonical left-to-right order of the small templates that can prefix a definition line's gloss text:
// maneid, then kategori (K), then etîket (e).
var LEADING_TPL_ORDER = startData.leadingTplOrder;
// True for a leading call the advanced panel edits in a box of its own -- those are kept out of the
// gloss box so the same template is never editable in two places at once. Redirect spellings count:
// {{f}} and {{etîket}} are {{e}}.
function hasOwnFieldBox(key) {
return LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(key)) !== -1;
}
// Inserts, extends, or (with opts.replace) overwrites one of the small templates that prefix a definition
// line's gloss text -- {{maneid|lang|id}}, {{K|lang|kat1|...}} or {{e|lang|etîket1|...}}. Under
// opts.replace an empty values list removes the call rather than writing an empty one.
function insertLeadingSenseTpl(pageContent, language, sectionType, sectionOrdinal, definitionLevel, tplKey, values, opts) {
opts = opts || {};
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// Collecting the whole leading-call run means an existing tplKey call is found and merged wherever it
// already sits, not just when it happens to be first.
var split = splitLeadingCalls(afterMarker);
var calls = split.calls;
var rest = split.rest;
var existing = null;
calls.forEach(function (c) {
if (canonicalTemplateKey(c.key) === tplKey) {
existing = c;
}
});
if (existing) {
if (opts.replace) {
// Editing an existing call: an empty result means the user cleared it, so drop the call.
if (values.length) {
existing.params = [language].concat(values);
} else {
calls = calls.filter(function (c) { return c !== existing; });
}
} else {
var existingVals = existing.params.slice(1);
values.forEach(function (v) {
if (existingVals.indexOf(v) === -1) {
existingVals.push(v);
}
});
existing.params = [language].concat(existingVals);
}
} else if (!values.length) {
return pageContent; // nothing to add, and nothing there to clear
} else {
// Not present yet -- insert right before the first existing call that outranks tplKey in
// LEADING_TPL_ORDER (an unrecognized call always outranks everything), or at the end of the run.
var myRank = LEADING_TPL_ORDER.indexOf(tplKey);
var insertAt = calls.length;
for (var i = 0; i < calls.length; i++) {
var otherRank = LEADING_TPL_ORDER.indexOf(canonicalTemplateKey(calls[i].key));
if (otherRank === -1 || otherRank > myRank) {
insertAt = i;
break;
}
}
calls.splice(insertAt, 0, { key: tplKey, params: [language].concat(values) });
}
var rebuilt = calls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (rebuilt ? rebuilt + " " : "") + rest;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Replaces just the gloss text of a definition line, leaving any leading template calls untouched --
// the save half of "Maneyê biguhêre" (edit sense).
function updateSenseGloss(pageContent, language, sectionType, sectionOrdinal, definitionLevel, newGloss) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(marker.length).replace(/^\s+/, "");
// The box holds everything except the calls with their own field, so those are put back in front.
// Dropping "{{mane?}}" here is what clears start.js's placeholder once a real gloss is written.
var kept = splitLeadingCalls(afterMarker).calls.filter(function (c) {
return hasOwnFieldBox(c.key);
});
var leadingText = kept.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
lines[defLine] = marker + " " + (leadingText ? leadingText + " " : "") + newGloss;
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Deletes one sense: its definition line plus everything indented under it (its "#:" groups, "#*"
// citations, any sub-senses). Refuses to empty a section, since a POS heading with no senses is worse
// than the sense the user wanted gone.
// A new sense goes directly below the one whose pencil asked for it -- after the lines that sense owns
// (its "#:" examples, "#*" citations and "##" sub-senses), so it reads as the next sense rather than
// landing at the foot of the section. It takes the same marker depth as its neighbour, so asking from
// a "##" sub-sense adds a sibling sub-sense.
// extras are the sense's own "#:" lines, written under it in one go: a sense that is not on the
// page yet cannot be located by number, so everything it carries goes in with the insertion.
function insertNewSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel, text, extras) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
var block = [marker + " " + text];
(extras || []).forEach(function (extra) {
block.push(marker + extra);
});
lines.splice.apply(lines, [end, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function removeSense(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var marker = /^#+/.exec(lines[defLine])[0];
var end = defLine + 1;
var ownedRegex = new RegExp("^" + marker + "[#:*]");
while (end < lines.length && ownedRegex.test(lines[end])) {
end++;
}
// Top-level senses of this section only -- "##" sub-senses go with their parent either way.
if (definitionLevel.length === 2) {
var siblings = 0;
for (var i = loc.posLineIndex + 1; i < lines.length && !/^==/.test(lines[i]); i++) {
if (/^#[^*#:]/.test(lines[i])) {
siblings++;
}
}
if (siblings < 2) {
throw new SubmitAbort("Ev maneya dawî ya vê beşê ye. Ji bo rakirinê divê tu tevahiya beşê rakî.");
}
}
lines.splice(defLine, end - defLine);
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
function fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel) {
return fetchPageContent().then(function (content) {
return readSenseFields(content, language, sectionType, sectionOrdinal, definitionLevel);
});
}
// Everything the advanced panel can edit about one sense, read off its definition line and the "#:" lines
// under it: the raw gloss (templates included), the leading maneid/K/e values, and each word-list group.
function readSenseFields(pageContent, language, sectionType, sectionOrdinal, definitionLevel) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var defLine = findDefinitionLine(lines, loc.posLineIndex, definitionLevel);
var defMarker = /^#+/.exec(lines[defLine])[0];
var afterMarker = lines[defLine].slice(defMarker.length).replace(/^\s+/, "");
var leading = {};
splitLeadingCalls(afterMarker).calls.forEach(function (c) {
leading[canonicalTemplateKey(c.key)] = c.params.slice(1);
});
// Only start.js's "{{mane?|lang}}" placeholder is hidden; every other template stays visible so it can
// be edited by hand in the gloss box.
// The gloss box shows the line minus the calls that have their own box ({{maneid}}, {{K}}, {{e}} and
// their redirects) and minus start.js's "{{mane?}}" placeholder. Everything else -- a {{g}} link, a
// {{guharto}} -- stays visible and editable there.
var split = splitLeadingCalls(afterMarker);
var glossCalls = split.calls.filter(function (c) {
return !hasOwnFieldBox(c.key) && canonicalTemplateKey(c.key) !== "mane?";
});
var glossLead = glossCalls.map(function (c) {
return "{{" + c.key + "|" + c.params.join("|") + "}}";
}).join(" ");
var gloss = $.trim((glossLead ? glossLead + " " : "") + split.rest);
var marker = new Array(definitionLevel.length).join("#");
var groupKeys = SENSE_GROUPS.map(function (g) { return g.key; });
var anyBlockLineRegex = new RegExp("^" + marker + "[:*]");
var words = {};
for (var i = defLine + 1; i < lines.length; i++) {
if (!anyBlockLineRegex.test(lines[i])) {
break;
}
var call = senseGroupCall(lines[i], marker, groupKeys);
if (call) {
words[canonicalTemplateKey(call.name)] = call.words;
}
}
return {
gloss: gloss,
maneid: leading.maneid ? leading.maneid[0] : "",
etiket: leading.e || [],
kategori: leading.K || [],
words: words
};
}
// ---- template-parameter editing -------------------------------------------------------------------
// A gloss that is nothing but one template call gets a labelled box per parameter instead of a raw
// wikitext line, the way VisualEditor's template dialog works.
// The single template call a gloss is made of, or null when the line is anything less tidy. {{g|...}} is a
// link inside the gloss rather than the gloss itself, so it neither disqualifies the line nor gets a form.
function glossTemplateTarget(gloss) {
var parsed = parseTemplateCalls(gloss || "");
if (parsed.otherContent) {
return null;
}
var editable = parsed.calls.filter(function (c) {
return canonicalTemplateKey(c.name) !== "g";
});
return editable.length === 1 ? editable[0] : null;
}
// ---- "guhartina pêşketî" (advanced editing): combine several independent per-sense edits into ONE save ----
// Threads pageContent through the same pure functions every single-field form uses, applying only the
// pieces the caller actually filled in. A piece that conflicts with the page (maneid already set, a
// subsection that's already there) is skipped with a warning rather than failing the whole save. Throws
// SubmitAbort if nothing was filled in, so this can never turn into a no-op save.
function applyAdvancedChanges(pageContent, language, sectionType, sectionOrdinal, definitionLevel, changes) {
var content = pageContent;
var warnings = [];
var appliedAnything = false;
if (changes.gloss) {
content = updateSenseGloss(content, language, sectionType, sectionOrdinal, definitionLevel, changes.gloss);
appliedAnything = true;
}
if (changes.newSense) {
content = insertNewSense(content, language, sectionType, sectionOrdinal, definitionLevel,
changes.newSense, changes.newSenseExtras);
appliedAnything = true;
}
// Every box below is prefilled with what the sense already has, so a save replaces that value rather
// than merging into it -- and clearing a box removes the template. changes.changed marks the boxes
// whose contents actually differ from the page, so an opened-but-untouched box writes nothing.
var replace = { replace: true };
if (changes.changed.maneid) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "maneid", changes.maneid ? [changes.maneid] : [], replace);
appliedAnything = true;
}
if (changes.changed.etiket) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "e", changes.etiket, replace);
appliedAnything = true;
}
if (changes.changed.kategori) {
content = insertLeadingSenseTpl(content, language, sectionType, sectionOrdinal, definitionLevel, "K", changes.kategori, replace);
appliedAnything = true;
}
SENSE_GROUPS.forEach(function (group) {
if (changes.changed["word:" + group.key]) {
content = insertSenseWords(content, language, sectionType, sectionOrdinal, definitionLevel, group.key, changes.words[group.key], replace);
appliedAnything = true;
}
});
if (!appliedAnything) {
throw new SubmitAbort("Tiştek nehat guhartin.");
}
return { content: content, warnings: warnings };
}
// Plain removable-tag field, used for Etîket and Kategorî (simple value lists with no per-word modifiers).
// Enter or "," commits the current text as a tag; Backspace on an empty input removes the last tag;
// values() also commits whatever is still typed but not yet confirmed.
function buildTagField(placeholder) {
var $input = $("<input type='text' class='dsb-input dsb-tag-input'>").attr("placeholder", placeholder);
var $element = $("<span class='dsb-tagfield'></span>").append($input);
function currentValues() {
var vals = [];
$element.children(".dsb-tag").each(function () {
vals.push($(this).data("value"));
});
return vals;
}
function addTag(val) {
val = $.trim(val || "");
if (!val || currentValues().indexOf(val) !== -1) {
return;
}
var $tag = $("<span class='dsb-tag'></span>").text(val).data("value", val);
var $remove = $("<button type='button' class='dsb-tag-remove' title='Jê bibe'>×</button>");
$remove.on("click", function () {
$tag.remove();
$input.trigger("focus");
});
$tag.append($remove);
$tag.insertBefore($input);
}
$input.on("keydown", function (e) {
if (e.which === 13 || e.which === 188) { // Enter or comma
e.preventDefault();
addTag($input.val());
$input.val("");
} else if (e.which === 8 && !$input.val()) {
$element.children(".dsb-tag").last().remove();
}
});
$input.on("blur", function () {
addTag($input.val());
$input.val("");
});
return {
$element: $element,
// Replaces every tag, for prefilling from what the sense already has.
setValues: function (vals) {
$element.children(".dsb-tag").remove();
vals.forEach(function (v) {
addTag(v);
});
$input.val("");
},
values: function () {
addTag($input.val());
$input.val("");
return currentValues();
},
focus: function () {
$input.trigger("focus");
}
};
}
// The template-param dialog (openTemplateDialog/buildTemplateParamForm) moved to wikt.cite, shared with
// start.js's own Etîmolojî and Çavkanî pickers: cite.openTemplateDialog / cite.buildTemplateParamForm.
// Each new example queued is its own entry, so several can be waiting at once.
var exampleSeq = 0;
// Takes the save over from add-examples while its form is open for one sense: its "Qeyd bike" becomes
// "Lê zêde bike" and the edit joins the pending queue instead of publishing on its own, so examples and
// whatever else is queued go up as one edit. Installed on every open, so the handler always closes over
// the sense whose form is actually open. ctx: {$li, $h3, language, sectionType, sectionOrdinal,
// definitionLevel, $save, $status, afterQueue}.
function takeOverExampleSave(ctx) {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
return; // older add-examples: it publishes on its own, as before
}
// Lets the form read an example as the queue has already changed it.
wikt.addExamples.pendingContent = queuedContent;
wikt.addExamples.setSubmitHandler(function (job) {
exampleSeq++;
queueChange(
placeLabel(ctx.language, ctx.sectionType, ctx.$h3),
// add-examples' own summary, so the combined edit still credits the tool and names its
// version. The trailing stop goes: this is one item in a list, not a sentence of its own.
(job.summary || "nimûne/jêgirtin hate zêdekirin").replace(/\.\s*$/, ""),
function (pageContent) {
// add-examples builds against a revision object, and returns {text, summary}.
var built = job.transform({ content: pageContent });
return { content: built.text, warnings: [] };
},
ctx.$save,
ctx.$status,
// Drawn like any other queued change, in the sense it belongs to -- except a removal,
// which strikes the line through where it sits, as "Maneyê rake" does for a sense.
function () {
if (job.removes && ctx.itemEl) {
var $item = $(ctx.itemEl).addClass("dsb-example-doomed");
return function () {
$item.removeClass("dsb-example-doomed");
};
}
return previewSense(ctx.$li, ctx.language, ctx.sectionType, ctx.sectionOrdinal, ctx.definitionLevel);
},
// An edit to an example already on the page brings its own key, so editing it again replaces it.
{ key: job.key || ("example:" + ctx.sectionType + "|" + ctx.sectionOrdinal + "|" +
ctx.definitionLevel.join(".") + "|" + exampleSeq), sensePath: ctx.definitionLevel }
);
job.done();
if (ctx.afterQueue) {
ctx.afterQueue();
}
});
}
// Opens add-examples' form on one example already under a sense -- dsb.js's pencil on it. Its save joins
// the queue like any other change.
function editExample($li, $h3, language, sectionType, sectionOrdinal, definitionLevel, itemEl) {
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $(), $status: $(),
itemEl: itemEl, afterQueue: openQueueBox });
if (!(wikt.addExamples && wikt.addExamples.editFor && wikt.addExamples.editFor($li.get(0), itemEl))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
}
// Builds the whole "guhartina pêşketî" panel into $quickRow: gloss and the two most-used word lists
// (Hevmane/Dijmane) sit directly in the picker row; the remaining word lists, maneid, etîket, kategorî
// and Nimûne/jêgirtin live behind a "Hevmane û Bêhtir" dropdown; one shared "Lê zêde bike" queues everything the
// user filled in as a single edit via applyAdvancedChanges() and queueChange(). Nimûne/jêgirtin is an
// immediate proxy-click into a different gadget's own form, not part of this combined save.
// pending, when given, means this panel composes a sense that is not on the page yet: it edits no
// line, and its save queues one insertion carrying the definition, its word lists and its
// quotations. Everything the user sees is the panel's own, so a new sense is edited exactly like a
// saved one. See openNewSenseRow().
function buildAdvancedPanel($quickRow, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel, isLastSense, pending) {
// "#:" lines for a pending sense's quotations, in the order add-examples handed them over.
var pendingQuotes = pending ? pending.state.quotes.slice() : [];
var $pendingQuotes = $("<div class='dsb-new-sense-quotes'></div>");
// "+" marks the actions that add something, matching the dropdown's own "+ Hevmane" labels.
var $nimune = $("<a href='#' class='dsb-quick-link'></a>").text("+ Nimûne/jêgirtin");
$nimune.on("click", function (e) {
e.preventDefault();
if (pending) {
collectQuotation();
$moreMenu.slideUp();
return;
}
takeOverExampleSave({ $li: $li, $h3: $h3, language: language, sectionType: sectionType,
sectionOrdinal: sectionOrdinal, definitionLevel: definitionLevel, $save: $save, $status: $status });
// Toggle, so clicking this a second time closes the form again. add-examples builds nothing
// on the page of its own, so this is the only way its form opens.
var drive = wikt.addExamples.toggleFor || wikt.addExamples.openFor;
if (!(drive && drive($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
}
$moreMenu.slideUp();
});
// The form has to be opened on a sense that exists, so it opens on the one this row hangs off;
// only the line it builds is kept, and the form itself is moved under the row, title and all.
function collectQuotation() {
if (!(wikt.addExamples && wikt.addExamples.setSubmitHandler)) {
mw.notify("Amûra nimûneyan ne berdest e.");
return;
}
wikt.addExamples.setSubmitHandler(function (job) {
if (!job.line) {
job.fail("Ev guhartoya amûra nimûneyan nikare rêzê bide.");
return;
}
pendingQuotes.push(job.line.replace(/^#+/, ""));
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text(job.line));
job.done();
});
if (!(wikt.addExamples.openFor && wikt.addExamples.openFor($li.get(0)))) {
mw.notify("Amûra nimûneyan li vê maneyê nehat dîtin.");
return;
}
// Its title is a list item in the sense's own example list; taken out of that list so it
// does not read as a numbered sense of its own.
var $title = $li.find(".add-example-form-title").first().closest("li");
var $form = $li.find(".add-example-form").first();
if ($title.length) {
$pendingQuotes.after($("<div class='dsb-new-sense-form-title'></div>")
.append($title.contents()));
$title.remove();
}
if ($form.length) {
$quickRow.find(".dsb-new-sense-form-title").first().after($form);
}
}
// getters holds one function per currently-open box (key -> () => value); collectChanges() only ever
// reads from a box the user actually clicked to open.
var getters = {};
// What the sense currently holds, fetched once and shared by every box that prefills from it.
// collectChanges() also diffs against it, so an untouched box never queues a change.
var original = null;
var sensePromise = null;
function loadSense() {
if (!sensePromise) {
if (pending) {
original = { gloss: pending.state.text || "", maneid: pending.state.maneid || "",
etiket: pending.state.etiket || [], kategori: pending.state.kategori || [],
words: pending.state.lists || {} };
sensePromise = $.Deferred().resolve(original).promise();
} else {
sensePromise = fetchSenseFields(language, sectionType, sectionOrdinal, definitionLevel).then(function (fields) {
original = fields;
return fields;
});
}
}
return sensePromise.then(withQueued);
}
// The change already queued for this sense, as the fields it writes. Read back from the queue
// rather than kept in a variable here, so dropping the entry from the queue box also drops this.
function queuedSenseChanges() {
if (pending) {
return null;
}
var key = senseQueueKey();
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === key && entry.senseChanges) {
found = entry.senseChanges;
}
});
return found;
}
// The sense with whatever is queued for it written over the top (nothing queued: as the page has it).
function withQueued(fields) {
return overlaySenseFields(fields, queuedSenseChanges());
}
function sameList(a, b) {
return (a || []).join("\u0000") === (b || []).join("\u0000");
}
// -- gloss ("Maneyê biguhêre" -- the one box that EDITS the sense's wording; everything else here only ADDS) --
function addGlossField() {
var $fields = $("<div class='dsb-gloss-fields'></div>");
var $textarea = $("<textarea class='dsb-input dsb-edit-gloss' rows='2'></textarea>")
.attr("placeholder", util.msg.loading).prop("disabled", true);
var $autoLink = $("<button type='button' class='dsb-autolink'>Oto-lînk bike</button>").prop("disabled", true);
var $toRaw = $("<a href='#' class='dsb-quick-link dsb-gloss-raw'>Wekî wîkîtekst biguhêre</a>").hide();
var $status = $("<span class='dsb-status'></span>");
var originalGloss = null;
var tplForm = null; // set while the parameter boxes are showing instead of the raw line
var tplCall = null;
// The gloss of a non-Kurdish entry is a translation followed by {{îzah|…}} explaining it. That
// is one field on the page, so it stays one box here; the explanation opens beside it only when
// asked for, and the two are joined back into a single gloss on save. A Kurdish sense is its
// own definition, so it is never offered one -- though a ku gloss that somehow already carries
// an {{îzah}} still opens its box below, rather than being edited blind.
var offerIzah = language !== "ku";
var $izah = $("<textarea class='dsb-input dsb-edit-izah' rows='2'></textarea>")
.attr("placeholder", "Îzaha maneyî… ji bo school-a îngilîzî 'cihê tehsîlgirtinê'");
// A non-Kurdish gloss is a translation and its explanation, so both boxes open together. A Kurdish
// sense is its own definition and gets no îzah box, unless its gloss already carries one.
var $izahWrap = $("<div class='dsb-izah-wrap'></div>")
.append($("<label class='dsb-adv-label'>Îzaha maneyî</label>").append($izah))
.toggle(offerIzah);
$fields.append($textarea);
// The template form edits one call inside the line, so its result is spliced back into the rest.
function currentGloss() {
if (tplForm) {
return $.trim(originalGloss.slice(0, tplCall.start) + tplForm.values() + originalGloss.slice(tplCall.end));
}
return joinGloss($.trim($textarea.val()), $.trim($izah.val()));
}
function showRaw() {
tplForm = null;
tplCall = null;
$fields.empty().append($textarea);
$autoLink.show();
$toRaw.hide();
}
loadSense().then(
function (fields) {
originalGloss = fields.gloss;
// An existing gloss that already ends in {{îzah|…}} comes apart into the two boxes, so
// editing it does not mean editing template syntax by hand.
var parts = util.splitTercimeIzah(fields.gloss);
$textarea.val(parts ? $.trim(parts.tercime) : fields.gloss)
.prop("disabled", false)
// A non-Kurdish gloss is a translation; the box under it takes the explanation.
.attr("placeholder", offerIzah ? util.msg.tercimePrompt : "Maneyê binivîse…");
if (parts) {
$izah.val($.trim(parts.izah));
$izahWrap.show();
}
$autoLink.prop("disabled", false);
var target = glossTemplateTarget(fields.gloss);
if (!target) {
return;
}
$.when(fetchTemplateData(target.name), cite.templateAliases(target.name)).then(function (td, aliases) {
// The fetch is async; leave the raw box alone if it has already been typed into.
if ($.trim($textarea.val()) !== $.trim(originalGloss)) {
return;
}
tplCall = target;
tplForm = cite.buildTemplateParamForm(target, td, aliases);
$fields.empty().append(tplForm.$element);
$autoLink.hide(); // linkifying a bare template call makes no sense
// A gloss that is one template call carries no separate explanation.
$izahWrap.hide();
$toRaw.show();
});
},
function (codeOrAbort, result) {
$textarea.attr("placeholder", "");
var message = codeOrAbort instanceof SubmitAbort ? codeOrAbort.message : "Nikare maneya heyî bar bike: " + apiErrorMessage(codeOrAbort, result);
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(message);
}
);
$autoLink.on("click", function () {
var mane = $.trim($textarea.val());
var izah = $.trim($izah.val());
if (!mane && !izah) {
return;
}
$autoLink.prop("disabled", true);
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
linkifyGlossParts(mane, izah, function (linkedMane, linkedIzah, error) {
$textarea.val(linkedMane);
$izah.val(linkedIzah);
$autoLink.prop("disabled", false);
if (error) {
$status.addClass("dsb-status-error").text(error);
} else {
$status.text("");
}
});
});
$toRaw.on("click", function (e) {
e.preventDefault();
$textarea.val(currentGloss()); // read the form before showRaw() discards it
showRaw();
});
getters.gloss = function () {
var v = currentGloss();
// A pending sense has no line on the page to differ from: whatever is in the box is the sense.
return (pending || (v && v !== originalGloss)) ? (v || null) : null;
};
return $("<div class='dsb-adv-group'></div>").append(
$("<div class='dsb-adv-label'></div>").text("Mane"),
$fields,
$izahWrap,
$("<div class='dsb-form-actions'></div>").append($autoLink, $toRaw, $status)
);
}
// -- one SENSE_GROUPS word-list box (Hevmane/Dijmane/etc.) --
function addWordField(group) {
var field = buildWordListField(group.label + "…");
loadSense().then(function (fields) {
var current = fields.words[group.key];
if (current && current.length) {
field.setValues(current);
}
});
getters["word:" + group.key] = function () {
return field.values();
};
// The hint is senseGroups' own, so the box carries the same example sentence the wizard puts
// beside its copy of this list.
var $label = $("<div class='dsb-adv-label'></div>").text(group.label);
if (group.hint) {
$label.append($("<span class='dsb-adv-hint'></span>").text(group.hint));
}
return $("<div class='dsb-adv-group'></div>").append($label, field.$element);
}
// -- maneid (single value; re-adding one that already exists is rejected -- see applyAdvancedChanges) --
// "Maneyeke din" opens a row of its own directly under this sense, so the list numbers it as the
// sense it is about to become, and it carries its own buttons: everything it collects -- the
// definition, its word lists, its quotations -- goes onto the page as a single insertion, because a
// sense that is not saved yet has no line number for a second change to find (see insertNewSense).
var newSenseKey = "newSense:" + definitionLevel.join(".");
function queuedNewSense() {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === newSenseKey) {
found = entry;
}
});
return found;
}
function dropQueuedNewSense() {
dsbQueue.entries.slice().forEach(function (entry) {
if (entry.key === newSenseKey) {
dropQueueEntry(entry);
}
});
}
// A sense that is not on the page yet is edited with this same panel, in a row of its own: same
// picker, same dropdown, same buttons. Only the save differs -- it writes an insertion rather
// than editing a line -- and the row renders what the insertion will say.
function openNewSenseRow(state) {
var existing = $quickRow.data("dsbNewSenseRow");
if (existing && existing.closest("body").length && !state) {
existing.find("textarea").first().trigger("focus");
return;
}
if (existing) {
existing.remove();
}
if (state) {
// The rendered row belongs to the queue entry: hide it while the box is open, and leave
// the entry alone so cancelling can put it back.
$li.nextAll("li.dsb-new-sense-pending").first().remove();
}
openQueueBox();
var $row = $("<li class='dsb-sense-preview dsb-new-sense-row'></li>");
var $panel = $("<div class='dsb-quick-row dsb-new-sense'></div>");
$row.append($panel);
$li.after($row);
$quickRow.data("dsbNewSenseRow", $row);
buildAdvancedPanel($panel, $li, $h3, language, sectionType, sectionOrdinal, definitionLevel,
isLastSense, {
state: state || { text: "", lists: {}, quotes: [], maneid: "", etiket: [], kategori: [] },
key: newSenseKey,
$row: $row,
close: function () {
$row.remove();
$quickRow.removeData("dsbNewSenseRow");
hideQueueIfIdle();
},
reopen: openNewSenseRow,
queued: queuedNewSense,
drop: dropQueuedNewSense
});
$panel.show();
$panel.find("textarea").first().trigger("focus");
}
function addManeidField() {
var $input = $("<input type='text' class='dsb-input' placeholder='id…'>");
loadSense().then(function (fields) {
$input.val(fields.maneid);
});
getters.maneid = function () {
return $.trim($input.val());
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Maneid"), $input);
}
// -- etîket / kategorî --
function addEtiketField() {
var field = buildTagField("Etîket…");
loadSense().then(function (fields) {
field.setValues(fields.etiket);
});
getters.etiket = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Etîket"), field.$element);
}
function addKategoriField() {
var field = buildTagField("Kategorî…");
loadSense().then(function (fields) {
field.setValues(fields.kategori);
});
getters.kategori = function () {
return field.values();
};
return $("<div class='dsb-adv-group'></div>").append($("<div class='dsb-adv-label'></div>").text("Kategorî"), field.$element);
}
// -- the picker row: the gloss and Nimûne/jêgirtin links sit directly in the row; every word group and
// the rest live behind a "Hevmane û Bêhtir" dropdown. Clicking any link builds and appends its box
// to $openBoxes and hides that link; the box's own "×" removes it, clears its getter, and un-hides the
// link -- several boxes can be open at once. No "Beşek (====)" entry here: buildSubsectionPanel()'s
// own "+ Binebeşekê lê zêde bike" trigger already covers page-level subsections. ----
var FIELD_DEFS = [{ key: "gloss", label: GLOSS_LABEL, build: addGlossField, primary: true }]
.concat(SENSE_GROUPS.map(function (group) {
return { key: "word:" + group.key, label: group.label, build: function () { return addWordField(group); }, primary: false };
}))
.concat([
{ key: "maneid", label: "Maneid", build: addManeidField, primary: false },
{ key: "etiket", label: "Etîket", build: addEtiketField, primary: false },
{ key: "kategori", label: "Kategorî", build: addKategoriField, primary: false }
]);
// Every sense offers this: the new one lands directly below whichever sense asked for it (see
// insertNewSense), so it is no longer only the last one that can add to the end.
// No build(): its link opens a row of its own (openNewSenseRow), not a box in this panel.
FIELD_DEFS.push({ key: "newSense", label: util.msg.addAnotherSense, primary: false });
var $picker = $("<div class='dsb-field-picker'></div>");
var $moreMenu = $("<div class='dsb-more-menu'></div>").hide();
var $openBoxes = $("<div class='dsb-open-boxes'></div>");
// $actions (built below) stays hidden until at least one field box is actually open -- otherwise
// "Lê zêde bike" sits there enabled with nothing to queue, which just produces an empty no-op change.
var openBoxCount = 0;
function updateActionsVisibility() {
$actions.toggle(openBoxCount > 0);
}
function makeFieldLink(def) {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-pick'></a>").text(def.key === "gloss" ? def.label : "+ " + def.label);
$link.on("click", function (e) {
e.preventDefault();
if (def.key === "newSense") {
$moreMenu.slideUp();
openNewSenseRow();
return;
}
openQueueBox();
var $box = def.build();
var $remove = $("<a href='#' class='dsb-box-remove' title='Rake'>×</a>");
$remove.on("click", function (e2) {
e2.preventDefault();
delete getters[def.key];
$box.remove();
$link.show();
openBoxCount--;
updateActionsVisibility();
});
$box.prepend($remove);
$openBoxes.append($box);
util.initManualResize($box);
$link.hide();
$moreMenu.slideUp();
openBoxCount++;
updateActionsVisibility();
});
return $link;
}
// Destructive, so it lives at the bottom of the dropdown behind a confirmation, and shares the
// sense's queue key: deleting supersedes any edit still pending on the same sense.
function makeDeleteLink() {
var $link = $("<a href='#' class='dsb-quick-link dsb-field-delete'>Maneyê rake</a>");
$link.on("click", function (e) {
e.preventDefault();
$moreMenu.slideUp();
if (pending) {
// Nothing to remove from the page: the sense only exists in the queue.
pending.drop();
pending.close();
return;
}
OO.ui.confirm("Ev mane û hemû rêzên bin wê dê werin rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueChange(
placeLabel(language, sectionType, $h3),
"maneyek hate rakirin",
function (baseContent) {
return removeSense(baseContent, language, sectionType, sectionOrdinal, definitionLevel);
},
$save,
$note,
function () {
$li.addClass("dsb-sense-doomed");
return function () {
$li.removeClass("dsb-sense-doomed");
};
},
{ key: senseQueueKey(), sensePath: definitionLevel, senseLanguage: language,
removesSense: true }
);
$note.show();
});
});
return $link;
}
// Only the links the row actually shows are built now; the dropdown's nine are 43% of the work and
// nobody sees them until "Hevmane û Bêhtir" is clicked, so buildMenuLinks() waits for that.
FIELD_DEFS.forEach(function (def) {
if (!def.primary || (pending && def.key === "newSense")) {
return;
}
$picker.append(makeFieldLink(def));
if (def.key === "gloss") {
$picker.append($nimune); // directly after "Maneyê biguhêre"
}
});
// Three sections, ruled apart: what labels this sense, the word lists that hang off it, and adding
// another sense. "Maneyê rake" is appended after these by makeDeleteLink() and so closes the last
// one. The word lists follow senseGroups' "order" -- that array's own order is the order the "#:"
// lines are written to the page in, which is not a reading order; the wizard's boxes read the same
// field, so both gadgets list them alike. Anything not named here still appears, after these, so a
// field added later is never silently dropped from the menu.
var MENU_GROUPS = [
["etiket", "kategori", "maneid"],
SENSE_GROUPS.slice().sort(function (a, b) {
return (a.order || 0) - (b.order || 0);
}).map(function (g) {
return "word:" + g.key;
}),
["newSense"]
];
var menuBuilt = false;
function buildMenuLinks() {
if (menuBuilt) {
return;
}
menuBuilt = true;
var byKey = {};
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !(pending && def.key === "newSense")) {
byKey[def.key] = def;
}
});
var placed = {};
MENU_GROUPS.forEach(function (group, groupIndex) {
var defs = group.map(function (key) {
return byKey[key];
}).filter(Boolean);
defs.forEach(function (def, i) {
placed[def.key] = true;
var $link = makeFieldLink(def);
// The rule goes on the first entry of every group after the first.
if (groupIndex > 0 && i === 0) {
$link.addClass("dsb-menu-group-start");
}
$moreMenu.append($link);
});
});
FIELD_DEFS.forEach(function (def) {
if (!def.primary && !placed[def.key] && !(pending && def.key === "newSense")) {
$moreMenu.append(makeFieldLink(def));
}
});
$moreMenu.append(makeDeleteLink());
}
// Lives inside $picker (not appended straight to $quickRow) so .dsb-more-menu's CSS can position it as
// an overlay anchored to $picker, instead of pushing $openBoxes/the rest of the page down when it opens.
var $moreArrow = $("<span class='dsb-more-arrow'></span>").text("▸");
var $moreToggle = $("<a href='#' class='dsb-quick-link dsb-more-toggle'></a>")
.text(util.msg.moreMenu + " ").append($moreArrow);
$moreToggle.on("click", function (e) {
e.preventDefault();
buildMenuLinks();
$moreMenu.slideToggle(function () {
$moreArrow.text($moreMenu.is(":visible") ? "▾" : "▸");
});
});
$picker.append($moreToggle, $moreMenu);
// Outside clicks are handled by one shared document listener (see closeMoreMenus), not one per panel:
// hovering a sense builds its panel, so a long entry would otherwise accumulate dozens of them.
$moreMenu.data("dsbArrow", $moreArrow);
// -- combined save: whatever boxes are open right now, queued together via queueChange(); the actual
// save happens when the user clicks "Guhartinan qeyd bike" on the floating queue box. ----
// Small "not saved yet" preview of everything this queued change will do, appended right under the
// sense's gloss (plain text, not a real render of {{hevmane}}/etc.).
function collectChanges() {
// Against what the boxes were filled from, queued change and all -- a box reopened and left
// alone writes nothing, and one the user has just emptied counts as emptied.
var base = withQueued(original || { maneid: "", etiket: [], kategori: [], words: {} });
var changed = {};
var words = {};
SENSE_GROUPS.forEach(function (group) {
var getter = getters["word:" + group.key];
words[group.key] = getter ? getter() : [];
if (getter && !sameList(words[group.key], base.words[group.key])) {
changed["word:" + group.key] = true;
}
});
var maneid = getters.maneid ? getters.maneid() : "";
var etiket = getters.etiket ? getters.etiket() : [];
var kategori = getters.kategori ? getters.kategori() : [];
if (getters.maneid && maneid !== (base.maneid || "")) {
changed.maneid = true;
}
if (getters.etiket && !sameList(etiket, base.etiket)) {
changed.etiket = true;
}
if (getters.kategori && !sameList(kategori, base.kategori)) {
changed.kategori = true;
}
return {
gloss: getters.gloss ? getters.gloss() : null,
maneid: maneid,
etiket: etiket,
kategori: kategori,
words: words,
changed: changed
};
}
// One insertion carrying the whole sense: its leading calls and definition on the "#" line, then
// a "#:" line per word list and per quotation, in the order the wizard writes them.
function newSenseWikitext(changes) {
var leading = LEADING_TPL_ORDER.map(function (key) {
var values = key === "maneid" ? (changes.maneid ? [changes.maneid] : [])
: key === "e" ? changes.etiket
: key === "K" ? changes.kategori : [];
return values && values.length
? "{{" + key + "|" + language + "|" + values.join("|") + "}}" : null;
}).filter(Boolean).join(" ");
var lines = [];
SENSE_GROUPS.forEach(function (group) {
var words = changes.words[group.key] || [];
if (words.length) {
lines.push(": {{" + group.key + "|" + language + "|" + words.join("|") + "}}");
}
});
lines = lines.concat(pendingQuotes);
return { text: (leading ? leading + " " : "") + $.trim(changes.gloss || ""), extras: lines };
}
function pendingState(changes) {
return { text: $.trim(changes.gloss || ""), lists: changes.words, quotes: pendingQuotes.slice(),
maneid: changes.maneid, etiket: changes.etiket, kategori: changes.kategori };
}
function queueNewSense(changes) {
if (!$.trim(changes.gloss || "")) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Maneya nû vala ye.");
return;
}
var built = newSenseWikitext(changes);
var held = pendingState(changes);
var block = ["# " + built.text].concat(built.extras.map(function (extra) {
return "#" + extra;
})).join(NEWLINE);
pending.close();
queueChange(
placeLabel(language, sectionType, $h3),
"maneyeke nû hate zêdekirin",
function (baseContent) {
return { content: insertNewSense(baseContent, language, sectionType, sectionOrdinal,
definitionLevel, built.text, built.extras), warnings: [] };
},
$save,
$status,
// Drawn fresh on every call: redrawPreviews() runs this again whenever anything else
// leaves the queue, and the row it drew last time is gone by then.
function () {
var $pending = $("<li class='dsb-sense-preview dsb-new-sense-pending'></li>");
$li.after($pending);
var $edit = $("<span class='dsb-example-toolbar'></span>").append(
$("<a href='#' class='dsb-toggle'></a>").attr("title", GLOSS_LABEL)
.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
pending.reopen(held);
}));
renderWikitext(block).then(function (html) {
if (!$pending.closest("body").length) {
return;
}
var $content = $("<div class='mw-parser-output'></div>")
.html(parsedSenseItem(html, 1).html);
var $block = $content.children("dl, ul, ol").first();
if ($block.length) {
$block.before($edit);
} else {
$content.append($edit);
}
$pending.empty().append($content);
announceContent($pending);
}, function () {
$pending.empty().append($("<span></span>").text(built.text), $edit);
});
return function () {
$pending.remove();
};
},
{ key: pending.key, sensePath: definitionLevel, senseLanguage: language }
);
}
// Both the field save and "Maneyê rake" queue under this, so the later one supersedes the earlier.
function senseQueueKey() {
return "sense:" + sectionType + "|" + sectionOrdinal + "|" + definitionLevel.join(".");
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
// Where queueChange() writes its confirmation for actions that have no box of their own.
var $note = $("<div class='dsb-status dsb-quick-note'></div>").hide();
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status).hide();
// Emptying the boxes is what says the change has been taken: they were left open with their
// text in them, so "Lê zêde bike" could be clicked again and queue the same values twice.
function closeOpenBoxes() {
$openBoxes.find(".dsb-box-remove").trigger("click");
}
$cancel.on("click", function () {
if (pending) {
pending.close();
if (pending.queued()) {
redrawPreviews();
}
return;
}
// Close the boxes too, not just the row. Hiding alone left every field still open with its
// text in it, so the next click on the pencil reopened the half-filled panel that had just
// been cancelled. Each box's own "x" already clears its getter, restores its link and updates
// the counter, so those are triggered rather than repeating the teardown here.
$openBoxes.find(".dsb-box-remove").trigger("click");
$note.hide().text("");
// dsb.js owns the pencil's pinned state; tell it rather than hiding behind its back, or the
// next click on the pencil would only toggle "pinned" off and appear to do nothing.
$quickRow.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var changes = collectChanges();
function afterLinkify() {
if (pending) {
queueNewSense(changes);
return;
}
// This save supersedes any change still queued for the same sense, so it has to write that
// one's fields too: the boxes it was made from are closed by now and collectChanges() cannot
// see them.
var merged = mergeSenseChanges(queuedSenseChanges(), changes);
var parts = senseChangeLabels(merged);
queueChange(
placeLabel(language, sectionType, $h3),
parts.length
? parts.join(", ") + (parts.length > 1 ? " hatin guhartin" : " hate guhartin")
: "mane hate guhartin",
function (baseContent) {
return applyAdvancedChanges(baseContent, language, sectionType, sectionOrdinal, definitionLevel, merged);
},
$save,
$status,
function () {
return previewSense($li, language, sectionType, sectionOrdinal, definitionLevel);
},
// Re-saving this same sense supersedes its pending change rather than queuing another.
{ key: senseQueueKey(), sensePath: definitionLevel, senseChanges: merged }
);
closeOpenBoxes();
}
$save.prop("disabled", true);
// Auto-linkify the gloss as part of this save, but only a half nobody has linked by hand.
// Split back into its two halves first, so the explanation is linked on its own rather than
// sitting unseen inside {{îzah|…}}.
if (changes.gloss) {
$status.removeClass("dsb-status-ok dsb-status-error").text("Tê lînkkirin…");
var glossParts = util.splitTercimeIzah(changes.gloss);
linkifyGlossParts(
glossParts ? $.trim(glossParts.tercime) : changes.gloss,
glossParts ? $.trim(glossParts.izah) : "",
function (linkedMane, linkedIzah, error) {
// The change is queued either way, so a gloss that came back empty is not written
// back -- that would save the sense with its wording gone.
var joined = joinGloss(linkedMane, linkedIzah);
if ($.trim(joined)) {
changes.gloss = joined;
}
if (error) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text(error);
}
afterLinkify();
},
true
);
} else {
afterLinkify();
}
});
// The links sit under the buttons, so each open box pushes them down rather than leaving them far above.
$quickRow.empty().append($openBoxes, $actions, $picker, $note);
if (pending) {
// The gloss box is what a new sense is: it opens with the row, and the quotations the row
// collects sit above the actions. Done here, once $actions exists.
$quickRow.append($pendingQuotes);
pending.state.quotes.forEach(function (extra) {
$pendingQuotes.append($("<div class='dsb-new-sense-quote'></div>").text("#" + extra));
});
$picker.children(".dsb-field-pick").filter(function () {
return $(this).text() === GLOSS_LABEL;
}).trigger("click");
}
}
module.exports = {
buildAdvancedPanel: buildAdvancedPanel,
editExample: editExample
};
}());
// </nowiki>
icfmxtkem8c319tlk41ihyi5g5ga0ps
MediaWiki:Gadget-wikt.dsb-subsection.js
8
1359510
6865613
2026-10-23T22:08:36Z
Wikihez
7344
DefSideBoxes: the subsection panel as a module of its own, loaded with its pencil
6865613
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-subsection.js]]
*
* The "====" subsection editors of DefSideBoxes, loaded by wikt.dsb from a subsection's pencil. The queue,
* previews and shared helpers are in [[MediaWiki:Gadget-wikt.dsb-panels.js]].
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var cite = require("ext.gadget.wikt.cite");
var util = edit.util;
var wt = edit.wt;
var startData = edit.data;
var escapeRegExp = util.escapeRegExp;
var parseTemplateCalls = wt.parseTemplateCalls;
var parseWordListCalls = wt.parseWordListCalls;
var namedParam = wt.namedParam;
var withNamedParam = wt.withNamedParam;
var buildWordListCall = wt.buildWordListCall;
var parseBulletWordList = wt.parseBulletWordList;
var PAGE_SUBSECTIONS = sections.PAGE_SUBSECTIONS;
var headingOuter = sections.headingOuter;
var headingText = sections.headingText;
var shared = require("ext.gadget.wikt.dsb-panels").shared;
var SubmitAbort = shared.SubmitAbort;
var buildWordListField = shared.buildWordListField;
var dsbQueue = shared.dsbQueue;
var fetchPageContent = shared.fetchPageContent;
var fillWithRendered = shared.fillWithRendered;
var headingPosition = shared.headingPosition;
var hideQueueIfIdle = shared.hideQueueIfIdle;
var langLabel = shared.langLabel;
var locateSection = shared.locateSection;
var makeSearchable = shared.makeSearchable;
var placeLabel = shared.placeLabel;
var queueChange = shared.queueChange;
var queuedContent = shared.queuedContent;
var translationEditor = shared.translationEditor;
// The column word-list template new sections are written with; the rest of wordlistTemplates are
// spellings the parser also accepts.
var WORDLIST_TEMPLATE = startData.wordlistTemplates[0];
// ---- {{werger-ser}} / {{werger-bin}}: one translation box ----
// The pair brackets the rows; the translation editor gadget writes the rows themselves onto the
// rendered page, so this only ever builds (and reads back) the opening call.
// Parameters, from Modul:translations' "trans-top": 1 = the sense shown in the box head, 2 = a note in
// parentheses after it, id = anchor override (defaults to 1), stûn = column count, column-width. A
// box's params are {"1": …, "2": …, name: …}: every named parameter it has is kept, known or not.
// The opening call alone, under the template name the page already uses (a test copy stays one).
function buildWergerTop(params, name) {
var parts = [name || "werger-ser"];
// Positional slots up to the last one filled: 2 can only be reached past 1.
var last = 0;
Object.keys(params).forEach(function (key) {
if (/^\d+$/.test(key) && params[key]) {
last = Math.max(last, +key);
}
});
for (var n = 1; n <= last; n++) {
var value = params[String(n)] || "";
// An "=" would make it a named parameter.
parts.push(value.indexOf("=") !== -1 ? n + "=" + value : value);
}
Object.keys(params).forEach(function (key) {
if (!/^\d+$/.test(key) && params[key]) {
parts.push(key + "=" + params[key]);
}
});
return "{{" + parts.join("|") + "}}";
}
function buildWergerBlock(params) {
return buildWergerTop(params) + "\n{{werger-bin}}";
}
// Every box's opening call in text, in order, as {start, end, name, params}; params is the same
// {1, 2, id, ...} object buildWergerTop() takes. Scans for the matching close rather than the first
// "}}": a gloss can hold a template of its own.
function wergerBoxes(text) {
var out = [];
// {{werger-bnr-h}} is a box too: its head also points to other pages' boxes.
var re = /\{\{\s*(werger-(?:ser|bnr-h)(?:\/ceribandin)?)\s*(?=[|}])/g;
var m;
while ((m = re.exec(text))) {
var depth = 1;
var start = m.index + m[0].length;
var end = -1;
for (var i = start; i < text.length - 1; i++) {
var two = text.substr(i, 2);
if (two === "{{") {
depth++; i++;
} else if (two === "}}") {
depth--; i++;
if (!depth) {
end = i + 1;
break;
}
}
}
if (end === -1) {
break;
}
var body = text.charAt(start) === "|" ? text.slice(start + 1, end - 2) : "";
out.push({ start: m.index, end: end, name: m[1], params: parseWergerArgs(body) });
re.lastIndex = end;
}
return out;
}
function parseWergerArgs(body) {
var out = {};
if (!body) {
return out;
}
var positional = 0;
splitTemplateArgs(body).forEach(function (arg) {
var named = /^\s*([^=|{}\[\]<>]+?)\s*=([\s\S]*)$/.exec(arg);
if (named) {
out[named[1]] = $.trim(named[2]);
} else {
out[String(++positional)] = $.trim(arg);
}
});
return out;
}
// The section text with the given boxes' opening calls rewritten: rows of {box, params}, where box is
// the box's place among wergerBoxes(text).
function rewriteWergerTops(text, rows) {
var boxes = wergerBoxes(text);
rows.slice().sort(function (a, b) {
return b.box - a.box;
}).forEach(function (row) {
var box = boxes[row.box];
if (!box) {
throw new SubmitAbort("Qutiya tercimeyê ya hejmar " + (row.box + 1) + " nehate dîtin.");
}
text = text.slice(0, box.start) + buildWergerTop(row.params, box.name) + text.slice(box.end);
});
return text;
}
// Splits on "|" at brace/bracket depth 0, so a value containing a template or a link stays whole.
function splitTemplateArgs(body) {
var args = [];
var depth = 0;
var current = "";
for (var i = 0; i < body.length; i++) {
var two = body.substr(i, 2);
if (two === "{{" || two === "[[") {
depth++; current += two; i++; continue;
}
if (two === "}}" || two === "]]") {
depth--; current += two; i++; continue;
}
if (body[i] === "|" && depth <= 0) {
args.push(current); current = ""; continue;
}
current += body[i];
}
args.push(current);
return args;
}
// Canonical ku.wiktionary "====" subsection order, used only as insertPageSubsection()'s insertion rank
// (see subsectionRankForHeader()). Wider than PAGE_SUBSECTIONS: it also covers subsections the gadget
// cannot create but still has to order around.
var SUBSECTION_ORDER = startData.subsectionOrder;
// Bilêvkirin sits above the parts of speech and belongs to the whole language, so naming the section
// it was opened from would say something untrue about what the edit touches.
function subsectionPlaceLabel(subsection, language, sectionType, $h3) {
if (subsection && subsection.free) {
return langLabel(language) + "/" + subsection.header;
}
return subsection && subsection.place === "pron"
? langLabel(language)
: placeLabel(language, sectionType, $h3);
}
function fetchPageSubsection(language, sectionType, sectionOrdinal, subsection) {
return fetchPageContent().then(function (content) {
return readPageSubsection(content, language, sectionType, sectionOrdinal, subsection);
});
}
// Finds a POS block's body range (up to, but not including, the next heading of level <= 3), and the
// header text + line index of any "====" subsections it already contains.
// depth is the part-of-speech heading's own ("===" on most entries, "====" where the senses are
// grouped under numbered etymologies). Its block runs to the next heading at that depth or shallower,
// and its subsections are the headings one deeper.
function posBlockBounds(lines, posLineIndex, depth) {
depth = depth || 3;
var blockEnd = wt.headingAtOrAbove(depth);
var subsectionHeading = wt.headingAtDepth(depth + 1);
var end = lines.length;
var existingHeaders = [];
for (var i = posLineIndex + 1; i < lines.length; i++) {
if (blockEnd.test(lines[i])) {
end = i;
break;
}
var sub = subsectionHeading.exec(lines[i]);
if (sub) {
existingHeaders.push({ header: $.trim(sub[1]), lineIndex: i });
}
}
return { end: end, existingHeaders: existingHeaders, depth: depth };
}
// Looks up a header's rank in SUBSECTION_ORDER. -1 for a header that isn't one of ours; such a header is
// never used as an insertion anchor.
function subsectionRankForHeader(header) {
return SUBSECTION_ORDER.indexOf(header);
}
// Body line range of one "====" subsection inside a POS block: everything after its heading up to the next
// "====" heading, or the end of the block. Null if the block has no such subsection.
function subsectionBodyRange(bounds, header) {
var headers = bounds.existingHeaders;
for (var i = 0; i < headers.length; i++) {
if (headers[i].header === header) {
return {
start: headers[i].lineIndex + 1,
end: i + 1 < headers.length ? headers[i + 1].lineIndex : bounds.end
};
}
}
return null;
}
// ---- the three places that carry no "====" heading of their own ----------------------------------
// Bilêvkirin is a "===" section above the POS blocks, images sit at the top of a POS block, and the
// h-bnr hatnote goes above the first language heading. Each is located here and then read, written or
// inserted through the same three lines of splice, so nothing below needs to know about them.
var IMAGE_LINE = /^\[\[\s*(?:Wêne|File|Image|Dosya)\s*:/i;
/**
* {lines, langSectionIndex, exists, start, end, insertAt} for a subsection carrying a "place", or null
* for an ordinary one. start..end is the body as it stands; insertAt is where a missing one goes.
*/
function locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection) {
var place = subsection.place;
if (!place) {
return null;
}
var i, end;
if (place === "page-top") {
// Above every language section, so the whole page is the search space. The call is found under
// any of its names, and its own name is carried back so a save never renames it.
var pageLines = pageContent.split("\n");
var call = wt.findHbnrCall(pageContent);
if (call) {
return { lines: pageLines, langSectionIndex: 0, exists: true, call: call,
start: call.lineIndex, end: call.lineIndex + 1, insertAt: call.lineIndex };
}
return { lines: pageLines, langSectionIndex: 0, exists: false, start: 0, end: 0, insertAt: 0 };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
if (place === "pron") {
// One per language, not one per part of speech, so it is looked for above the POS heading.
for (i = 0; i < loc.posLineIndex; i++) {
var h3 = /^===\s*([^=]+?)\s*===/.exec(lines[i]);
if (h3 && $.trim(h3[1]) === subsection.header) {
end = i + 1;
while (end < lines.length && !/^==/.test(lines[end])) {
end++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: true,
start: i + 1, end: end, insertAt: i };
}
}
// Straight after the language heading, which puts it before every POS block.
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: false,
start: 1, end: 1, insertAt: 1 };
}
if (place === "pos-top") {
// Between the conjugation line and the headword template -- where the wizard writes them.
var at = loc.posLineIndex + 1;
while (at < lines.length && /^\{\{\s*ku-tewîn/.test($.trim(lines[at]))) {
at++;
}
var from = at;
while (at < lines.length && IMAGE_LINE.test($.trim(lines[at]))) {
at++;
}
return { lines: lines, langSectionIndex: loc.langSectionIndex, exists: at > from,
start: from, end: at, insertAt: from };
}
return null;
}
// The wikitext a "place" section's content becomes. A word list there is one template call, not a
// {{kol3}} block, so the words are joined into it -- and any ⊕ modifier a row picked up is dropped,
// since a page name is all the call can carry.
function loosePlaceBody(subsection, content, language) {
if (subsection.kind === "images") {
return $.grep($.map(content || [], function (image) {
return wt.buildImageLine({
file: image.file,
caption: wt.captionForLang(image.caption, language),
alt: image.alt,
size: image.size,
extra: image.extra
});
}), Boolean).join("\n");
}
return content;
}
// The words a "place" word list holds, flattened out of its single group. Any ⊕ modifier a row picked
// up is dropped: a page name is all the call can carry.
function loosePlaceWords(content) {
var words = [];
$.each(content || [], function (_, group) {
$.each(group.words || [], function (_, raw) {
var plain = $.trim(util.parseWordValue(raw).word);
if (plain && $.inArray(plain, words) === -1) {
words.push(plain);
}
});
});
return words;
}
// Writes one of those places, inserting it when it is not there yet and replacing it when it is. Only
// Bilêvkirin carries a heading; the other two are bare lines.
function writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var at = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (!at) {
throw new SubmitAbort("Cih nehate dîtin: " + subsection.header);
}
// A call naming a subpage -- "{{h-bnr/-ane}}" -- holds a list shared by every page transcluding
// it. Writing here would change this page alone and desync the rest, so the edit is refused and
// the template that actually owns the list is named instead.
if (at.call && at.call.subpage) {
throw new SubmitAbort("Ev rûpel «{{" + at.call.name + "}}» bi kar tîne — lîste li " +
"[[Şablon:" + at.call.subpage + "]] ye, lê wê zêde bike, ne li vê rûpelê.");
}
// A word list here is one template call, so it is built where the existing call's own name is
// known -- editing "{{bnr-h|...}}" must not turn it into "{{h-bnr|...}}".
var body = subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content), at.call && at.call.name)
: $.trim(content || "");
// A "===" section is separated from what is above and below it by a blank line, the way the rest
// of an entry is written. The other two places are bare lines inside a block and take none.
var heading = subsection.place === "pron";
function blank(index) {
return $.trim(at.lines[index] || "") === "";
}
if (at.exists) {
if (body) {
var replacement = [body];
// end is the next "==" heading; the blank line before it is inside the range being replaced.
if (heading && /^==/.test(at.lines[at.end] || "")) {
replacement.push("");
}
at.lines.splice.apply(at.lines, [at.start, at.end - at.start].concat(replacement));
} else {
// Emptying the box removes the section rather than leaving an empty heading behind.
at.lines.splice(at.start, at.end - at.start);
if (heading) {
at.lines.splice(at.insertAt, 1);
// The blank line that separated it goes with it, rather than doubling up.
if (blank(at.insertAt) && blank(at.insertAt - 1)) {
at.lines.splice(at.insertAt, 1);
}
}
}
} else {
if (!body) {
throw new SubmitAbort("Tiştek nehate nivîsandin.");
}
var block = heading ? ["=== " + subsection.header + " ===", body] : [body];
if (heading) {
if (!blank(at.insertAt - 1)) {
block.unshift("");
}
if (!blank(at.insertAt)) {
block.push("");
}
}
at.lines.splice.apply(at.lines, [at.insertAt, 0].concat(block));
}
return pageContent.slice(0, at.langSectionIndex) + at.lines.join("\n");
}
// What a section body holds: its {{kol3}} groups, the plain bullet list that stands in for them, or
// the raw wikitext when it is neither. A free section is read this way before its kind is known.
function readSubsectionBody(body, subsection) {
if (subsection.kind === "wordlist" || subsection.free) {
var calls = parseWordListCalls(body);
if (calls.length) {
return {
kind: "wordlist",
groups: calls.map(function (call) {
return { sernav: namedParam(call.named, "sernav"), words: call.words };
})
};
}
var bullets = parseBulletWordList(body);
if (bullets) {
// Editable as a word list; saving rewrites the bullets as {{kol3}}.
return { kind: "wordlist", groups: [{ sernav: "", words: bullets }], fromBullets: true };
}
}
return { kind: "text", text: $.trim(body) };
}
// The wikitext a section body becomes after an edit. A wordlist keeps its own template name, language
// and named params ("cure=Jê", ...) and only has its positional words replaced; a text one is
// replaced whole.
function rewriteSubsectionBody(body, language, subsection, content) {
if (subsection.kind !== "wordlist") {
// Replacing the body wholesale drops that separator, so put it back.
return content + (/\n\s*$/.test(body) ? "\n" : "");
}
var calls = parseWordListCalls(body);
if (calls.length) {
// Each call is rewritten where it stands, so text between and around them survives untouched.
// Last to first, so the offsets of the earlier ones stay valid as we go.
var replacement = body;
for (var gi = calls.length - 1; gi >= 0; gi--) {
var call = calls[gi];
var group = content[gi];
// A group emptied in the form drops its call entirely rather than leaving "{{kol3|ku}}".
var rebuilt = group && group.words.length
? buildWordListCall(call.name, call.lang, withNamedParam(call.named, "sernav", group.sernav), group.words)
: "";
replacement = replacement.slice(0, call.start) + rebuilt + replacement.slice(call.end);
}
// Groups the form added beyond the ones already there go after the last of them.
var added = content.slice(calls.length).filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
});
if (added.length) {
replacement = replacement.replace(/\s*$/, "") + "\n\n" + added.join("\n\n") + "\n";
}
return replacement.replace(/\n{3,}/g, "\n\n"); // gaps left by any dropped call
}
if (parseBulletWordList(body)) {
// A plain bullet list is replaced wholesale by a real {{kol3}} on its first edit.
return content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n") + (/\n\s*$/.test(body) ? "\n" : "");
}
throw new SubmitAbort("Nikare lîsteya vê beşê bixwîne: " + subsection.header);
}
// ---- sections that are not subsections of a part of speech ----
// A "=== Etîmolojî ===" that the parts of speech hang under, a "=== Çavkanî ===" at the foot of a
// language, a heading nobody planned for. Located by its own text, and edited as the wikitext under
// it -- or as a {{kol3}} list when that is what it holds.
function freeSectionBody(pageContent, language, subsection) {
var loc = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: loc, body: loc.lines.slice(loc.bodyStart, loc.bodyEnd).join("\n") };
}
function readFreeSection(pageContent, language, subsection) {
var at = freeSectionBody(pageContent, language, subsection);
return readSubsectionBody(at.body, subsection);
}
function updateFreeSection(pageContent, language, subsection, content) {
var at = freeSectionBody(pageContent, language, subsection);
at.loc.lines.splice(at.loc.bodyStart, at.loc.bodyEnd - at.loc.bodyStart,
rewriteSubsectionBody(at.body, language, subsection, content));
return pageContent.slice(0, at.loc.langSectionIndex) + at.loc.lines.join("\n");
}
// ---- removing and renaming a heading ----
// A subsection's heading line and the end of what it owns, as {loc, heading, end, depth, bounds}. A
// "====" subsection runs to the next one, taking deeper headings with it. A free section owns only its
// own text: what sits under it belongs to the parts of speech (see removeSubsection).
function headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
var free = wt.locateHeadingByText(pageContent, language, subsection.header, subsection.free.ordinal);
return { loc: free, heading: free.headingLineIndex, end: free.bodyEnd, depth: free.depth };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
return { loc: loc, heading: range.start - 1, end: range.end, depth: bounds.depth + 1, bounds: bounds };
}
// A free section with deeper headings under it is refused: removing it would leave them under the
// section before, and taking them along would remove whole parts of speech.
function hasDeeperHeadingAfter(lines, index, depth) {
var m = /^(=+)[^=]/.exec(lines[index] || "");
return !!m && m[1].length > depth;
}
function removeSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (subsection.free && hasDeeperHeadingAfter(lines, span.end, span.depth)) {
throw new SubmitAbort("Di bin «" + subsection.header + "» de beşên din hene; pêşî wan jê bibe.");
}
lines.splice(span.heading, span.end - span.heading);
// The blank line that separated it goes with it, rather than doubling up.
if (span.heading > 0 && !$.trim(lines[span.heading - 1]) && !$.trim(lines[span.heading] || "x")) {
lines.splice(span.heading, 1);
}
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
function renameSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, newHeader) {
var span = headingSpan(pageContent, language, sectionType, sectionOrdinal, subsection);
var lines = span.loc.lines;
if (span.bounds && span.bounds.existingHeaders.some(function (h) {
return h.header === newHeader;
})) {
throw new SubmitAbort("Ev beş jixwe heye: " + newHeader);
}
var marks = wt.headingMarks(span.depth);
lines[span.heading] = marks + " " + newHeader + " " + marks;
// A {{kol3}} names its section in cure=, which follows the heading. Read across lines: a call is
// often written one parameter per line.
var cure = new RegExp("(\\|\\s*cure\\s*=\\s*)" + escapeRegExp(subsection.header) + "(\\s*[|}])", "g");
var body = lines.slice(span.heading + 1, span.end).join("\n").replace(cure, "$1" + newHeader + "$2");
lines.splice.apply(lines, [span.heading + 1, span.end - span.heading - 1].concat(body.split("\n")));
return pageContent.slice(0, span.loc.langSectionIndex) + lines.join("\n");
}
// Current contents of an existing "====" subsection. A wordlist one whose template can't be parsed comes
// back as {kind: "text"} so the caller can still edit it as raw wikitext instead of refusing outright.
function readPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection) {
if (subsection.free) {
return readFreeSection(pageContent, language, subsection);
}
var loose = locateLoosePlace(pageContent, language, sectionType, sectionOrdinal, subsection);
if (loose) {
if (!loose.exists) {
return null;
}
if (loose.call) {
return { kind: "wordlist", groups: [{ sernav: "", words: loose.call.words }] };
}
var looseBody = $.trim(loose.lines.slice(loose.start, loose.end).join("\n"));
if (subsection.kind === "images") {
return {
kind: "images",
images: $.grep($.map(looseBody.split("\n"), wt.parseImageLine), Boolean)
};
}
if (subsection.kind !== "wordlist") {
return { kind: "text", text: looseBody };
}
// One template call rather than a {{kol3}} block: its positional parameters are the words.
var looseCall = parseTemplateCalls(looseBody).calls[0];
return { kind: "wordlist", groups: [{ sernav: "", words: (looseCall && looseCall.params) || [] }] };
}
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var bounds = posBlockBounds(loc.lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
return null;
}
return readSubsectionBody(loc.lines.slice(range.start, range.end).join("\n"), subsection);
}
// Rewrites an existing "====" subsection in place. A wordlist one keeps its own template name, language and
// named params ("cure=Jê", ...) and only has its positional words replaced; a text one is replaced whole.
function updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var range = subsectionBodyRange(bounds, subsection.header);
if (!range) {
throw new SubmitAbort(util.msg.sectionMissing + subsection.header);
}
var body = lines.slice(range.start, range.end).join("\n");
lines.splice(range.start, range.end - range.start,
rewriteSubsectionBody(body, language, subsection, content));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// Inserts a new "==== Header ====" subsection into its SUBSECTION_ORDER position within a POS block, right
// before the first ranked subsection that outranks it, or at the end if none does. Either a
// {{kol3|...}} word list or a plain paragraph, matching start.js's own subsection formats.
function insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, subsection, content) {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var bounds = posBlockBounds(lines, loc.posLineIndex, loc.depth);
var alreadyThere = false;
bounds.existingHeaders.forEach(function (h) {
if (h.header === subsection.header) {
alreadyThere = true;
}
});
if (alreadyThere) {
throw new SubmitAbort("Ev beş jixwe heye: " + subsection.header);
}
var body = subsection.kind === "wordlist"
? content.map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + subsection.header], "sernav", group.sernav), group.words);
}).join("\n\n")
: content;
var myRank = subsectionRankForHeader(subsection.header);
var headers = bounds.existingHeaders;
var insertAt = bounds.end;
for (var i = 0; i < headers.length; i++) {
var otherRank = subsectionRankForHeader(headers[i].header);
if (otherRank !== -1 && otherRank > myRank) {
insertAt = headers[i].lineIndex;
break;
}
}
// Exactly one blank line on each side: the line before the insertion point is normally already blank
// (it separated the previous subsection from the next heading), so only add one where there isn't one.
var block = [];
if (insertAt > 0 && $.trim(lines[insertAt - 1]) !== "") {
block.push("");
}
var marks = wt.headingMarks(bounds.depth + 1);
block.push(marks + " " + subsection.header + " " + marks, body);
if (insertAt < lines.length) {
block.push("");
}
lines.splice.apply(lines, [insertAt, 0].concat(block));
return pageContent.slice(0, loc.langSectionIndex) + lines.join("\n");
}
// The wizard's own etymology templates, straight out of data-main.json's "etymTpls" -- the same list start.js's
// insert menu offers, so the two can never drift apart. CODE/SOURCELANG are its placeholders.
// The wizard's own template lists, straight out of data-main.json, so both gadgets offer the same
// templates in the same order. A pronunciation template names the languages it is for; one that names
// none is offered everywhere.
function templateChoices(pool, language) {
var defs = startData[pool] || {};
return Object.keys(defs).filter(function (name) {
var langs = defs[name].langs;
return !langs || !langs.length || $.inArray(language, langs) !== -1;
}).map(function (name) {
return { name: name, desc: defs[name].desc, snippet: defs[name].snippet };
});
}
// One template call inside the surface: shown as its own wikitext, but atomic -- contenteditable="false"
// keeps the caret out of it, so it can only be changed through the dialog.
function makeTemplateChip(wikitext) {
// The × is its own element rather than the chip's only way out: on a phone there is no reliable
// backspace onto a contenteditable="false" span, so without it the dialog is the only way to take
// a call off the line. readChipSurface() reads chips from data-wikitext, so the extra child does
// not reach the saved text.
var $remove = $("<span class='dsb-tpl-chip-remove' contenteditable='false'>×</span>")
.attr("title", "Vê şablonê jê bibe");
return $("<span class='dsb-tpl-chip' contenteditable='false' role='button' tabindex='0'></span>")
.attr("data-wikitext", wikitext)
.attr("title", "Bitikîne da ku parametreyan biguhêrî")
.text(wikitext)
.append($remove);
}
// wikitext -> surface DOM: template calls become chips, everything else stays plain editable text.
function renderChipSurface($surface, wikitext) {
var text = wikitext || "";
var calls = parseTemplateCalls(text).calls;
var pos = 0;
$surface.empty();
calls.forEach(function (call) {
if (call.start > pos) {
$surface.append(document.createTextNode(text.slice(pos, call.start)));
}
$surface.append(makeTemplateChip(text.slice(call.start, call.end)));
pos = call.end;
});
if (pos < text.length) {
$surface.append(document.createTextNode(text.slice(pos)));
}
}
// surface DOM -> wikitext. Chips give back their stored call; anything the browser inserted while typing
// (a <br>, a wrapper <div>) degrades to its text, so the result is always plain wikitext.
function readChipSurface(node) {
var out = "";
$(node).contents().each(function () {
if (this.nodeType === 3) {
out += this.nodeValue;
return;
}
var $el = $(this);
if ($el.hasClass("dsb-tpl-chip")) {
out += $el.attr("data-wikitext");
return;
}
if (this.nodeName === "BR") {
out += "\n";
return;
}
if (this.nodeName === "DIV" || this.nodeName === "P") {
out += "\n" + readChipSurface(this);
return;
}
out += readChipSurface(this);
});
return out;
}
// Drops a node in at the caret, or at the end when the caret is not inside the surface.
function insertAtCaret($surface, node) {
// Inserting a fragment moves its children into the surface and leaves the fragment itself empty
// and parentless, so the caret follows that last child; setStartAfter on the fragment throws.
var last = node.nodeType === Node.DOCUMENT_FRAGMENT_NODE ? node.lastChild : node;
var sel = window.getSelection();
if (sel && sel.rangeCount && $.contains($surface.get(0), sel.getRangeAt(0).commonAncestorContainer)) {
var range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(node);
if (last) {
range.setStartAfter(last);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
} else {
$surface.append(node);
}
}
// Attaches the pencil toggle to one leaf definition <li> -- clicking it reveals the "guhartina pêşketî"
// (advanced editing) panel built by buildAdvancedPanel() above.
// ---- per-section UI: "+ Binebeşekê lê zêde bike" after a POS block's own content ----
// Reads the header text of "====" subsections already rendered under this POS heading, straight from the DOM.
// The subsections of a part of speech are the headings one deeper than it is.
function subsectionDepthFor($h3) {
return (sections.headingDepth($h3) || 3) + 1;
}
function isSubsectionHeading($el, depth) {
return sections.headingDepth($el) === depth;
}
function existingSubsectionHeadersInDom($h3) {
var depth = subsectionDepthFor($h3);
var headers = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
headers.push(headingText($hh));
}
}
return headers;
}
// Finds where a new "====" subsection preview belongs in the live DOM, using the same SUBSECTION_ORDER rank
// insertPageSubsection() uses. Returns {insertBefore: $el} (a ranked subsection that outranks this one) or
// {insertAfter: $el} (goes at the end of the block).
function domSubsectionInsertAnchor($h3, header) {
var depth = subsectionDepthFor($h3);
var myRank = subsectionRankForHeader(header);
var $last = headingOuter($h3);
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
var otherRank = subsectionRankForHeader(headingText($hh));
if (otherRank !== -1 && otherRank > myRank) {
return { insertBefore: $cursor };
}
}
$last = $cursor;
}
return { insertAfter: $last };
}
// The wikitext one subsection's content becomes, exactly as insertPageSubsection() writes it -- so the
// preview parses the same string the save will produce.
function subsectionBodyWikitext(language, s, content) {
if (s.kind !== "wordlist") {
return content;
}
return content.filter(function (group) {
return group.words.length;
}).map(function (group) {
return buildWordListCall(WORDLIST_TEMPLATE, language,
withNamedParam(["cure=" + s.header], "sernav", group.sernav), group.words);
}).join("\n\n");
}
// The plain word list this preview falls back to when the parse fails: the values that were typed,
// without the template around them. Right, but not what the page will look like.
function subsectionPlainDom($wrap, s, content) {
if (s.kind !== "wordlist") {
$wrap.append($("<p class='dsb-preview-text'></p>").text(content));
return;
}
content.forEach(function (group) {
if (group.sernav) {
$wrap.append($("<div class='dsb-preview-subheading'></div>").text(group.sernav));
}
var $list = $("<ul class='dsb-preview-list'></ul>");
group.words.forEach(function (w) {
$list.append($("<li></li>").text(w.replace(/<[^<>]*>/g, "")));
});
$wrap.append($list);
});
}
// A "not saved yet" preview of a page-level "====" subsection, dropped into the live page at the
// position the save will put it, with its body rendered the way the page renders it -- real {{kol3}}
// columns and real links, not a list of the words that went into them. Returns the inserted element so
// the caller can remove it again; onRendered($body) runs once the body is drawn.
function buildSubsectionPreviewDom($h3, s, content, language, onRendered) {
var $wrap = $("<div class='dsb-preview-block'></div>");
// A styled <div>, not a real <h4>, so existingSubsectionHeadersInDom() never mistakes this live-only
// preview for an actual subsection already on the page.
$wrap.append($("<div class='dsb-preview-heading' role='heading' aria-level='4'></div>").text(s.header));
$wrap.append($("<span class='dsb-preview-badge'></span>").text("nû — hîn nehatiye qeydkirin"));
// Placed before it is filled: a cached render resolves almost at once, and filling a wrap that is
// still detached would announce content nobody can see.
var anchor = domSubsectionInsertAnchor($h3, s.header);
if (anchor.insertBefore) {
anchor.insertBefore.before($wrap);
} else {
anchor.insertAfter.after($wrap);
}
var wikitext = language ? subsectionBodyWikitext(language, s, content) : "";
if ($.trim(wikitext)) {
var $body = $("<div class='dsb-preview-rendered'></div>").appendTo($wrap);
fillWithRendered($body, wikitext, function () {
$body.remove();
subsectionPlainDom($wrap, s, content);
}).then(function () {
if (onRendered && $body.closest("body").length) {
onRendered($body);
}
});
} else {
subsectionPlainDom($wrap, s, content);
}
return $wrap;
}
// A free section owns what lies between its own heading and the next heading of any depth, matching
// the range locateHeadingByText() edits. Returns a function that puts the page back, or null.
function replaceFreeSectionInPlace($heading, wikitext) {
var $start = headingOuter($heading);
var body = [];
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (sections.headingDepth($cursor)) {
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
/**
* Redraws a subsection that is already on the page with the content this edit would give it.
*
* The section's own nodes -- everything between its heading and the next heading of the same rank or
* higher -- are detached rather than dropped, so the returned function puts the page back exactly as it
* was when the change is cancelled or superseded. onRendered($slot) runs once the new content is drawn.
*/
function replaceSubsectionInPlace($h3, header, wikitext, onRendered) {
var depth = subsectionDepthFor($h3);
// Walked the same way existingSubsectionHeadersInDom() walks it, so "which nodes are this
// subsection" is decided once and the same way everywhere.
var $start = null;
var body = [];
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && here < depth) {
break; // out of this part of speech
}
if (isSubsectionHeading($cursor, depth)) {
if ($start) {
break; // the next subsection starts here
}
var $hh = $cursor.is("h2, h3, h4, h5, h6") ? $cursor : $cursor.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === header) {
$start = $cursor;
}
continue;
}
// This gadget's own furniture stays where it is: the subsection's editing panel is the sibling
// right after its heading, so sweeping it into the body would detach the very panel the change
// was made in and leave the pencil toggling an element that is no longer on the page.
if ($start && !isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
if (!$start || !body.length) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>").insertAfter($start);
var $body = $(body).detach();
fillWithRendered($slot, wikitext, function () {
$slot.remove();
$slot = null;
$body.insertAfter($start);
}).then(function () {
if (onRendered && $slot) {
onRendered($slot);
}
});
return function () {
if ($slot) {
$body.insertAfter($start);
$slot.remove();
$slot = null;
}
};
}
// Queued translation boxes, drawn the way the save leaves the page: a new Werger section with them, or
// the section already there redrawn from the queued text. The translation adder is attached to what
// is drawn, so translations can be added before anything is saved. Returns a function that puts the
// page back.
function drawWergerBoxes($h3, s, newBoxes, language, sectionType, sectionOrdinal) {
function withAdder($body) {
mw.loader.using("ext.gadget.wikt.translation-adder").then(function (require) {
return require("ext.gadget.wikt.translation-adder").attach($body,
translationEditor($h3, language, sectionType));
}).catch(function () { /* without the adder the boxes are still shown */ });
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, newBoxes, language, withAdder);
return function () {
$preview.remove();
};
}
var restore = null;
var dropped = false;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var current = readPageSubsection(queuedContent(pageContent), language, sectionType, sectionOrdinal,
$.extend({}, s, { kind: "text" }));
if (current) {
restore = replaceSubsectionInPlace($h3, s.header, current.text || "", withAdder);
}
});
return function () {
dropped = true;
if (restore) {
restore();
restore = null;
}
};
}
/**
* The live DOM one "place" section occupies: {nodes, insert}. nodes are what a render replaces --
* empty when the section is not on the page yet -- and insert() puts an element where the section
* belongs, so a new one appears exactly where the save will put it.
*/
// This gadget's own panels sit between a heading and the page's content, so every walk steps over
// them rather than counting one as content.
function isOwnFurniture($el) {
return $el.is(".dsb-toolbar, .dsb-quick-row, .dsb-subsection-panel, .dsb-convert-pos-panel," +
" .dsb-preview-block, .dsb-sense-preview, .dsb-preview-inline, .dsb-werger-add");
}
// One subsection's own nodes on the page, read the way headingSpan() reads the wikitext: a "===="
// subsection down to the next heading at its depth or shallower, a free section ($h3 is its heading)
// down to the next heading of any depth. hasDeeper says a free section has headings under it.
function subsectionDom($h3, s) {
var $start = null;
var depth;
if (s.free) {
$start = headingOuter($h3);
depth = sections.headingDepth($start) || sections.headingDepth($h3);
} else {
depth = subsectionDepthFor($h3);
for (var $c = headingOuter($h3).next(); $c.length; $c = $c.next()) {
var d = sections.headingDepth($c);
if (d && d < depth) {
break;
}
if (d === depth) {
var $hh = $c.is("h2, h3, h4, h5, h6") ? $c : $c.find("h2, h3, h4, h5, h6").first();
if (headingText($hh) === s.header) {
$start = $c;
break;
}
}
}
}
if (!$start) {
return null;
}
var body = [];
var hasDeeper = false;
for (var $cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
var here = sections.headingDepth($cursor);
if (here && (s.free || here <= depth)) {
hasDeeper = !!s.free && here > depth;
break;
}
if (!isOwnFurniture($cursor)) {
body.push($cursor.get(0));
}
}
return { $heading: $start, $body: $(body), hasDeeper: hasDeeper };
}
function loosePlaceDom($h3, subsection) {
var place = subsection.place;
var nodes = [];
var $cursor;
if (place === "page-top") {
// Şablon:herwiha binêre renders as <div class="rellink binere"> at the head of the content.
var $out = $("#mw-content-text .mw-parser-output").first();
var $hat = $out.children(".binere, .rellink").first();
if ($hat.length) {
return { nodes: $hat.get(), insert: function ($el) { $hat.before($el); } };
}
return { nodes: [], insert: function ($el) { $out.prepend($el); } };
}
var $start = headingOuter($h3);
if (place === "pron") {
// A "===" heading above this POS block, under the language one.
for (var $back = $start.prev(); $back.length; $back = $back.prev()) {
if ($back.is(".mw-heading2, h2")) {
break;
}
if ($back.is(".mw-heading3, h3")) {
var $hh = $back.is("h3") ? $back : $back.find("h3").first();
if (headingText($hh) === subsection.header) {
for ($cursor = $back.next();
$cursor.length && !$cursor.is(".mw-heading2, .mw-heading3, h2, h3");
$cursor = $cursor.next()) {
if (!isOwnFurniture($cursor)) {
nodes.push($cursor.get(0));
}
}
return { nodes: nodes, insert: function ($el) { $back.after($el); } };
}
}
}
return { nodes: [], insert: function ($el) { $start.before($el); } };
}
if (place === "pos-top") {
// The thumbnails between the heading and the headword line.
for ($cursor = $start.next(); $cursor.length; $cursor = $cursor.next()) {
if (isOwnFurniture($cursor)) {
continue;
}
if (!$cursor.is(".thumb, figure, .floatright, .floatleft")) {
break;
}
nodes.push($cursor.get(0));
}
return { nodes: nodes, insert: function ($el) { $start.after($el); } };
}
return null;
}
// The wikitext a "place" section's pending content renders as. A word list is its one call; everything
// else is already the body that will be written.
function loosePlacePreviewWikitext(subsection, content, language) {
return subsection.kind === "wordlist"
? wt.buildHbnrCall(loosePlaceWords(content))
: loosePlaceBody(subsection, content, language);
}
// Redraws one of those places where it stands, the same way replaceSubsectionInPlace() redraws a
// "====" one. The page's own nodes are detached rather than dropped, so cancelling puts them back.
function replaceLoosePlaceInPlace($h3, subsection, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var at = loosePlaceDom($h3, subsection);
if (!at) {
return null;
}
var $slot = $("<div class='dsb-sense-preview'></div>");
at.insert($slot);
var $body = $(at.nodes).detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the subsection picker + its word-list/text field. A subsection that isn't on the page yet is saved
// via insertPageSubsection(); one that already is gets prefilled and saved via updatePageSubsection().
// onlyHeader pins the panel to a single subsection (the per-heading pencil); omit it for the full picker.
// free pins the panel to a heading that is not a subsection of a part of speech, located by its own
// text: {header, ordinal}.
function buildSubsectionPanel($panel, $h3, language, sectionType, sectionOrdinal, onlyHeader, free) {
var existing = existingSubsectionHeadersInDom($h3);
// A heading the data does not name is still editable. It is declared a word list so the reader
// tries, in order, its {{kol3}} calls and then a plain bullet list; anything else falls through to
// raw wikitext, which is what prefill() does with a list it cannot read.
// Hidden counts as unknown here: a section the picker does not offer still opens, as the wikitext
// it is.
var unknown = onlyHeader && !PAGE_SUBSECTIONS.some(function (s) {
return s.header === onlyHeader && !s.hidden;
});
// A free section starts as wikitext: what it holds is only known once it has been read, and
// prefill() swaps in the {{kol3}} form there if it turns out to be a list.
var available = free ? [{
header: onlyHeader,
id: "free",
kind: "text",
free: free,
existing: true
}] : unknown ? [{
header: onlyHeader,
id: "unknown",
kind: "wordlist",
unknown: true,
existing: true
}] : PAGE_SUBSECTIONS.filter(function (s) {
// "hidden" takes a section out of the picker without removing it. Werger carries lang: "ku" --
// it is a Kurdish-entry section, so it is not offered elsewhere.
return !s.hidden && (!s.lang || s.lang === language) &&
(!onlyHeader || s.header === onlyHeader);
}).map(function (s) {
return $.extend({}, s, { existing: existing.indexOf(s.header) !== -1 });
});
if (!available.length) {
$panel.text("Ji bo vê beşê guhartin tune.");
return;
}
var $select = $("<select class='dsb-subsection-select'></select>");
if (!onlyHeader) {
// Label-less on purpose: it holds the "nothing chosen yet" value without becoming a row in the
// list (makeSearchable() skips a blank option that has no label), so the prompt lives in the
// input's placeholder instead.
$select.append($("<option value=''></option>"));
}
available.forEach(function (s) {
// A "place" entry has no "====" heading of its own, so it is marked rather than left looking
// like the subsections around it.
var label = s.place ? s.header + " (ne binebeş e)"
: (s.existing ? s.header + " (biguhêre)" : s.header);
$select.append($("<option></option>").val(s.id).text(label));
});
var $selectWrap = makeSearchable($select, "Binebeşek hilbijêre");
if (onlyHeader) {
$selectWrap.hide(); // pinned to one subsection: nothing to choose
}
var $fieldWrap = $("<div class='dsb-subsection-field'></div>");
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil that opened the panel, so it is told the panel is closed.
$cancel.on("click", function () {
resetForm();
$panel.hide().trigger("dsbPanelClosed");
hideQueueIfIdle();
});
// A panel pinned to a heading on the page can rename or remove it. Bilêvkirin and the other places
// have no heading of their own to change.
var headingEditable = !!onlyHeader && available.length === 1 && available[0].existing && !available[0].place;
// Werger holds translation boxes, which no other section can: it can be removed, not renamed.
var renamable = headingEditable && available[0].kind !== "werger";
var $nameWrap = null;
// Only a panel pinned to one heading renames it; elsewhere the name is the picked subsection's own.
var readName = function (s) {
return s.header;
};
if (headingEditable) {
var subject = available[0];
var $nameLabel = $("<div class='dsb-adv-label'>Nav</div>");
if (renamable && subject.free) {
var $nameInput = $("<input type='text' class='dsb-input dsb-heading-name'>").val(onlyHeader);
readName = function () {
return $.trim($nameInput.val());
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameInput);
} else if (renamable) {
// The names the gadget knows, for a subsection of a part of speech; one this part of speech
// already has is left out, and Werger, which holds boxes no other section has.
var $nameSelect = $("<select class='dsb-heading-name'></select>");
var names = [onlyHeader];
PAGE_SUBSECTIONS.forEach(function (s) {
if (!s.place && !s.hidden && s.kind !== "werger" && (!s.lang || s.lang === language) &&
names.indexOf(s.header) === -1 && existing.indexOf(s.header) === -1) {
names.push(s.header);
}
});
names.forEach(function (name) {
$nameSelect.append($("<option></option>").val(name).text(name));
});
var $nameSearch = makeSearchable($nameSelect, "Nav");
$nameSelect.val(onlyHeader).trigger("wiktku:refresh");
readName = function () {
return $nameSelect.val() || onlyHeader;
};
$nameWrap = $("<div class='dsb-heading-name-wrap'></div>").append($nameLabel, $nameSearch);
}
var subjectDom = subsectionDom($h3, subject);
if (!(subjectDom && subjectDom.hasDeeper)) {
var $delete = $("<button type='button' class='dsb-delete'>Beşê jê bibe</button>");
$cancel.after($delete);
$delete.on("click", function () {
OO.ui.confirm("Beşa «" + subject.header + "» dê were rakirin. Dewam bike?").done(function (ok) {
if (!ok) {
return;
}
queueHeadingChange(subject, "beşa «" + subject.header + "» hate rakirin",
function (pageContent) {
return removeSubsection(pageContent, language, sectionType, sectionOrdinal, subject);
},
function () {
var dom = subsectionDom($h3, subject);
if (!dom) {
return null;
}
var $doomed = dom.$heading.add(dom.$body).addClass("dsb-preview-removed");
return function () {
$doomed.removeClass("dsb-preview-removed");
};
});
});
});
}
}
// Renaming and removing share one queue key, so the later choice replaces the earlier one. Both run
// last and bottom-up, since other queued changes find the section by the heading they change.
function queueHeadingChange(s, action, transform, previewFn) {
var dom = subsectionDom($h3, s);
queueChange(subsectionPlaceLabel(s, language, sectionType, $h3), action,
function (pageContent) {
return { content: transform(pageContent), warnings: [] };
},
$save, $status, previewFn,
{
runLast: true,
key: "heading:" + (s.free ? s.header + "|" + s.free.ordinal
: sectionType + "|" + sectionOrdinal + "|" + s.header),
pagePosition: dom ? headingPosition(dom.$heading) : 0
});
}
function queueRename(s, newName) {
queueHeadingChange(s, "beşa «" + s.header + "» bû «" + newName + "»",
function (pageContent) {
return renameSubsection(pageContent, language, sectionType, sectionOrdinal, s, newName);
},
function () {
var dom = subsectionDom($h3, s);
if (!dom) {
return null;
}
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + newName + " — nû, hîn nehatiye qeydkirin");
dom.$heading.after($note);
return function () {
$note.remove();
};
});
}
// Undefined while the placeholder option is selected.
function currentSubsection() {
var id = $select.val();
return id ? available.filter(function (s) {
return s.id === id;
})[0] : undefined;
}
// One entry per "{{kol3}}" group in the current wordlist section; empty for any other kind.
var groupFields = [];
var $groups = null;
var editAsText = false; // set when an existing wordlist section turns out to be unparseable
// Whatever has been typed into each subsection's box, so switching the picker away and back restores it
// instead of rebuilding an empty field. Keyed by subsection id; lastId is whichever box is on screen.
var drafts = {};
var lastId = null;
// Called before renderField() rebuilds, while the group fields still hold the outgoing box.
function captureDraft() {
if (!lastId) {
return;
}
drafts[lastId] = imagesField
? { asText: false, images: imagesField.values() }
: (groupFields.length
? { asText: false, groups: readGroups() }
: { asText: editAsText, text: fieldText() });
}
function restoreDraft(s, draft) {
// An existing wordlist we previously fell back to raw wikitext for stays in that mode.
if (draft.asText && s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
}
if (imagesField) {
imagesField.setValues(draft.images);
} else if (groupFields.length) {
setGroups(s, draft.groups);
} else {
setFieldText(draft.text);
}
}
// Etîmolojî is edited on a live surface (see renderChipSurface) rather than in a textarea, so every
// read and write of the field goes through these two instead of touching .dsb-subsection-text.
var chipSurface = null;
function fieldText() {
if (wergerForm) {
return JSON.stringify(wergerForm.read());
}
return chipSurface
? $.trim(readChipSurface(chipSurface.get(0)))
: $.trim($fieldWrap.find(".dsb-subsection-text").val() || "");
}
function setFieldText(value) {
if (wergerForm) {
wergerForm.set(value ? JSON.parse(value) : []);
return;
}
if (chipSurface) {
renderChipSurface(chipSurface, value || "");
var schedule = chipSurface.data("dsbSchedulePreview");
if (schedule) {
schedule(); // show what the loaded etymology renders as
}
} else {
$fieldWrap.find(".dsb-subsection-text").val(value || "");
}
}
// The contenteditable surface: text typed directly, template calls as chips that open the dialog,
// plus start.js's etymology templates in a searchable insert menu.
// A live surface where a template shows as a chip, with a menu that inserts one. Etîmolojî and
// Bilêvkirin are both written this way; they differ only in which templates the menu offers.
function renderTemplateEditor(s, templates) {
$fieldWrap.empty();
var $select = $("<select class='dsb-subsection-select'></select>");
$select.append($("<option value=''></option>"));
templates.forEach(function (t) {
$select.append($("<option></option>").val(t.snippet).text(t.name + " — " + t.desc));
});
var $surface = $("<div class='dsb-tpl-surface' contenteditable='true'></div>")
.attr("role", "textbox")
.attr("aria-multiline", "true");
chipSurface = $surface;
// Clicking the insert menu moves focus out of the surface and the browser forgets the caret, so
// the last caret position inside the surface is kept and restored before inserting -- otherwise
// focus() drops it back to the very start and every template lands at the beginning of the line.
var savedRange = null;
function rememberCaret() {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.commonAncestorContainer === root || $.contains(root, range.commonAncestorContainer)) {
savedRange = range.cloneRange();
}
}
$surface.on("keyup mouseup focusout", rememberCaret);
function restoreCaret() {
if (!savedRange) {
return;
}
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
$select.on("change", function () {
var snippet = $select.val();
if (!snippet) {
return;
}
// Same substitution start.js makes: CODE is this section's language, SOURCELANG is left for
// the user to fill in.
snippet = snippet.replace(/CODE/g, language).replace(/SOURCELANG/g, "?");
// A pronunciation template is a bullet of its own ("* {{ku-IPA}}"), so it starts a line
// rather than landing in the middle of one.
var ownLine = /^\*\s/.test(snippet);
if (ownLine) {
snippet = $.trim(snippet.replace(/^\*\s*/, ""));
}
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet);
var piece = document.createDocumentFragment();
if (ownLine) {
// The bullet, and a line of its own unless the caret already starts one.
var at = caretOffset();
var before = at >= 0 ? readChipSurface($surface.get(0)).slice(0, at) : "";
piece.appendChild(document.createTextNode(!before || /\n$/.test(before) ? "* " : "\n* "));
}
piece.appendChild($chip.get(0));
insertAtCaret($surface, piece);
rememberCaret(); // so a second insert lands after the first, not back at the old spot
$select.val("");
// Straight into the dialog: the snippet is placeholders ("?", "…") until someone fills it in.
cite.openTemplateDialog(snippet).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
});
// Paste as plain text: pasted markup would otherwise become real nodes in the surface.
$surface.on("paste", function (e) {
var clip = e.originalEvent && e.originalEvent.clipboardData;
if (!clip) {
return;
}
e.preventDefault();
document.execCommand("insertText", false, clip.getData("text/plain"));
});
// The chip the dialog was opened for, or the one that has taken its place. renderChipSurface()
// rebuilds the surface from its wikitext whenever a call is typed in as text, which throws away
// the element this closure is holding; without this, "Jê bibe" would remove a node that is no
// longer on screen and appear to do nothing.
function liveChip($chip, wikitext) {
if ($.contains($surface.get(0), $chip.get(0))) {
return $chip;
}
return $surface.find(".dsb-tpl-chip").filter(function () {
return $(this).attr("data-wikitext") === wikitext;
}).first();
}
function applyChipEdit($chip, edited) {
if (edited === null || !$chip.length) {
return;
}
if (!$.trim(edited)) {
$chip.remove();
return;
}
// Edited into something that is no longer one template call: let it fall back to plain text
// rather than keeping a chip that lies about its contents.
var call = parseTemplateCalls(edited);
if (call.otherContent || call.calls.length !== 1) {
$chip.replaceWith(document.createTextNode(edited));
return;
}
$chip.replaceWith(makeTemplateChip(edited));
}
// Double-click edits the call's wikitext in place; single click opens the parameter dialog. The
// click is held back briefly so the first of a double-click does not open the dialog underneath.
var chipClickTimer = null;
var lastChipTap = 0;
var lastChipNode = null;
var DOUBLE_TAP_MS = 300;
// Double-tap drops the chip back to plain wikitext in the surface, rather than making the chip
// itself editable: a contenteditable inside a contenteditable gives Android a selection handle and
// no usable caret. As plain text the browser's own editing works, and chipifyTyped() puts the chip
// back once the caret leaves the call.
// A collapsed caret inside the text just inserted: at the character tapped where the browser can
// say which that is, otherwise just inside the closing braces so the caret stays within the call
// (which is what stops chipifyTyped putting the chip straight back).
function caretRangeIn(node, event) {
var range;
var x = event && event.clientX;
var y = event && event.clientY;
if (x !== undefined && document.caretRangeFromPoint) {
range = document.caretRangeFromPoint(x, y);
} else if (x !== undefined && document.caretPositionFromPoint) {
var pos = document.caretPositionFromPoint(x, y);
if (pos) {
range = document.createRange();
range.setStart(pos.offsetNode, pos.offset);
}
}
if (range && range.startContainer === node) {
range.collapse(true);
return range;
}
var text = node.nodeValue;
range = document.createRange();
range.setStart(node, /\}\}$/.test(text) ? text.length - 2 : text.length);
range.collapse(true);
return range;
}
function startInlineEdit($chip, event) {
if (!$.contains($surface.get(0), $chip.get(0))) {
return; // the tap timer and dblclick can both fire; only the first one has a chip left
}
var node = document.createTextNode($chip.attr("data-wikitext"));
$chip.replaceWith(node);
$surface.get(0).focus();
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(caretRangeIn(node, event));
}
// Bound before the chip's own handler and stopping there, so removing a call never opens the
// dialog for it on the way out.
$surface.on("click", ".dsb-tpl-chip-remove", function (e) {
e.preventDefault();
e.stopImmediatePropagation();
clearTimeout(chipClickTimer);
lastChipNode = null;
$(this).closest(".dsb-tpl-chip").remove();
syncPanelWidth();
});
$surface.on("click keydown", ".dsb-tpl-chip", function (e) {
if (e.type === "keydown" && e.which !== 13 && e.which !== 32) {
return;
}
e.preventDefault();
var $chip = $(this);
clearTimeout(chipClickTimer);
// A second tap on the same chip edits it inline. Timed here rather than left to "dblclick",
// which mobile browsers fire unreliably or not at all for a double-tap.
var now = new Date().getTime();
if (lastChipNode === this && now - lastChipTap < DOUBLE_TAP_MS) {
lastChipNode = null;
startInlineEdit($chip, e);
return;
}
lastChipTap = now;
lastChipNode = this;
var wikitext = $chip.attr("data-wikitext");
chipClickTimer = setTimeout(function () {
cite.openTemplateDialog(wikitext).then(function (edited) {
applyChipEdit(liveChip($chip, wikitext), edited);
syncPanelWidth();
});
}, DOUBLE_TAP_MS);
});
$surface.on("dblclick", ".dsb-tpl-chip", function (e) {
e.preventDefault();
clearTimeout(chipClickTimer);
startInlineEdit($(this), e);
});
// A long etymology cannot be read in the panel's usual 520px column, so the panel drops its
// width cap once there is enough text to need it. What it will look like is shown by the
// in-place render on the page itself, not by a second copy inside the form.
var WIDE_AT = 240; // characters
function syncPanelWidth() {
var wikitext = readChipSurface($surface.get(0));
if (wikitext.length > WIDE_AT || wikitext.indexOf("\n") !== -1) {
$surface.closest(".dsb-subsection-panel").addClass("dsb-panel-wide");
}
}
// A template typed by hand becomes a chip once typing settles, so it behaves like an inserted one:
// click for the parameter dialog, double-click for its raw wikitext.
var composing = false;
// The caret as an offset into the surface's wikitext, which is what survives rebuilding the DOM.
function caretOffset() {
var sel = window.getSelection();
var root = $surface.get(0);
if (!sel || !sel.rangeCount) {
return -1;
}
var range = sel.getRangeAt(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return -1; // caret is somewhere else on the page
}
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.startContainer, range.startOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
return readChipSurface(holder).length;
}
// Puts the caret back at that wikitext offset. Only runs straight after a rebuild, so the surface
// holds nothing but text nodes and chips.
function placeCaret(offset) {
var root = $surface.get(0);
var range = document.createRange();
var nodes = root.childNodes;
root.focus();
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
var len = node.nodeType === 3
? node.nodeValue.length
: ($(node).attr("data-wikitext") || $(node).text()).length;
if (offset <= len) {
if (node.nodeType === 3) {
range.setStart(node, offset);
} else {
range.setStartAfter(node); // a chip is atomic; the caret goes after it
}
break;
}
offset -= len;
}
if (!range.startContainer || range.startContainer === document) {
range.selectNodeContents(root);
}
range.collapse(true);
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
// True when the selection overlaps the inside of a template call. A caret resting exactly at the
// closing "}}" does not count -- that is someone who has just finished typing one.
function selectionTouchesCall(wikitext) {
var sel = window.getSelection();
if (!sel || !sel.rangeCount) {
return false;
}
var range = sel.getRangeAt(0);
var root = $surface.get(0);
if (range.startContainer !== root && !$.contains(root, range.startContainer)) {
return false;
}
var from = caretOffset();
var to = from;
if (!range.collapsed) {
var pre = document.createRange();
pre.selectNodeContents(root);
pre.setEnd(range.endContainer, range.endOffset);
var holder = document.createElement("div");
holder.appendChild(pre.cloneContents());
to = readChipSurface(holder).length;
}
return parseTemplateCalls(wikitext).calls.some(function (call) {
return from < call.end && to > call.start;
});
}
function chipifyTyped() {
if (composing) {
return; // never rebuild under an IME
}
var wikitext = readChipSurface($surface.get(0));
// Chips already hold one call each, so a higher call count means one was typed in as text.
if (parseTemplateCalls(wikitext).calls.length <= $surface.find(".dsb-tpl-chip").length) {
return;
}
var offset = caretOffset();
if (selectionTouchesCall(wikitext)) {
return; // still inside a call: leave it as text until the caret moves out
}
renderChipSurface($surface, wikitext);
savedRange = null; // the old range points at nodes that no longer exist
if (offset >= 0) {
placeCaret(offset);
rememberCaret();
}
}
$surface.on("compositionstart", function () {
composing = true;
});
$surface.on("compositionend", function () {
composing = false;
schedulePreview();
});
var previewTimer = null;
function schedulePreview() {
clearTimeout(previewTimer);
previewTimer = setTimeout(function () {
chipifyTyped();
syncPanelWidth();
}, 700);
}
$surface.on("input", schedulePreview);
$surface.data("dsbSchedulePreview", schedulePreview);
$fieldWrap.append(makeSearchable($select, util.msg.addTemplate), $surface);
// Bilêvkirin is written in IPA, so it gets the same character palette the wizard shows. Its
// characters go in as text and its two template buttons as chips, which is what the rest of
// this surface is made of.
if (s.place === "pron") {
$fieldWrap.append(util.buildIpaPalette({
insertText: function (text) {
$surface.focus();
restoreCaret();
insertAtCaret($surface, document.createTextNode(text));
rememberCaret();
schedulePreview();
},
insertTemplate: function (snippet) {
$surface.focus();
restoreCaret();
var $chip = makeTemplateChip(snippet.replace(/\|\s*\}\}$/, "}}"));
insertAtCaret($surface, $chip.get(0));
rememberCaret();
cite.openTemplateDialog($chip.attr("data-wikitext")).then(function (edited) {
applyChipEdit($chip, edited);
syncPanelWidth();
});
}
}));
}
}
// The three "place" sections are wikitext with a shape worth spelling out, since nothing on screen
// says what belongs in the box.
var PLACE_PLACEHOLDERS = {
"page-top": "{{h-bnr|Kelîme|Kelîmeyeke din}} an jî {{herwiha binêre/ane}}",
"pos-top": "[[Wêne:Navê pelî.jpg|thumb|Binnivîs]]",
"pron": "* {{ku-IPA}}"
};
function renderTextArea(s, rows) {
$fieldWrap.empty().append(
$("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", rows)
.attr("placeholder", PLACE_PLACEHOLDERS[s.place] || (s.header + "…"))
);
util.initManualResize($fieldWrap);
}
// The translation boxes of a Werger section, one numbered row each like the images' list: a box
// already on the page (box = its place in the section) or a new one (box = null), each with the
// sense it covers. The lines inside are left to the translation adder.
var wergerForm = null;
var imagesField = null;
function renderWergerField(s) {
var rows = [];
var $list = $("<ol class='dsb-resim-list'></ol>");
function renumber() {
$list.children().each(function (n) {
$(this).children(".dsb-resim-index").text("Qutî " + (n + 1));
});
}
// {{werger-ser}} also takes stûn and column-width, but they only restyle the box and its defaults
// are what these pages use, so the form does not offer them. A box that sets one keeps it, since
// a row starts from the box's own parameters.
function addRow(params, box) {
var inputs = {};
// Its own row class rather than .dsb-tpl-param-row: that one is styled in cite.css, which is
// only on the page when wikt.cite happens to be loaded.
function field(key, label, placeholder) {
inputs[key] = $("<input type='text' class='dsb-input'>").attr("placeholder", placeholder || "")
.val(params[key] || "");
return $("<div class='dsb-werger-row'></div>").append(
$("<label class='dsb-adv-label'></label>").text(label).append(inputs[key])
);
}
$("<li class='dsb-resim-item'></li>").append(
$("<div class='dsb-resim-index'></div>"),
field("1", "Mane", "ava vexwarinê"),
field("2", "Mane no", "ne mecbûrî — di parantezê de xuya dike"),
field("id", "id", "ne mecbûrî — IDya tercimeyê")
).appendTo($list);
rows.push({ inputs: inputs, params: params, box: box });
renumber();
return inputs["1"];
}
function addNewRow() {
// Empty: the user writes the gloss for the box they are adding.
return addRow({}, null);
}
var $add = $("<a href='#' class='dsb-quick-link'>+ Qutiyeke din zêde bike</a>").on("click", function (e) {
e.preventDefault();
addNewRow().trigger("focus");
});
var $note = $("<p class='dsb-werger-note'></p>").text(
"Tercimeyan piştî lêzêdekirinê di bin qutiyê de binivîse."
);
wergerForm = {
// Each row as {box, params, loaded}: what it holds now and what it was opened with.
read: function () {
return rows.map(function (r) {
var params = $.extend({}, r.params);
$.each(r.inputs, function (key, $input) {
var value = $.trim($input.val() || "");
if (value) {
params[key] = value;
} else {
delete params[key];
}
});
return { box: r.box, params: params, loaded: r.params };
});
},
// Rows from read() or from the page; an empty list still offers one new box.
set: function (list) {
rows = [];
$list.empty();
(list || []).forEach(function (r) {
addRow(r.loaded || r.params, r.box);
if (r.loaded) {
$.each(rows[rows.length - 1].inputs, function (key, $input) {
$input.val(r.params[key] || "");
});
}
});
if (!rows.length) {
addNewRow();
}
},
addRow: addNewRow
};
// The "+ Qutiyeke din zêde bike" under the boxes on the page adds a row through this.
$panel.data("dsbAddBox", function () {
addNewRow().trigger("focus");
});
$fieldWrap.empty().append($note, $list, $add);
if (!s.existing) {
addNewRow();
}
}
// The boxes already in the section, as rows, with the changes still in the queue (a new gloss from
// the box's ±, say) but not this form's own. A pending "+ Qutiyeke din zêde bike" from the page adds
// a new one after them.
function prefillWerger(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
// Unread, the boxes on the page are unknown, so nothing is offered that a save could get wrong.
fetchPageContent().then(function (pageContent) {
return readPageSubsection(queuedContent(pageContent, wergerKey(s)), language, sectionType,
sectionOrdinal, $.extend({}, s, { kind: "text" }));
}).then(function (current) {
$save.prop("disabled", false);
$status.text("");
var rows = wergerBoxes((current && current.text) || "").map(function (b, n) {
return { box: n, params: b.params };
});
// This form's own queued change, if any, is what it was last saved with: its rows go back in
// over the boxes they edit, and its new boxes after them.
var own = queuedFormState(wergerKey(s));
if (own) {
rows = rows.map(function (row) {
var mine = own.filter(function (r) {
return r.box === row.box;
})[0];
return mine ? { box: row.box, params: mine.params, loaded: row.params } : row;
}).concat(own.filter(function (r) {
return r.box === null;
}).map(function (r) {
return { box: null, params: r.params };
}));
}
wergerForm.set(rows);
if ($panel.data("dsbAddBoxPending")) {
$panel.removeData("dsbAddBoxPending");
wergerForm.addRow().trigger("focus");
}
loadedSnapshot = contentSnapshot();
}, function () {
$panel.removeData("dsbAddBoxPending");
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
});
}
// Queues the new boxes and the changed ones as one change: a new Werger section, or the boxes
// rewritten and appended in the section already there.
function saveWerger(s) {
var rows = wergerForm.read();
var added = rows.filter(function (r) {
return r.box === null && (r.params["1"] || r.params["2"]);
});
var changed = rows.filter(function (r) {
return r.box !== null && JSON.stringify(r.params) !== JSON.stringify(r.loaded);
});
if (!added.length && !changed.length) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
return;
}
var actions = [];
if (added.length) {
actions.push(added.length === 1 ? "qutiyeke tercimeyê hate zêdekirin"
: added.length + " qutiyên tercimeyê hatin zêdekirin");
}
if (changed.length) {
actions.push(changed.length === 1 ? "qutiyeke tercimeyê hate nûkirin"
: changed.length + " qutiyên tercimeyê hatin nûkirin");
}
var asText = $.extend({}, s, { kind: "text" });
var newBoxes = added.map(function (r) {
return buildWergerBlock(r.params);
}).join("\n\n");
$save.prop("disabled", true);
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
actions.join(" û "),
function (pageContent) {
var current = s.existing &&
readPageSubsection(pageContent, language, sectionType, sectionOrdinal, asText);
if (!current) {
return {
content: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, asText, newBoxes),
warnings: []
};
}
var body = rewriteWergerTops($.trim(current.text || ""), changed);
if (newBoxes) {
body = body ? body + "\n\n" + newBoxes : newBoxes;
}
return {
content: updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, asText, body),
warnings: []
};
},
$save,
$status,
function () {
return drawWergerBoxes($h3, s, newBoxes, language, sectionType, sectionOrdinal);
},
{ key: wergerKey(s), formState: rows }
);
}
function wergerKey(s) {
return "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header;
}
function queuedFormState(key) {
var found = null;
dsbQueue.entries.forEach(function (entry) {
if (entry.key === key && entry.formState) {
found = entry.formState;
}
});
return found;
}
// Same [[Kategorî:Çavkanî]] search widget start.js's own picker uses: choosing an entry inserts
// "* {{Target}}" at the cursor. The field itself stays the plain ".dsb-subsection-text" textarea
// every other "kind: text" subsection already uses, so save/prefill/drafts need no changes.
function renderCavkaniField(s) {
var $ta = $("<textarea class='dsb-input dsb-subsection-text'></textarea>")
.attr("rows", 4)
.attr("placeholder", util.msg.pickOrTypeTemplate);
var picker = cite.buildCategoryTemplateSearch({
placeholder: util.msg.addTemplate,
describe: true,
// Same param dialog Etîmolojî opens on a fresh insert: filled from the picked template's own
// TemplateData, so the user adds "rûpel=", "sernav=", ... before anything lands in the field
// rather than typing a bare {{Target}} and going back to fill params in by hand.
onPick: function (entry) {
cite.openTemplateDialog("{{" + entry.target + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or "Jê bibe" on a call that was never inserted
}
util.insertAtCursor($ta.get(0), "* " + edited + "\n");
$ta.trigger("focus");
});
}
});
var $picker = $("<div class='dsb-cavkani-picker'></div>").append(picker.$element);
$fieldWrap.empty().append($picker, $ta);
}
// One "{{kol3}}" group: its "sernav=" heading and its words. Several of these make up one section --
// "Herwiha binêre" typically carries an unheaded list plus a few headed ones.
function addGroup(s, group) {
var $sernav = $("<input type='text' class='dsb-input'>").attr("placeholder", "ne mecbûrî…");
// {{h-bnr}} takes bare page names, so its rows carry no ⊕: there is nowhere in the call for a
// modifier to go.
var words = buildWordListField(s.header + "…", { modifiers: !s.single });
var $remove = $("<a href='#' class='dsb-quick-link dsb-group-remove'>Komê rake</a>");
var $element = $("<div class='dsb-wordgroup'></div>").append(
// Label beside the box, not above it: stacked, "Sernav" read as a heading for the whole
// group rather than as the name of the field under it. The input is nested in the label so
// clicking the word focuses the box, without needing an id per group.
// A single-call list has no "sernav=" to carry, so the row is built (readGroups still reads
// it) but never shown. Hidden here rather than in renderField: setGroups() rebuilds every
// group on prefill, which would put a visible one back.
$("<div class='dsb-tpl-param-row dsb-param-row-inline'></div>").append(
$("<label class='dsb-adv-label'></label>").text("Sernav").append($sernav)
).toggle(!s.single),
words.$element,
$remove
);
var field = {
$element: $element,
read: function () {
return { sernav: $.trim($sernav.val()), words: words.values() };
},
set: function (g) {
$sernav.val((g && g.sernav) || "");
words.setValues((g && g.words) || []);
}
};
$remove.on("click", function (e) {
e.preventDefault();
var at = $.inArray(field, groupFields);
if (at !== -1) {
groupFields.splice(at, 1);
}
$element.remove();
updateGroupChrome();
});
field.set(group);
groupFields.push(field);
$groups.append($element);
updateGroupChrome();
return field;
}
// With a single group there is nothing to tell apart, so it needs neither a remove link nor a label.
function updateGroupChrome() {
var many = groupFields.length > 1;
$groups.toggleClass("dsb-wordgroups-many", many);
$groups.find(".dsb-group-remove").toggle(many);
}
function readGroups() {
return groupFields.map(function (field) {
return field.read();
});
}
// What a save writes: the same groups with each word kept once, in the order it first appears. A
// list written made bi made often repeats itself, and {{kol3}} only hides that at render time. The
// same word under two different headings is two different entries, so groups are deduped apart.
function readGroupsForSave() {
return readGroups().map(function (group) {
var seen = {};
return $.extend({}, group, {
// The whole row is the key, modifiers and all, so "dan<t:x>" is not the same word as
// "dan".
words: (group.words || []).filter(function (word) {
var key = $.trim(word);
if (seen[key]) {
return false;
}
seen[key] = true;
return true;
})
});
});
}
// Rebuilds the blocks to match what the page (or the draft) holds, always leaving at least one.
function setGroups(s, groups) {
groupFields = [];
$groups.empty();
(groups && groups.length ? groups : [null]).forEach(function (group) {
addGroup(s, group);
});
}
// Loads what the page already has into the form, so an edit starts from the current list rather than
// from an empty one. A wordlist section we can't parse degrades to raw wikitext editing.
function prefill(s) {
$status.removeClass("dsb-status-error dsb-status-ok").text("Tê barkirin…");
$save.prop("disabled", true);
fetchPageSubsection(language, sectionType, sectionOrdinal, s).then(function (current) {
$save.prop("disabled", false);
$status.text("");
if (!current) {
return;
}
// A "place" section has no heading of its own, so whether the page already has one cannot
// be seen in the DOM the way a "====" subsection can -- it is known only once its content
// has been read. That is what decides "nûkirin" or "zêdekirin" in the edit summary.
if (s.place) {
s.existing = !!(current.images ? current.images.length : $.trim(current.text || ""));
// The label was set before this content arrived, when nothing yet said one was there.
$save.text(s.existing ? util.msg.edit : util.msg.add);
}
if (current.kind === "images" && imagesField) {
imagesField.setValues(current.images);
return;
}
// A free section's field was built before its content was known: a list turns it into the
// {{kol3}} form, anything else leaves it the wikitext box it already is.
if (s.free && current.kind === "wordlist" && !groupFields.length) {
s.kind = "wordlist";
renderWordListField(s);
setGroups(s, current.groups);
announceBullets(current);
return;
}
if (current.kind === "wordlist" && groupFields.length) {
setGroups(s, current.groups);
announceBullets(current);
return;
}
if (s.kind === "wordlist") {
editAsText = true;
groupFields = [];
renderTextArea(s, 6);
$status.text(s.unknown
? "Ev beş nayê naskirin — wekî wîkîtekst tê guhartin."
: "Şablona vê lîsteyê nehate naskirin — wekî wîkîtekst biguhêre.");
}
setFieldText(current.text);
}, function () {
$save.prop("disabled", false);
$status.addClass("dsb-status-error").text("Nikare muhtewaya heyî bixwîne.");
}).then(function () {
if (currentSubsection() === s) {
loadedSnapshot = contentSnapshot();
}
});
}
// A list the page writes made bi made is about to become {{kol3}}, which is a change to the
// section even when not one word of it is touched -- so the save is not held back as a no-op.
var loadedFromBullets = false;
function announceBullets(current) {
if (!current.fromBullets) {
return;
}
loadedFromBullets = true;
$status.text("Ev lîste made bi made hatiye nivîsîn — bi qeydkirinê dibe {{kol3}}.");
}
// What the box held when the page's content was put in it, so a save that changed nothing queues
// nothing -- renaming a heading does not also rewrite its body.
var loadedSnapshot = null;
function contentSnapshot() {
if (imagesField) {
return JSON.stringify(imagesField.values());
}
return JSON.stringify(groupFields.length ? readGroups() : fieldText());
}
// One "{{kol3}}" group per block, plus a way out to raw wikitext for a list no row can express --
// a shared subpage list like {{herwiha binêre/ane}}, say.
function renderWordListField(s) {
$fieldWrap.empty();
chipSurface = null;
editAsText = false;
var $addGroup = $("<a href='#' class='dsb-quick-link'>+ Komek lê zêde bike</a>");
$addGroup.on("click", function (e) {
e.preventDefault();
addGroup(s, null);
});
$groups = $("<div class='dsb-wordgroups'></div>");
$fieldWrap.append($groups, $addGroup);
addGroup(s, null);
// One call takes one flat list of names, so there is no second group to add.
if (s.single) {
$addGroup.hide();
}
var $toRaw = $("<a href='#' class='dsb-quick-link'>Wekî wîkîtext biguhêre</a>");
$toRaw.on("click", function (e) {
e.preventDefault();
var current = s.place
? wt.buildHbnrCall(loosePlaceWords(readGroupsForSave()))
: subsectionBodyWikitext(language, s, readGroupsForSave());
editAsText = true;
groupFields = [];
$groups = null;
renderTextArea(s, 3);
setFieldText(current);
util.initManualResize($fieldWrap);
});
$fieldWrap.append($toRaw);
util.initManualResize($fieldWrap);
}
function renderField() {
var s = currentSubsection();
loadedSnapshot = null;
loadedFromBullets = false;
$fieldWrap.empty();
groupFields = [];
$groups = null;
editAsText = false;
chipSurface = null;
wergerForm = null;
imagesField = null;
$status.removeClass("dsb-status-error dsb-status-ok").text("");
// Save stays disabled (but "Betal bike" usable) until something is actually chosen.
$save.prop("disabled", !s);
if (!s) {
$save.text(util.msg.add);
lastId = null;
return; // still on "Binebeşekê hilbijêre"
}
if (s.kind === "wordlist") {
renderWordListField(s);
} else if (s.kind === "images") {
imagesField = util.buildImageListField({
prefix: "dsb-",
rowClass: "dsb-tpl-param-row dsb-param-row-inline",
inputClass: "dsb-input",
addClass: "dsb-quick-link",
pageName: mw.config.get("wgTitle")
});
$fieldWrap.append(imagesField.$element);
} else if (s.header === "Etîmolojî") {
renderTemplateEditor(s, templateChoices("etymTpls", language));
} else if (s.place === "pron") {
renderTemplateEditor(s, templateChoices("pronTpls", language));
} else if (s.kind === "werger") {
renderWergerField(s);
} else if (s.header === "Çavkanî") {
renderCavkaniField(s);
} else {
renderTextArea(s, 3);
}
$save.text(s.existing ? util.msg.edit : util.msg.add);
// Covers whichever branch above built the field. Idempotent, so a textarea already carrying a
// grip is skipped.
util.initManualResize($fieldWrap);
lastId = s.id;
// A draft always wins over re-reading the page: it is the newer, unsaved state.
if (drafts[s.id]) {
restoreDraft(s, drafts[s.id]);
} else if (s.kind === "werger" && s.existing) {
prefillWerger(s);
} else if (s.existing || s.place) {
prefill(s);
}
}
$select.on("change", function () {
captureDraft();
renderField();
});
renderField();
// Back to the state a fresh open would show: no draft, no picked subsection. Called directly
// rather than through the "change" handler above, so the discarded draft is never captured first.
function resetForm() {
drafts = {};
if (!onlyHeader) {
$select.val("").trigger("wiktku:refresh");
}
renderField();
}
$save.on("click", function () {
var s = currentSubsection();
if (!s) {
return;
}
if (s.kind === "werger") {
saveWerger(s);
return;
}
var newName = readName(s);
if (!newName) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Navê beşê vala ye.");
return;
}
var asText = editAsText || (s.kind !== "wordlist" && s.kind !== "images");
var effective = asText ? $.extend({}, s, { kind: "text" }) : s;
var content;
var hasContent;
if (asText) {
content = fieldText();
hasContent = !!content;
} else if (s.kind === "images") {
content = imagesField ? imagesField.values() : [];
hasContent = content.length > 0;
} else {
content = readGroupsForSave();
hasContent = content.some(function (group) { return group.words.length; });
}
var renaming = newName !== s.header;
if (renaming) {
queueRename(s, newName);
}
var unchanged = loadedSnapshot !== null && contentSnapshot() === loadedSnapshot
&& !loadedFromBullets;
if (!hasContent || unchanged) {
if (unchanged && !renaming) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Tiştek nehatiye guhartin.");
}
return;
}
$save.prop("disabled", true);
queueChange(
subsectionPlaceLabel(s, language, sectionType, $h3),
"beşa «" + s.header + "» hate " + (s.existing ? "nûkirin" : "zêdekirin"),
function (pageContent) {
var body = content;
return {
content: s.free
? updateFreeSection(pageContent, language, effective, body)
: (s.place
? writeLoosePlace(pageContent, language, sectionType, sectionOrdinal, effective,
loosePlaceBody(effective, body, language))
: (s.existing
? updatePageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body)
: insertPageSubsection(pageContent, language, sectionType, sectionOrdinal, effective, body))),
warnings: []
};
},
$save,
$status,
// A new subsection gets a block of its own at the position it will land in; one already on the
// page is redrawn where it stands.
function () {
// A free section is redrawn under its own heading, which is the element the panel hangs
// on.
if (s.free) {
var restoreFree = replaceFreeSectionInPlace($h3,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restoreFree) {
restoreFree();
restoreFree = null;
}
};
}
// A "place" section has no heading to redraw between, so it is found in the DOM by what
// it renders as and replaced there.
if (s.place) {
var restoreLoose = replaceLoosePlaceInPlace($h3, effective,
loosePlacePreviewWikitext(effective, content, language));
return function () {
if (restoreLoose) {
restoreLoose();
restoreLoose = null;
}
};
}
if (!s.existing) {
var $preview = buildSubsectionPreviewDom($h3, s, content, language);
return function () {
$preview.remove();
};
}
var restore = replaceSubsectionInPlace($h3, effective.header,
subsectionBodyWikitext(language, effective, content));
return function () {
if (restore) {
restore();
restore = null;
}
};
},
{ key: s.free
? "section:" + s.header + "|" + s.free.ordinal
: "subsection:" + sectionType + "|" + sectionOrdinal + "|" + s.header }
);
});
$panel.empty().append($selectWrap, $nameWrap, $fieldWrap, $actions);
}
module.exports = {
buildSubsectionPanel: buildSubsectionPanel
};
}());
// </nowiki>
3g1mchweci8ui19vy7aqjqln1iyk4ep
MediaWiki:Gadget-wikt.dsb-pos.js
8
1359511
6865614
2026-10-23T22:08:51Z
Wikihez
7344
DefSideBoxes: the pos panel as a module of its own, loaded with its pencil
6865614
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.dsb-pos.js]]
*
* The part-of-speech picker of DefSideBoxes, loaded by wikt.dsb from a part-of-speech heading's pencil.
* The queue, previews and shared helpers are in [[MediaWiki:Gadget-wikt.dsb-panels.js]].
*/
(function () {
"use strict";
var sections = require("ext.gadget.wikt.core").sections;
var edit = require("ext.gadget.wikt.edit");
var util = edit.util;
var posparams = edit.posparams;
var wt = edit.wt;
var startData = edit.data;
var escapeRegExp = util.escapeRegExp;
var canonicalTemplateKey = wt.canonicalTemplateKey;
var parseTemplateCalls = wt.parseTemplateCalls;
var sectionNames = sections.sectionNames;
var headingOuter = sections.headingOuter;
var shared = require("ext.gadget.wikt.dsb-panels").shared;
var SubmitAbort = shared.SubmitAbort;
var fetchPageContent = shared.fetchPageContent;
var fillWithRendered = shared.fillWithRendered;
var headingPosition = shared.headingPosition;
var hideQueueIfIdle = shared.hideQueueIfIdle;
var langLabel = shared.langLabel;
var locateSection = shared.locateSection;
var makeSearchable = shared.makeSearchable;
var queueChange = shared.queueChange;
var sectionLabel = shared.sectionLabel;
// metadata[lang].g, minus the grammatical-number codes start.js filters out with isNumberCode(), rendered
// the way its genderLabel() does ("m-p" -> "mê (pirjimar)").
// start.js asks transitivity and structural form as two questions and joins them as "<form>-<base>", so
// "gerguhêz" alone is "gh" and "xwerû" + "gerguhêz" is "x-gh".
// Template call convertSectionToPos() drops under a newly-chosen POS heading, e.g. "Hoker" -> "{{hoker|ku}}".
// A "Formeke X" (form-of) heading uses the unchanged base pos template. Doubles as the list of template
// calls a conversion may find under the OLD heading and replace, rather than leave stacked beside the new one.
// A section's template call. "Formeke Xê" uses X's own template with form=erê. Biwêj is recognised so a
// conversion can replace its {{biwêj}}, but never written: no new ones.
function posTemplateFor(header) {
var form = /^Formeke (.+)ê$/.exec(header || "");
if (form && startData.posChoices[form[1]] && sections.hasFormHeading(form[1])) {
return { name: form[1], params: ["form=erê"] };
}
var key = util.decapitalize(header || "");
return startData.posChoices[key] ? { name: key, params: [] } : null;
}
// convertSectionToPos() drops whichever of the tag's names is actually present.
var MANE_TAG_REGEX = wt.MANE_TAG_REGEX;
// ---- converting an unclassified "=== Mane ===" placeholder heading into a real POS heading ----
// Some pages carry a heading literally named "Mane" (optionally "Mane 2" etc.) instead of a real
// part-of-speech name, tagged right below with {{cure?|<lang>}} (ku.wiktionary's "part of speech not yet
// determined" maintenance flag). It is a section key of its own (sections.MANE_SECTION), so the shared
// locator finds it like any other heading; only the rewriting below is particular to it.
// Renames the "=== Mane ===" placeholder to the chosen POS heading, drops the maintenance tag line right
// after it if present (MANE_TAG_REGEX -- cure? or any of its known aliases), and inserts the matching
// "{{<pos>|<lang>}}" call in its place (skipped when the section has no template of its own).
// Other content under the heading is left untouched.
/**
* Where a section's own headword template sits, as {index, text}, or {index: -1} when it has none.
*
* Not always the line under the heading: a "{{ku-tewîn-lk}}" line and any number of "[[Wêne:...]]"
* images can come first. Everything down to the first definition line is searched, and index doubles
* as the slot a new call belongs in when there is no old one.
*/
function findPosTemplateLine(lines, headingLine, sectionType) {
var oldSpec = posTemplateFor(sectionType);
var oldTemplate = oldSpec ? oldSpec.name : (sectionType === "Biwêj" ? "biwêj" : null);
var oldTemplateRegex = oldTemplate &&
new RegExp("^\\{\\{\\s*" + escapeRegExp(oldTemplate) + "\\s*[|}]");
var at = headingLine + 1;
while (at < lines.length && !/^#/.test(lines[at]) && !/^==/.test(lines[at])) {
if (MANE_TAG_REGEX.test(lines[at]) ||
(oldTemplateRegex && oldTemplateRegex.test(lines[at]))) {
return { index: at, text: lines[at], end: at };
}
at++;
}
return { index: -1, text: "", end: at };
}
// Named parameters merged in order, each written once: a later value replaces an earlier one of the
// same name. Positional values are kept as they come.
function mergeCallParams() {
var out = [];
var seen = {};
$.each(Array.prototype.slice.call(arguments), function (_, list) {
$.each(list || [], function (__, param) {
var m = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(param);
if (!m) {
out.push(param);
return;
}
// "z" and "c" are one name to Modul:rêziman, so the form's "c=m" meets the page's "z=".
var key = wt.canonicalParamName(m[1]);
if (!Object.prototype.hasOwnProperty.call(seen, key)) {
seen[key] = out.length;
out.push(param);
return;
}
// The page's own spelling stays when it carried a value; an empty one gives way entirely.
var earlier = /^\s*([^=|]+?)\s*=([\s\S]*)$/.exec(out[seen[key]]);
// "?" asks for a value, so it never replaces one the page already has.
if ($.trim(earlier[2]) && $.trim(m[2]) === "?") {
return;
}
out[seen[key]] =$.trim(earlier[2]) ? earlier[1] + "=" + m[2] : param;
});
});
return out;
}
// What the call on the page already says, minus its language. Carried over only when the new section
// uses that same template -- "z=m" survives Navdêr becoming Formeke navdêrê, while a verb's "c=h-gh"
// has no meaning on a noun and is left behind.
function keptCallParams(line, templateName) {
var call = parseTemplateCalls($.trim(line || "")).calls[0];
if (!call || canonicalTemplateKey(call.name) !== canonicalTemplateKey(templateName)) {
return [];
}
return call.params.slice(1).filter(function (param) {
return /=/.test(param);
});
}
// The headword call a converted section gets, or "" when the new section has none. What the old call
// (found, from findPosTemplateLine) said keeps its place, then the section's own defaults, then what the
// form was filled in with -- each name written once, the last value winning, so a converted call reads
// as small a change from the old one as it can.
function posCallLine(found, language, newSectionType, posParams) {
var spec = posTemplateFor(newSectionType);
if (!spec) {
return "";
}
var kept = found.index !== -1 ? keptCallParams(found.text, spec.name) : [];
// A lemma is not an inflected form, so the old section's form=erê stays behind.
if (spec.params.indexOf("form=erê") === -1) {
kept = kept.filter(function (param) {
return !/^\s*form\s*=\s*erê\s*$/.test(param);
});
}
var params = mergeCallParams(kept, spec.params, posParams ? posParams.split("|") : []);
return "{{" + spec.name + "|" + language + (params.length ? "|" + params.join("|") : "") + "}}";
}
function convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, newSectionType, posParams) {
if (!sectionNames[newSectionType]) {
throw new SubmitAbort("Navê cureyê rêzimanî yê hilbijartî nediyar e.");
}
// locateSection() already handles the "Mane" placeholder as a section type of its own.
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var lines = loc.lines;
var headingLine = loc.posLineIndex;
var depth = loc.depth;
// A section straight under a numbered group heading takes that heading's place: the group heading
// held nothing but its gloss.
var group = depth > 3 ? wt.numberedGroupAbove(lines, headingLine, depth) : -1;
if (group !== -1) {
lines.splice(group, headingLine - group);
headingLine = group;
depth--;
}
// At the heading's own depth. Written without a number: the renumbering pass below decides every
// number in this language section at once, so one carried over from the old name would just be
// overwritten.
var marks = wt.headingMarks(depth);
lines[headingLine] = marks + " " + newSectionType + " " + marks;
// The line under the heading is either the old section's own template call ("{{biwêj|ku}}") or, for a
// Mane placeholder, the "needs a POS" maintenance tag. Both are replaced by the new POS's call.
// The new call takes the old one's own place -- or, when there was none, the slot just above the
// definitions, which is where the wizard writes it.
var found = findPosTemplateLine(lines, headingLine, sectionType);
var insertAt = found.index !== -1 ? found.index : found.end;
if (found.index !== -1) {
lines.splice(found.index, 1);
}
var call = posCallLine(found, language, newSectionType, posParams);
if (call) {
lines.splice(insertAt, 0, call);
}
// Renaming a section can make it repeat one the page already had, and can equally leave a name that
// is no longer repeated. Both are settled by renumbering the whole language section: a "Rengdêr"
// beside a new "Rengdêr" becomes "Rengdêr 1" and "Rengdêr 2", and a number left behind by a name
// that is now unique is dropped.
var body = lines.join("\n");
var nextLang = body.indexOf("\n== ", 1);
var end = nextLang === -1 ? body.length : nextLang;
body = wt.numberRepeatedPosHeadings(body.slice(0, end)) + body.slice(end);
return pageContent.slice(0, loc.langSectionIndex) + body;
}
// Draws a section's headword line from wikitext where the page's own line stands, or above the
// definitions when the section has none. The page's line is detached rather than dropped, so the
// returned function puts it back.
function replaceHeadwordInPlace($h3, wikitext) {
if (!$.trim(wikitext || "")) {
return null;
}
var $old = $();
var $at = $();
for (var $cursor = headingOuter($h3).next(); $cursor.length; $cursor = $cursor.next()) {
if ($cursor.is("p")) {
$old = $cursor;
break;
}
if (sections.headingDepth($cursor) || $cursor.is("ol")) {
$at = $cursor;
break;
}
}
var $slot = $("<div class='dsb-sense-preview'></div>");
if ($old.length) {
$old.before($slot);
} else if ($at.length) {
$at.before($slot);
} else {
return null;
}
var $body = $old.detach();
function restore() {
if (!$slot) {
return;
}
$body.insertBefore($slot);
$slot.remove();
$slot = null;
}
fillWithRendered($slot, wikitext, restore);
return restore;
}
// Builds the "choose a part of speech" picker that reclassifies a section -- an unclassified
// "=== Mane ===" placeholder, or a "=== Biwêj ===" that turns out to be a real part of speech. Saved via
// convertSectionToPos(); lists every name from sectionNames except the one the section already is.
function buildConvertPosPanel($panel, $h3, language, sectionType, sectionOrdinal) {
var $select = $("<select class='dsb-convert-pos-select'></select>");
// Label-less, like the subsection picker's: it holds the "nothing chosen yet" value without showing
// up as a row, so the prompt lives in the input's placeholder instead.
$select.append($("<option value=''></option>"));
Object.keys(sectionNames).forEach(function (name) {
// The section's own part of speech is listed too, marked: picking it changes nothing about the
// heading and leaves the parameters below to be edited on their own.
$select.append($("<option></option>").val(name)
.text(name === sectionType ? name + " (ya niha)" : name));
});
var $selectWrap = makeSearchable($select, "Li cureyekî rêzimanî bigere");
// Opens on the section's own part of speech, so editing only its parameters needs no pick.
if (sectionNames[sectionType]) {
$select.val(sectionType).trigger("wiktku:refresh");
}
// The same card the entry wizard shows under its part-of-speech question -- gender, transitivity,
// the transliteration boxes and everything behind "Tercîhên pêşketî". Shared from
// wikt.edit/posparams.js, so reclassifying a section here offers exactly what creating one in the
// wizard does.
var card = posparams.build({ lang: language, pos: "" });
// Opened on the call the section already has, so this edits the current template rather than
// starting from a blank line. setContext() rebuilds the line whenever the part of speech moves,
// so the prefill is only the starting point.
var spec0 = posTemplateFor(sectionType);
card.setContext(language, spec0 ? spec0.name : "", /^Formeke /.test(sectionType));
fetchPageContent().then(function (pageContent) {
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
var found = findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType);
if (found.index !== -1 && ($select.val() || sectionType) === sectionType) {
card.setWikitext(found.text);
}
} catch (e) { /* an unreadable section just leaves the card as it was */ }
});
$select.on("change", function () {
var spec = posTemplateFor($select.val());
card.setContext(language, spec ? spec.name : "", /^Formeke /.test($select.val() || ""));
});
// "gh" on its own, or "<form>-<base>" once a structural form is picked -- start.js's own composition.
// convertSectionToPos() writes the template itself, so only what follows "{{pos|lang" is wanted
// from the card's line.
function posParams() {
var m = /^\{\{[^|}]*\|[^|}]*\|?([\s\S]*)\}\}$/.exec(card.wikitext());
return m ? m[1] : "";
}
var $save = $("<button type='button' class='dsb-save'></button>").text(util.msg.add);
var $cancel = $("<button type='button' class='dsb-cancel'></button>").text(util.msg.cancel);
var $status = $("<span class='dsb-status'></span>");
var $actions = $("<div class='dsb-form-actions'></div>").append($save, $cancel, $status);
// dsb.js owns the pencil and the panel's built state, so it does the closing.
$cancel.on("click", function () {
$panel.trigger("dsbCancel");
hideQueueIfIdle();
});
$save.on("click", function () {
var chosen = $select.val();
if (!chosen) {
$status.removeClass("dsb-status-ok").addClass("dsb-status-error").text("Pêşî cureyekî rêzimanî hilbijêre.");
return;
}
var params = posParams();
$save.prop("disabled", true);
$status.removeClass("dsb-status-error").text("");
queueChange(
langLabel(language),
"beşa «" + sectionLabel(sectionType, $h3) + "» bû «" + chosen + "»",
function (pageContent) {
return { content: convertSectionToPos(pageContent, language, sectionType, sectionOrdinal, chosen, params), warnings: [] };
},
$save,
$status,
function () {
// The heading keeps its text, which every other queued change finds the section by; a
// note under it names the new one. The headword line is drawn as the save will write it.
var $note = $("<div class='dsb-preview-inline dsb-preview-heading-note'></div>")
.text("→ " + chosen + (params ? " (" + params + ")" : "") + " — nû, hîn nehatiye qeydkirin");
headingOuter($h3).after($note);
var dropped = false;
var restoreHeadword = null;
fetchPageContent().then(function (pageContent) {
if (dropped) {
return;
}
var call;
try {
var loc = locateSection(pageContent, language, sectionType, sectionOrdinal);
call = posCallLine(findPosTemplateLine(loc.lines, loc.posLineIndex, sectionType),
language, chosen, params);
} catch (e) {
return;
}
restoreHeadword = replaceHeadwordInPlace($h3, call);
});
return function () {
dropped = true;
$note.remove();
if (restoreHeadword) {
restoreHeadword();
restoreHeadword = null;
}
};
},
// Runs after every other queued change: it renames the "Mane" heading those changes locate
// themselves by, so anything edited on this section has to be applied first.
{ runLast: true, key: "convert:" + sectionType + "|" + sectionOrdinal, pagePosition: headingPosition($h3) }
);
});
$panel.empty().append($selectWrap, card.$element, $actions);
}
module.exports = {
buildConvertPosPanel: buildConvertPosPanel
};
}());
// </nowiki>
jk51rvrjip5d4hyuy66xbsywqftl5dp
MediaWiki:Gadget-wikt.edit-data.js
8
1359512
6865621
2026-10-23T22:15:17Z
Wikihez
7344
wikt.edit's data tables as a module of their own
6865621
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit-data.js]]
*
* wikt.edit's data tables ([[MediaWiki:Gadget-wikt.edit/data-main.json]]) as a module of their own, so neither
* module goes over the 100 KB ResourceLoader keeps in the browser's module store.
*
* var data = require("ext.gadget.wikt.edit-data");
*/
module.exports = require("./wikt.edit/data-main.json");
// </nowiki>
sh7bjusizsne3qavt7se7wr796gfycx
6865646
6865621
2026-10-24T08:45:58Z
Wikihez
7344
data.metadata from wikt.lang-metadata
6865646
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.edit-data.js]]
*
* wikt.edit's data tables ([[MediaWiki:Gadget-wikt.edit/data-main.json]]) as a module of their own, so neither
* module goes over the 100 KB ResourceLoader keeps in the browser's module store. The per-language table,
* data.metadata, is wikt.lang-metadata's, the one the translation adder reads too.
*
* var data = require("ext.gadget.wikt.edit-data");
*/
var data = require("./wikt.edit/data-main.json");
data.metadata = require("ext.gadget.wikt.lang-metadata");
module.exports = data;
// </nowiki>
mk3vk8cw7exl3j5htnfvrdrizvg8rw5
MediaWiki:Gadget-wikt.start-wizard-steps.js
8
1359513
6865627
2026-10-23T22:38:29Z
Wikihez
7344
Start wizard: its step definitions as a module of their own
6865627
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.start-wizard-steps.js]]
*
* The step definitions of [[MediaWiki:Gadget-wikt.start-wizard.js]]: what each step asks, offers and writes.
* A module of its own so that neither half passes the 100 KB the browser's module store keeps. The wizard
* calls it with ctx, its own helpers and settings; the few settings it changes after loading
* (DEVER_QUICK_PICKS, SENSE_GROUPS, catSrcTemplates, pageName, wiktkuLabelSets) are read through ctx as
* they are at that moment.
*/
/* global wikt */
module.exports = function (ctx) {
var GENDER_BASE_LABELS = ctx.GENDER_BASE_LABELS;
var ZARAVA_LANG_CODES = ctx.ZARAVA_LANG_CODES;
var addLangMode = ctx.addLangMode;
var applyTplDescriptions = ctx.applyTplDescriptions;
var bindMoreMenuCloser = ctx.bindMoreMenuCloser;
var buildChipField = ctx.buildChipField;
var buildCite = ctx.buildCite;
var buildDeverTpl = ctx.buildDeverTpl;
var buildEtiketTpl = ctx.buildEtiketTpl;
var buildManeidTpl = ctx.buildManeidTpl;
var buildSenseTpl = ctx.buildSenseTpl;
var capitalize = ctx.capitalize;
var decapitalize = ctx.decapitalize;
var fetchCatSrcTemplates = ctx.fetchCatSrcTemplates;
var findLangHeader = ctx.findLangHeader;
var getLangLabel = ctx.getLangLabel;
var getValues = ctx.getValues;
var highlightLinebreak = ctx.highlightLinebreak;
var initSelects = ctx.initSelects;
var insertAtCursor = ctx.insertAtCursor;
var insertLineSnippet = ctx.insertLineSnippet;
var isReconstruction = ctx.isReconstruction;
var isWordGroupCls = ctx.isWordGroupCls;
var langHasMultipleScripts = ctx.langHasMultipleScripts;
var langsByCode = ctx.langsByCode;
var makeModifierLi = ctx.makeModifierLi;
var makeTrashButton = ctx.makeTrashButton;
var makeWordListStep = ctx.makeWordListStep;
var manePlaceholder = ctx.manePlaceholder;
var markTemplateOptions = ctx.markTemplateOptions;
var needsTransliteration = ctx.needsTransliteration;
var nimuneTranslationPrompt = ctx.nimuneTranslationPrompt;
var populateMenus = ctx.populateMenus;
var refresh = ctx.refresh;
var senseHasExtraContent = ctx.senseHasExtraContent;
var splitTercimeIzah = ctx.splitTercimeIzah;
var stripWikilinks = ctx.stripWikilinks;
var tplNameFromSnippet = ctx.tplNameFromSnippet;
var wiktkuCite = ctx.wiktkuCite;
var wiktkuEdit = ctx.wiktkuEdit;
var wiktkuEnsureLabels = ctx.wiktkuEnsureLabels;
var wiktkuLabelListeners = ctx.wiktkuLabelListeners;
var wiktkuLabelSuggestions = ctx.wiktkuLabelSuggestions;
var wiktkuPosparams = ctx.wiktkuPosparams;
var wiktkuUtil = ctx.wiktkuUtil;
var wiktkuWikitext = ctx.wiktkuWikitext;
var wordGroups = ctx.wordGroups;
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + ctx.pageName + '" bi çi zimanî ye?',
// Populated from data-main.json (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + ctx.pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data-main.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qism ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vala bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", wiktkuUtil.msg.addTemplate);
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
// The palette lives in wikt.edit, shared with DefSideBoxes' own Bilêvkirin box.
wrap.append(wiktkuUtil.buildIpaPalette({
insertText: function (text, back) {
insertAtCursor(ta[0], text);
if (back) {
var pos = ta[0].selectionStart - back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
}
ta.focus();
}
}));
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text(wiktkuUtil.msg.loading);
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'></button>")
.text("✕ " + wiktkuUtil.msg.cancel);
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not expandtemplates: the preview needs rendered HTML, not expanded
// wikitext. The leading "* " is dropped so it renders as a line, not a bulleted list.
new mw.Api().post({
action: "parse",
title: ctx.pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate çêkirin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + ctx.pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from posChoices (data-sections.json) and posExamples (data-main.json).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen,
// shown as plain text right under the dropdown (see the
// .wiktku-start-pos-example build/refresh() above). Kept clear of
// the Mane textarea's placeholder, which answers "what to type
// here" rather than "what this grammatical category means".
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-start-posparams [data-pp-opt='form']").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs, parsed straight out of tplLine (not the posparams input fields, which a cache/section restore doesn't repopulate) once niha/borî/transitivity are all present.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var niha = wiktkuWikitext.callParamValue(tplLine, "niha");
var borî = wiktkuWikitext.callParamValue(tplLine, "borî");
var lekturCode = wiktkuWikitext.callParamValue(tplLine, "c");
var cParts = lekturCode ? /^(?:([xphb])-)?(n?gh)$/.exec(lekturCode) : null;
var lekturForm = cParts ? (cParts[1] || "") : "";
var lktransitivity = cParts ? cParts[2] : "";
var lkForm = lktransitivity === "gh" ? "gerguhêz" : (lktransitivity === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + ctx.pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + ctx.pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data-main.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + ctx.pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
// The card itself lives in wikt.edit/posparams.js, shared with DefSideBoxes -- see there for the
// fields and the per-language gating. This is only the wizard's side: which language and part of
// speech it is for, and mirroring gender/number into the two hidden steps the wizard keeps.
posparams: {
id: "posparams",
// Fused onto the "pos" card in styles.css so the two read as one question. The "xisûsiyet"
// name is a no-CSS fallback: its h3 is hidden, and every field below is self-labelled.
name: "xisûsiyet",
question_fmt: "",
card: null,
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-step").length) return null;
var values = getValues();
steps.posparams.card = wiktkuPosparams.build({
lang: values.lang,
pos: values.pos,
onChange: refresh,
// steps.cinsiyet/steps.jimar own these hidden fields; the card reports rather than
// writes, so it needs no knowledge of them.
onSync: function (cinsiyet, jimar) {
$("#wiktku-start-cinsiyet-field").val(cinsiyet || "");
$("#wiktku-start-jimar-field").val(jimar || "");
}
});
stepDiv.append(steps.posparams.card.$element);
return null;
},
rebuildTa: function () {
if (steps.posparams.card) steps.posparams.card.rebuild();
},
/** Called from refresh() — re-gates every row for the current language/pos. */
syncHelpers: function () {
if (!steps.posparams.card) return;
var values = getValues();
steps.posparams.card.setContext(values.lang, values.pos);
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Nothing on keystrokes: only the Werger gloss list needs a live Mane edit, and rebuilding
// it re-reads every sense. Deferred to blur; the wikitext is read fresh on output anyway.
dfnArea.on("blur", function () { refresh(); });
// — Îzaha maneyî (optional second half of the mane) —
// A non-Kurdish entry's gloss is a translation followed by {{îzah|…}} explaining it. The
// mane box is always the mane box and the explanation opens under it when asked for, so
// the pair reads as one gloss rather than as a different kind of field. getSenses() joins
// the two back together, so every consumer downstream sees one gloss string.
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 2)
.attr("placeholder", "Îzaha maneyî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var izahWrap = $("<div class='wiktku-dfn-izah-wrap'></div>")
.append($("<span class='wiktku-dfn-sublabel'></span>")
.append($("<span class='wiktku-dfn-sublabel-term'></span>").text("Îzaha maneyî")).append(": "))
.append(izahArea)
.hide();
// Opening it from a restore, where the text decides rather than a click.
var izahOpen = false;
function setIzahOpen(open) {
izahOpen = !!open;
updateManeMode();
}
block.data("setIzahOpen", setIzahOpen);
izahArea.on("input", function () {
// dfnArea drives the live preview and the auto-linker; the îzah is part of the gloss,
// so a change to it has to reach them too.
dfnArea.trigger("input");
});
izahArea.on("blur", function () { refresh(); });
// The mane box gives way to the template title box when a template is chosen; the îzah
// goes with it, since a template gloss has no separate explanation.
function updateManeMode() {
var isTpl = !!tplSelect.val();
if (isTpl) {
dfnArea.hide();
izahWrap.hide();
return;
}
dfnArea.show();
dfnArea.attr("placeholder", manePlaceholder(getValues().lang || ""));
// {{îzah}} explains a translation, so it belongs to entries in another language; a
// Kurdish sense is its own definition and is not offered one. A box that already holds
// text stays on screen whatever the language -- getSenses() joins it into the gloss
// either way, and hiding it would make that happen unseen.
var lang = getValues().lang || "";
var offer = !!lang && lang !== "ku";
// A non-Kurdish gloss is a translation and its explanation, so the box opens with the
// mane box rather than waiting to be asked for.
var open = offer || izahOpen || $.trim(izahArea.val()) !== "";
izahWrap.toggle(open);
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from data-main.json.
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", wiktkuUtil.msg.searchTemplate);
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data-main.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so the word-list groups (hevmane, dijmane, formên
// alternatîv…) make no sense for them — the class hides both those groups and their
// entries in the "Hevmane û Bêhtir" menu, so they cannot be asked for either. Etîket, Kategorî,
// Nimûne and Jêgirtin stay available.
function updateFieldVisibility() {
block.toggleClass("wiktku-sense-is-tpl", !!tplSelect.val());
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
izahWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şaşnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// Dever/Şîwe, Etîket and Kategorî as chip+toggle fields, so a sense that leaves them empty
// stays short. getOutput() folds Dever/Şîwe into {{dever|}} for ku, {{e|}} otherwise.
// Each closes the other two on open; onOpen runs from a click, so forward references hold.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîwe",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
// Kurmancî's pills are the handful in data-main.json; another language gets the regions
// Modul:labels tags for it. The rest are found by typing.
quickPicks: function () {
var lang = getValues().lang || "ku";
if (lang === "ku") return ctx.DEVER_QUICK_PICKS;
return ctx.wiktkuLabelSets ? wiktkuEdit.labels.taggedRegionsFor(ctx.wiktkuLabelSets, lang) : [];
},
suggestions: function () { return wiktkuLabelSuggestions("dever"); },
onOpen: function () { etiketField.close(); kategoriField.close(); wiktkuEnsureLabels(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
suggestions: function () { return wiktkuLabelSuggestions("etiket"); },
onOpen: function () { deverField.close(); kategoriField.close(); wiktkuEnsureLabels(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
suggestions: function () { return wiktkuLabelSuggestions("kategori"); },
onOpen: function () { deverField.close(); etiketField.close(); wiktkuEnsureLabels(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Maneid -- the anchor another entry links a sense by, written "{{maneid|lang|x}}" at the
// head of the line. One value rather than a list, so it is a plain box and not a chip
// field; it still behaves like one to the "Hevmane û Bêhtir" menu through the small api below.
var maneidInput = $("<input type='text'>")
.addClass("wiktku-dfn-maneid-input wiktku-start-narrow")
.attr("placeholder", "id");
var maneidWrap = $("<div class='wiktku-dfn-chipfield-wrap wiktku-dfn-maneid-wrap'></div>")
.append($("<label class='wiktku-dfn-maneid-label'></label>")
.text("Maneid ")
.append(maneidInput))
.hide();
var maneidField = {
wrap: maneidWrap,
open: function () { maneidWrap.show(); maneidInput.focus(); },
values: function () {
var v = $.trim(maneidInput.val() || "");
return v ? [v] : [];
}
};
// Dever/Şîwe starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
//
// Shown only for a language with regions: every Kurdish variety, and any other language
// Modul:labels tags one for (read once the labels arrive). One that already has a value stays.
function updateDeverOpenState() {
var lang = getValues().lang || "";
var hasRegions = !lang || wiktkuEdit.labels.isKurdish(lang) ||
(ctx.wiktkuLabelSets && wiktkuEdit.labels.regionalFor(ctx.wiktkuLabelSets, lang).length > 0);
if (!hasRegions && !ctx.wiktkuLabelSets) wiktkuEnsureLabels();
var show = hasRegions || deverField.values().length > 0;
deverField.wrap.toggle(show);
if (!show) deverField.panel.hide();
deverField.setOpenDefault(lang === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
wiktkuLabelListeners.push(updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
// The widget is still built on a reconstruction page so every reference to it stays valid;
// it just never reaches the DOM, so it shows nothing and contributes nothing to getOutput().
//
// The row is the same shape DefSideBoxes puts under a sense -- a few chips for the common
// things, everything else behind "Hevmane û Bêhtir" -- so the two gadgets are learned once. Etîket
// and Kategorî start in the menu and their toggle only joins the row once picked.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>");
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>");
if (!isReconstruction) {
chipFieldRow.append(deverField.wrap);
chipFieldPanels.append(deverField.panel);
}
etiketField.wrap.hide();
kategoriField.wrap.hide();
chipFieldRow.append(etiketField.wrap).append(kategoriField.wrap).append(maneidWrap);
chipFieldPanels.append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(izahWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// Sub-field groups. Every one of them is built hidden and asked for by name -- from a chip
// in the row above (Nimûne, Jêgirtin) or from the "Hevmane û Bêhtir" menu -- so a sense that needs
// none of them is four boxes tall instead of ten. Jêgirtin is built after the loop as a
// compound widget and inserted after Nimûne, so the visible order matches this list.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false, attested: true }
].concat($.map(wordGroups(), function (g) {
return { cls: g.cls, label: g.label, rows: 1, narrow: true, izah: g.izah };
}));
if (isReconstruction) {
allGroups = $.grep(allGroups, function (g) { return !g.attested; });
}
$.each(allGroups, function (_, g) {
// wiktku-dfn-wordgroup marks the six word lists, which a şablon sense hides -- both the
// group and its entry in the menu (see the .wiktku-sense-is-tpl rules).
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-subgroup-hidden " + g.cls + "-group");
if (g.cls !== "wiktku-dfn-nimune") groupDiv.addClass("wiktku-dfn-wordgroup");
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
// The language the example is to be written in, named in the box itself. Read fresh
// rather than fixed at build time: the entry's language is often chosen later.
function nimunePlaceholder() {
var code = getValues().lang || "";
return wiktkuUtil.examplePrompt(code && getLangLabel(code), false);
}
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", nimunePlaceholder());
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", nimuneTranslationPrompt(lang))
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
// Refreshed with the word groups' own boxes when the entry's language changes.
list.data("updatePlaceholders", function () {
list.find("> li > textarea.wiktku-dfn-nimune-text")
.attr("placeholder", nimunePlaceholder());
});
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = isWordGroupCls(g.cls);
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
if (!isReconstruction) (function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-subgroup-hidden wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
// Refreshed with every other sub-box when the entry's language changes.
jederList.data("updatePlaceholders", function () {
jederList.find("> li > textarea.wiktku-dfn-nimune-quote")
.attr("placeholder", jederPlaceholder());
});
// Same shape as the nimûne box: the language belongs in the prompt, and is read fresh
// because the entry's language is often chosen after the block was built.
function jederPlaceholder() {
var code = getValues().lang || "";
return wiktkuUtil.examplePrompt(code && getLangLabel(code), true);
}
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", jederPlaceholder());
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// The source, in the add-examples form's order and wording: the template, then the link
// with its Citoid button, then the template's own fields.
var jederDefs = steps.dfn.jederDefs || {};
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
// "" is auto-detect: a Citoid fetch picks the template.
srcSel.append($("<option value=''></option>").text(wiktkuUtil.msg.citoidAuto));
srcSel.append($("<option></option>").val("__manual__").text(wiktkuUtil.msg.sourceByHand));
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
srcSel.append($("<option></option>").val("__category__").text(wiktkuUtil.msg.sourceFromCategory));
// Paste a citation built on another page into the quote box and have it saved exactly
// as typed -- the quickest way to reuse one jêgirtin across many entries.
srcSel.append($("<option></option>").val("__raw__").text(wiktkuUtil.msg.sourceRaw));
var srcSelWrap = $("<div></div>").addClass("wiktku-dfn-quote-field")
.append($("<label></label>").text("Şablona çavkaniyê"), srcSel);
// Saved as url= in every mode but Kategorî and raw, which have no box for it.
var urlLbl = $("<label></label>");
var urlInput = $("<input type='text'>").addClass("wiktku-dfn-quote-url");
var citoidBtn = new OO.ui.ButtonWidget({
label: wiktkuUtil.msg.citoidFill,
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
var urlHint = $("<p></p>").addClass("wiktku-step-hint wiktku-dfn-quote-url-hint");
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-field wiktku-dfn-quote-citoid")
.append(urlLbl, urlInput, citoidBtn.$element, citoidStatus, urlHint);
// The add-examples form's labels: plain "attach a link" wording for a hand-written
// source, Citoid wording otherwise.
function setUrlWording(chosen) {
if (chosen === "__manual__") {
urlLbl.text("Adresa webê ya nimûneyê");
urlInput.attr("placeholder", "URLyeke wekî https://…");
urlHint.text("Tenê binivîse eger lînk jixwe di referansa çavkaniyê de tinebe.");
} else {
urlLbl.text("Lînk, DOI an ISBNya çavkaniyê");
urlInput.attr("placeholder", "URLyek wekî https://… an jî DOI/ISBN");
urlHint.text(chosen
? "Ev dibe lînka çavkaniyê (url=). Ji bo şablonên jêder-*, dikare bi Citoid an bi destan jî bên dagirtin."
: "Li qutiyê URL, DOI an ISBN binivîse û pê lê bike butona '" + wiktkuUtil.msg.citoidFill + "' da ku otomatîk çavkanî were dagirtin.");
}
}
setUrlWording("");
// Fills only empty boxes, so a repeat fetch never overwrites what was typed by hand.
function fillEmptyFields(fields) {
$.each(fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
}
// Prefills Dem from the link's own "t=" once jêder-vîdeo is active.
function autofillVideoTimestamp() {
var citoid = window.wikt && window.wikt.citoid;
var dem = srcFields.find("[data-field-key='dem']");
if (srcSel.val() !== "jêder-vîdeo" || !dem.length || $.trim(dem.val()) ||
!(citoid && citoid.extractVideoTimestamp)) {
return;
}
var t = citoid.extractVideoTimestamp($.trim(urlInput.val()));
if (t) dem.val(t);
}
function runCitoidFetch() {
var query = $.trim(urlInput.val());
if (!query) {
mw.notify(wiktkuUtil.msg.citoidNeedsUrl);
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê barkirin…");
// Through wikt.cite rather than straight to the citoid gadget: the wrapper adds what
// Citoid returns but citoid-helper's mapping drops -- the access date, the ISSN and
// the DOI.
wiktkuCite.fetchFromCitoid(query).then(function (result) {
// Citoid picks the template only while none is chosen; a hand-picked one is kept.
var current = srcSel.val();
if (!current && jederDefs[result.jederKey]) {
srcSel.val(result.jederKey).trigger("change");
current = result.jederKey;
}
if (jederDefs[current]) {
// Citoid shaped its fields for the type it detected; re-derived for another.
fillEmptyFields(current === result.jederKey ? result.fields
: wiktkuCite.citoidFieldsFor(current, result.raw, query));
autofillVideoTimestamp();
} else {
// No template for this kind of source: a hand-written source line from what
// Citoid returned, only while that box is still empty.
if (current !== "__manual__") {
srcSel.val("__manual__").trigger("change");
}
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text(wiktkuUtil.msg.citoidFilled);
}, function (errorMsg) {
// Citoid cannot read some sites (x.com): Websîte with a guessed name instead.
var citoid = window.wikt && window.wikt.citoid;
var site = citoid && citoid.websiteNameForUrl && citoid.websiteNameForUrl(query);
if (site && jederDefs["jêder-websîte"]) {
if (srcSel.val() !== "jêder-websîte") {
srcSel.val("jêder-websîte").trigger("change");
}
fillEmptyFields({ "websîte": site });
citoidStatus.text("Citoid nikare vê malperê bixwîne; şablona Websîte hate hilbijartin, ji kerema xwe mayî bi destan tijî bike.");
return;
}
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
urlInput.on("input", autofillVideoTimestamp);
urlInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
// "Malûmatên çavkaniyê" over a jêder-* template's boxes, with the button that empties
// them and the link.
var clearBtn = new OO.ui.ButtonWidget({
label: "Vala bike",
framed: false,
title: "Lînkê û qadên jêder-* yên vê beşê vala bike",
classes: [ "wiktku-dfn-quote-src-clear" ]
});
clearBtn.on("click", function () {
urlInput.val("");
citoidStatus.text("");
srcFields.find(".wiktku-dfn-quote-src-input").val("");
});
var srcHead = $("<div></div>").addClass("wiktku-dfn-quote-src-head")
.append($("<span></span>").addClass("wiktku-dfn-quote-src-head-label").text("Malûmatên çavkaniyê"),
clearBtn.$element)
.hide();
// One labelled box for a citation parameter. Wide/narrow/medium layout: sernav-type
// fields get a full-width multiline box, short ones like sal/rûpel/isbn stay narrow.
// Sizes come from wikt.cite, so a Kategorî:Çavkanî template that happens to use the
// same parameter names as jêder-* is laid out the same way rather than generically.
function buildSrcFieldRow(field) {
var size = wiktkuCite.fieldSize(field.key);
var width = field.width || size.width || "medium";
var multiline = field.multiline !== undefined ? field.multiline : size.multiline;
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + width);
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
// sal and tarîx say the same thing twice and Modul:quote refuses both; filling
// one clears the other rather than letting the citation break on save.
var partner = wiktkuCite.exclusivePartner(field.key);
if (partner) {
inp.on("input", function () {
if (!$.trim($(this).val())) return;
$(this).closest(".wiktku-dfn-quote-src-fields")
.find(".wiktku-dfn-quote-src-input[data-field-key='" + partner + "']")
.val("");
});
}
return row.append(fieldLbl).append(inp);
}
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// As in the add-examples form: no link for Kategorî or raw, no Citoid for a
// hand-written source, and the fields' header only over a jêder-* template.
citoidWrap.toggle(chosen !== "__category__" && chosen !== "__raw__");
citoidBtn.toggle(chosen !== "__manual__");
citoidStatus.text("");
setUrlWording(chosen);
srcHead.toggle(!!jederDefs[chosen]);
if (!chosen) {
return;
}
if (chosen === "__raw__") {
// Nothing to fill in: the quote box above already holds the finished
// template, and it is written out exactly as typed. Only the entry name
// inside its jêgirtin= is emboldened (see wikt.cite's boldQuoteParam).
srcFields.append(
$("<p></p>")
.addClass("wiktku-step-hint wiktku-dfn-quote-raw-hint")
.text("Şablona temam li qutiya jêgirtinê ya jorê binivîse — wek xwe tê qeydkirin.")
);
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Same [[Kategorî:Çavkanî]] search widget DefSideBoxes' own Çavkanî subsection box
// uses -- picking a result switches the row into the hidden manual mode with
// {{Şablon}} pre-filled. The picked şablon becomes the citation template itself:
// its own TemplateData parameters are drawn as fields below the picker, in the
// same markup the jêder-* branch uses, so readJederList() reads both with one loop.
var catPicker = wiktkuCite.buildCategoryTemplateSearch({
placeholder: wiktkuUtil.msg.searchTemplate,
describe: false,
onPick: function (entry) {
li.data("catTemplate", entry.target);
li.data("catLangParam", null);
catPicker.$status.text("Qutiyên şablonê tên barkirin…");
srcFields.find(".wiktku-dfn-quote-src-row").remove();
wiktkuCite.fieldSpecs(entry.target).then(function (spec) {
catPicker.$status.text("");
li.data("catLangParam", spec.langParamKey || null);
$.each(spec.fields, function (_, field) {
srcFields.append(buildSrcFieldRow(field));
});
}, function () {
catPicker.$status.text("Qutiyên şablonê nehatin anîn.");
});
}
});
srcFields.append(catPicker.$element);
return;
}
var def = jederDefs[chosen];
if (!def) return;
$.each(def.fields, function (_, field) {
srcFields.append(buildSrcFieldRow(field));
});
autofillVideoTimestamp();
});
// Exposed so draft restore can rebuild a saved category template's fields.
// A whole citation line pasted into the quote box ("#* {{jêder|…}}") means it was
// copied off another entry; switch to raw mode rather than wrapping it in a second
// {{nimûne}}. Only while no source type has been chosen by hand.
li.find(".wiktku-dfn-nimune-quote").on("input", function () {
if (srcSel.val()) return;
if (wiktkuCite.looksLikeCitationLine($(this).val())) {
srcSel.val("__raw__").trigger("change");
}
});
li.data("buildSrcFieldRow", buildSrcFieldRow);
li.append(srcSelWrap, citoidWrap, srcHead, srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── Which sub-field goes where: chips in the row above for the two everyday ones, the rest
// behind "Hevmane û Bêhtir". Same arrangement as DefSideBoxes' own sense menu, deliberately -- a
// person who has used one gadget should not have to learn the other.
(function () {
var PICK_DEFS = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", chip: true, attested: true },
{ cls: "wiktku-dfn-jeder", label: "Jêgirtin", chip: true, attested: true }
].concat($.map(wordGroups(), function (g) {
return { cls: g.cls, label: g.label, word: true };
}));
// Etîket and Kategorî are chip fields, not sub-groups, so they are revealed by showing
// their toggle rather than a whole box.
var CHIP_FIELD_DEFS = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", field: etiketField },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", field: kategoriField },
{ cls: "wiktku-dfn-maneid", label: "Maneid", field: maneidField }
];
function groupHasContent(groupDiv) {
return groupDiv.find("textarea, input").filter(function () {
return $.trim($(this).val() || "") !== "";
}).length > 0;
}
// Shows a group and takes its chip / menu entry out of the picker -- both carry the same
// data-pick, so one selector covers whichever of the two this group had.
// Which group carries no divider above it: the first one actually on screen, not the
// first in the DOM -- see the .wiktku-dfn-subgroup-first rule.
function updateFirstVisible() {
var $all = block.find(".wiktku-dfn-subgroup").removeClass("wiktku-dfn-subgroup-first");
$all.not(".wiktku-dfn-subgroup-hidden").first().addClass("wiktku-dfn-subgroup-first");
}
function revealGroup(cls, focus) {
var groupDiv = block.find("." + cls + "-group");
if (!groupDiv.length) return;
groupDiv.removeClass("wiktku-dfn-subgroup-hidden");
block.find("[data-pick='" + cls + "']").hide();
updateFirstVisible();
if (focus) groupDiv.find("textarea, input").first().focus();
}
block.data("revealSenseGroup", revealGroup);
// Hides it again and gives the picker its entry back. Anything typed is thrown away, so a
// group holding something asks first.
function hideGroup(cls) {
var groupDiv = block.find("." + cls + "-group");
if (!groupDiv.length) return;
if (groupHasContent(groupDiv) &&
!window.confirm("Tiştên di vê qutiyê de winda dibin. Dewam bike?")) {
return;
}
groupDiv.children("ol").children("li").not(":first").remove();
groupDiv.find("textarea, input").val("");
// The jêgirtin row's source <select> owns the fields under it; putting it back to ""
// is what clears them.
groupDiv.find("select.wiktku-dfn-quote-src-sel").val("").trigger("change");
groupDiv.addClass("wiktku-dfn-subgroup-hidden");
block.find("[data-pick='" + cls + "']").show();
updateFirstVisible();
}
// Drafts, restoreDfn() and the ⊕-grown lists all put content in without going through
// revealGroup(), so anything that ended up filled is shown on the next refresh pass.
function syncPicks() {
$.each(PICK_DEFS, function (_, def) {
var groupDiv = block.find("." + def.cls + "-group");
if (groupDiv.length && groupDiv.hasClass("wiktku-dfn-subgroup-hidden") &&
groupHasContent(groupDiv)) {
revealGroup(def.cls, false);
}
});
$.each(CHIP_FIELD_DEFS, function (_, def) {
if (def.field.values().length) {
def.field.wrap.show();
block.find("[data-pick='" + def.cls + "']").hide();
}
});
}
block.data("syncSensePicks", syncPicks);
// Every group gets an × in its label, so a box opened by mistake can go away again --
// the counterpart of DefSideBoxes' own box-remove link.
$.each(PICK_DEFS, function (_, def) {
var groupDiv = block.find("." + def.cls + "-group");
if (!groupDiv.length) return;
var $x = $("<button type='button' class='wiktku-dfn-subgroup-remove'>×</button>")
.attr("title", def.label + " veşêre");
$x.on("click", function (e) {
e.preventDefault();
hideGroup(def.cls);
});
groupDiv.children(".wiktku-dfn-sublabel").first().append($x);
});
// — the chips, beside Dever/Şîwe —
$.each(PICK_DEFS, function (_, def) {
if (!def.chip || (isReconstruction && def.attested)) return;
var $chip = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>")
.attr("data-pick", def.cls)
.text("+ " + def.label);
$chip.on("click", function (e) {
e.preventDefault();
revealGroup(def.cls, true);
});
chipFieldRow.append($chip);
});
// — "Hevmane û Bêhtir" —
var moreArrow = $("<span class='wiktku-dfn-more-arrow'>▸</span>");
var moreToggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle wiktku-dfn-more-toggle'></button>")
.append(document.createTextNode(wiktkuUtil.msg.moreMenu + " ")).append(moreArrow);
var moreMenu = $("<div class='wiktku-dfn-more-menu'></div>").hide();
function closeMenu() {
moreMenu.hide();
moreArrow.html("▸");
}
moreToggle.on("click", function (e) {
e.preventDefault();
$(".wiktku-dfn-more-menu").not(moreMenu).hide();
moreMenu.toggle();
moreArrow.html(moreMenu.is(":visible") ? "▾" : "▸");
});
function menuEntry(label, cls, extraCls) {
var $link = $("<a href='#' class='wiktku-dfn-more-item'></a>").text(label);
if (cls) $link.attr("data-pick", cls);
if (extraCls) $link.addClass(extraCls);
moreMenu.append($link);
return $link;
}
// Group 1: the two label fields. Their toggle only joins the chip row once asked for.
$.each(CHIP_FIELD_DEFS, function (_, def) {
var $link = menuEntry("+ " + def.label, def.cls);
$link.on("click", function (e) {
e.preventDefault();
closeMenu();
def.field.wrap.show();
def.field.open();
$link.hide();
});
});
// Group 2: the word lists. wiktku-dfn-pick-word so a şablon sense hides these entries
// along with the groups themselves.
var firstWord = true;
$.each(PICK_DEFS, function (_, def) {
if (!def.word) return;
var $link = menuEntry("+ " + def.label, def.cls,
"wiktku-dfn-pick-word" + (firstWord ? " wiktku-dfn-more-group-start" : ""));
firstWord = false;
$link.on("click", function (e) {
e.preventDefault();
closeMenu();
revealGroup(def.cls, true);
});
});
// Group 3: no "Maneê rake" counterpart -- the tab row already carries one bin for the
// open sense, and a second delete in here would be a second way to do the same thing.
var $newSense = menuEntry(wiktkuUtil.msg.addAnotherSense, null, "wiktku-dfn-more-group-start");
$newSense.on("click", function (e) {
e.preventDefault();
closeMenu();
steps.dfn.addSense($("#wiktku-start-dfn"), block);
});
chipFieldRow.append($("<span class='wiktku-dfn-more-wrap'></span>").append(moreToggle, moreMenu));
bindMoreMenuCloser();
syncPicks();
updateFirstVisible();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode(wiktkuUtil.msg.anotherSense));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Adds a sense, opens it, and hides the others. With afterBlock, the new one lands directly
* under that sense rather than at the end — "Maneyeke din zêde bike" in a sense's own
* "Hevmane û Bêhtir" menu is asking for one next to *this* meaning. */
addSense: function (stepDiv, afterBlock) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
if (afterBlock && afterBlock.length) newBlock.insertAfter(afterBlock);
else list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from data-main.json (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
// The mane box and the îzah box are one gloss on the page: "tercime {{îzah|…}}". Joined
// here so everything downstream (getOutput, the linkify pass, the cache) keeps seeing a
// single string, exactly as it did when the pair was two boxes.
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
var izah = $.trim(block.find("textarea.wiktku-dfn-izah").val());
if (izah) {
mane = $.trim(mane + " {{îzah|" + izah + "}}");
}
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
var url = tplName === "__raw__" || tplName === "__category__" ? ""
: $.trim(li.find(".wiktku-dfn-quote-url").val());
if (url) obj.url = url;
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__raw__") {
// The quote box already holds a finished citation template; it is written
// out untouched, so there is nothing else on this row to read.
obj.raw = true;
} else if (tplName === "__category__") {
// A picked Kategorî:Çavkanî template IS the citation template, with its own
// TemplateData parameters -- rendered into the same markup the jêder-* branch
// below uses, so one loop reads both.
var catName = li.data("catTemplate");
if (catName) {
obj.jederCatTpl = catName;
var catFields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var ck = $(this).attr("data-field-key");
var cv = $.trim($(this).val());
if (ck && cv) catFields[ck] = cv;
});
// A restore in progress has no boxes yet -- its TemplateData is still on
// the way -- so the values it is restoring stand in until they exist.
// See restoreDfn, which parks them here and clears them once they land.
if (!Object.keys(catFields).length) {
catFields = li.data("catFields") || {};
}
obj.jederCatFields = catFields;
var catLang = li.data("catLangParam");
if (catLang) obj.jederCatLangParam = catLang;
}
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = wiktkuCite.withLinkAsUrl(tplName, fields, url);
}
items.push(obj);
});
return items;
}
// Dever/Şîwe, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
var sense = {
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
maneid: $.trim(block.find("input.wiktku-dfn-maneid-input").val() || ""),
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
dever: readChipList("wiktku-dfn-dever")
};
// alternativ, hevmane, dijmane, koordine, jornav, jernav — one property each, named by
// senseGroups' "field" so getOutput() and restoreDfn() find them under the same names.
$.each(wordGroups(), function (_, g) {
sense[g.field] = readList(g.cls);
});
senses.push(sense);
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplManeidTpl = buildManeidTpl(sense, lang);
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplManeidTpl ? tplManeidTpl + " " : "") + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
//
// Every language, not just ku: a sense kept for its jêgirtin or word lists
// alone still needs the marker, or the line goes out as a bare "# ".
dfn = "{{mane?|" + lang + "}}";
}
var maneidTpl = buildManeidTpl(sense, lang);
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (maneidTpl ? maneidTpl + " " : "") + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
// Template name and sense property both come from senseGroups, so this and
// DefSideBoxes write the same "#:" lines.
$.each(ctx.SENSE_GROUPS, function (_, g) {
out += wordLine(g.key, sense[g.field] || []);
});
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
var linkifyText = wiktkuUtil.linkifyText;
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// Nothing written yet: the sense is still marked as needing one, but no example
// placeholder goes with it.
text = "# {{mane?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before. On failure the text
// comes back unchanged, so the save carries on with what the user wrote -- only the
// automatic linking is lost, and saying so is better than losing it silently.
linkifyText(text, function (mainLinked, error) {
if (error) {
mw.notify(error, { type: "warn" });
}
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + ctx.pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!ctx.pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
// The rows are wikt.edit's, shared with DefSideBoxes, which offers the same three boxes.
field: null,
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
steps.resim.field = wiktkuUtil.buildImageListField({
prefix: "wiktku-",
rowClass: "wiktku-posparams-row",
inputClass: "wiktku-start-step-field wiktku-start-narrow",
pageName: ctx.pageName
});
stepDiv.append($("<div class='wiktku-resim-wrap'></div>").append(steps.resim.field.$element));
return null;
},
/** {file, caption, alt} per filled row, with the caption wrapped for a non-Kurdish entry. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
if (!steps.resim.field) return [];
return $.map(steps.resim.field.values(), function (image) {
return {
file: image.file,
caption: wiktkuWikitext.captionForLang(image.caption, lang),
alt: image.alt,
size: image.size,
extra: image.extra
};
});
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return $.grep($.map(images, wiktkuWikitext.buildImageLine), Boolean).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var call = wiktkuWikitext.buildHbnrCall(values.bnrh);
return call ? call + "\n" : "";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data-main.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data-main.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data-main.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", wiktkuUtil.msg.addTemplate);
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
$(this).val("");
sel.trigger("wiktku:refresh");
// Same param dialog DefSideBoxes' own Etîmolojî editor opens on a fresh insert: filled
// from the template's own TemplateData, with CODE/SOURCELANG already substituted above
// as the fields' starting values, so the user only has to replace the "?" placeholders
// rather than hunt for them in raw wikitext afterward.
wiktkuCite.openTemplateDialog(snippet).then(function (edited) {
if (!edited) return; // cancelled, or "Jê bibe" on a call that was never inserted
insertAtCursor(ta[0], edited);
ta.focus();
});
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// {{ji?}} (a redirect to {{bêetîmolojî}}) is only ever added automatically to Kurdish
// entries.
if (values.lang !== "ku") return "";
// Multi-word page names skip the {{ji?|lang}} placeholder entirely -- "source unknown?" doesn't apply to a phrase.
if (ctx.pageName.includes(" ")) return "";
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.grep($.map(rows, function (row) {
return wiktkuWikitext.buildDundeLine(row[0], row[1]);
}), Boolean);
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ ctx.pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
refresh();
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
refresh();
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(ctx.catSrcTemplates || [], function (_, entry) {
sel.append($("<option></option>").val(entry.target).text(entry.label));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", wiktkuUtil.msg.pickOrTypeTemplate);
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", wiktkuUtil.msg.addTemplate);
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
// Same param dialog DefSideBoxes' own Çavkanî picker opens: the chosen template's
// TemplateData becomes a form, so "rûpel=", "sernav=" and the rest are filled in before
// anything lands in the box, instead of a bare {{Ç:…}} to go back and complete by hand.
sel.on("change", function () {
var name = $(this).val();
$(this).val("");
sel.trigger("wiktku:refresh");
if (!name) return;
wiktkuCite.openTemplateDialog("{{" + name + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or removed a call that was never inserted
}
insertAtCursor(ta[0], "* " + edited + "\n");
ta.trigger("focus");
});
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (ctx.catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text(wiktkuUtil.msg.templatesLoading);
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text(wiktkuUtil.msg.templatesFailed);
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
return steps;
};
// </nowiki>
7mw7ltrvrowaokg63xmttg15904zcoi
6865653
6865627
2026-10-24T10:03:00Z
Wikihez
7344
Comment: scripts and genders come from wikt.lang-metadata
6865653
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.start-wizard-steps.js]]
*
* The step definitions of [[MediaWiki:Gadget-wikt.start-wizard.js]]: what each step asks, offers and writes.
* A module of its own so that neither half passes the 100 KB the browser's module store keeps. The wizard
* calls it with ctx, its own helpers and settings; the few settings it changes after loading
* (DEVER_QUICK_PICKS, SENSE_GROUPS, catSrcTemplates, pageName, wiktkuLabelSets) are read through ctx as
* they are at that moment.
*/
/* global wikt */
module.exports = function (ctx) {
var GENDER_BASE_LABELS = ctx.GENDER_BASE_LABELS;
var ZARAVA_LANG_CODES = ctx.ZARAVA_LANG_CODES;
var addLangMode = ctx.addLangMode;
var applyTplDescriptions = ctx.applyTplDescriptions;
var bindMoreMenuCloser = ctx.bindMoreMenuCloser;
var buildChipField = ctx.buildChipField;
var buildCite = ctx.buildCite;
var buildDeverTpl = ctx.buildDeverTpl;
var buildEtiketTpl = ctx.buildEtiketTpl;
var buildManeidTpl = ctx.buildManeidTpl;
var buildSenseTpl = ctx.buildSenseTpl;
var capitalize = ctx.capitalize;
var decapitalize = ctx.decapitalize;
var fetchCatSrcTemplates = ctx.fetchCatSrcTemplates;
var findLangHeader = ctx.findLangHeader;
var getLangLabel = ctx.getLangLabel;
var getValues = ctx.getValues;
var highlightLinebreak = ctx.highlightLinebreak;
var initSelects = ctx.initSelects;
var insertAtCursor = ctx.insertAtCursor;
var insertLineSnippet = ctx.insertLineSnippet;
var isReconstruction = ctx.isReconstruction;
var isWordGroupCls = ctx.isWordGroupCls;
var langHasMultipleScripts = ctx.langHasMultipleScripts;
var langsByCode = ctx.langsByCode;
var makeModifierLi = ctx.makeModifierLi;
var makeTrashButton = ctx.makeTrashButton;
var makeWordListStep = ctx.makeWordListStep;
var manePlaceholder = ctx.manePlaceholder;
var markTemplateOptions = ctx.markTemplateOptions;
var needsTransliteration = ctx.needsTransliteration;
var nimuneTranslationPrompt = ctx.nimuneTranslationPrompt;
var populateMenus = ctx.populateMenus;
var refresh = ctx.refresh;
var senseHasExtraContent = ctx.senseHasExtraContent;
var splitTercimeIzah = ctx.splitTercimeIzah;
var stripWikilinks = ctx.stripWikilinks;
var tplNameFromSnippet = ctx.tplNameFromSnippet;
var wiktkuCite = ctx.wiktkuCite;
var wiktkuEdit = ctx.wiktkuEdit;
var wiktkuEnsureLabels = ctx.wiktkuEnsureLabels;
var wiktkuLabelListeners = ctx.wiktkuLabelListeners;
var wiktkuLabelSuggestions = ctx.wiktkuLabelSuggestions;
var wiktkuPosparams = ctx.wiktkuPosparams;
var wiktkuUtil = ctx.wiktkuUtil;
var wiktkuWikitext = ctx.wiktkuWikitext;
var wordGroups = ctx.wordGroups;
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + ctx.pageName + '" bi çi zimanî ye?',
// Populated from data-main.json (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + ctx.pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data-main.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qism ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vala bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", wiktkuUtil.msg.addTemplate);
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
// The palette lives in wikt.edit, shared with DefSideBoxes' own Bilêvkirin box.
wrap.append(wiktkuUtil.buildIpaPalette({
insertText: function (text, back) {
insertAtCursor(ta[0], text);
if (back) {
var pos = ta[0].selectionStart - back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
}
ta.focus();
}
}));
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text(wiktkuUtil.msg.loading);
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'></button>")
.text("✕ " + wiktkuUtil.msg.cancel);
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not expandtemplates: the preview needs rendered HTML, not expanded
// wikitext. The leading "* " is dropped so it renders as a line, not a bulleted list.
new mw.Api().post({
action: "parse",
title: ctx.pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate çêkirin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + ctx.pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from posChoices (data-sections.json) and posExamples (data-main.json).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen,
// shown as plain text right under the dropdown (see the
// .wiktku-start-pos-example build/refresh() above). Kept clear of
// the Mane textarea's placeholder, which answers "what to type
// here" rather than "what this grammatical category means".
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-start-posparams [data-pp-opt='form']").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs, parsed straight out of tplLine (not the posparams input fields, which a cache/section restore doesn't repopulate) once niha/borî/transitivity are all present.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var niha = wiktkuWikitext.callParamValue(tplLine, "niha");
var borî = wiktkuWikitext.callParamValue(tplLine, "borî");
var lekturCode = wiktkuWikitext.callParamValue(tplLine, "c");
var cParts = lekturCode ? /^(?:([xphb])-)?(n?gh)$/.exec(lekturCode) : null;
var lekturForm = cParts ? (cParts[1] || "") : "";
var lktransitivity = cParts ? cParts[2] : "";
var lkForm = lktransitivity === "gh" ? "gerguhêz" : (lktransitivity === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + ctx.pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + ctx.pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data-main.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + ctx.pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
// The card itself lives in wikt.edit/posparams.js, shared with DefSideBoxes -- see there for the
// fields and the per-language gating. This is only the wizard's side: which language and part of
// speech it is for, and mirroring gender/number into the two hidden steps the wizard keeps.
posparams: {
id: "posparams",
// Fused onto the "pos" card in styles.css so the two read as one question. The "xisûsiyet"
// name is a no-CSS fallback: its h3 is hidden, and every field below is self-labelled.
name: "xisûsiyet",
question_fmt: "",
card: null,
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-step").length) return null;
var values = getValues();
steps.posparams.card = wiktkuPosparams.build({
lang: values.lang,
pos: values.pos,
onChange: refresh,
// steps.cinsiyet/steps.jimar own these hidden fields; the card reports rather than
// writes, so it needs no knowledge of them.
onSync: function (cinsiyet, jimar) {
$("#wiktku-start-cinsiyet-field").val(cinsiyet || "");
$("#wiktku-start-jimar-field").val(jimar || "");
}
});
stepDiv.append(steps.posparams.card.$element);
return null;
},
rebuildTa: function () {
if (steps.posparams.card) steps.posparams.card.rebuild();
},
/** Called from refresh() — re-gates every row for the current language/pos. */
syncHelpers: function () {
if (!steps.posparams.card) return;
var values = getValues();
steps.posparams.card.setContext(values.lang, values.pos);
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Nothing on keystrokes: only the Werger gloss list needs a live Mane edit, and rebuilding
// it re-reads every sense. Deferred to blur; the wikitext is read fresh on output anyway.
dfnArea.on("blur", function () { refresh(); });
// — Îzaha maneyî (optional second half of the mane) —
// A non-Kurdish entry's gloss is a translation followed by {{îzah|…}} explaining it. The
// mane box is always the mane box and the explanation opens under it when asked for, so
// the pair reads as one gloss rather than as a different kind of field. getSenses() joins
// the two back together, so every consumer downstream sees one gloss string.
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 2)
.attr("placeholder", "Îzaha maneyî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var izahWrap = $("<div class='wiktku-dfn-izah-wrap'></div>")
.append($("<span class='wiktku-dfn-sublabel'></span>")
.append($("<span class='wiktku-dfn-sublabel-term'></span>").text("Îzaha maneyî")).append(": "))
.append(izahArea)
.hide();
// Opening it from a restore, where the text decides rather than a click.
var izahOpen = false;
function setIzahOpen(open) {
izahOpen = !!open;
updateManeMode();
}
block.data("setIzahOpen", setIzahOpen);
izahArea.on("input", function () {
// dfnArea drives the live preview and the auto-linker; the îzah is part of the gloss,
// so a change to it has to reach them too.
dfnArea.trigger("input");
});
izahArea.on("blur", function () { refresh(); });
// The mane box gives way to the template title box when a template is chosen; the îzah
// goes with it, since a template gloss has no separate explanation.
function updateManeMode() {
var isTpl = !!tplSelect.val();
if (isTpl) {
dfnArea.hide();
izahWrap.hide();
return;
}
dfnArea.show();
dfnArea.attr("placeholder", manePlaceholder(getValues().lang || ""));
// {{îzah}} explains a translation, so it belongs to entries in another language; a
// Kurdish sense is its own definition and is not offered one. A box that already holds
// text stays on screen whatever the language -- getSenses() joins it into the gloss
// either way, and hiding it would make that happen unseen.
var lang = getValues().lang || "";
var offer = !!lang && lang !== "ku";
// A non-Kurdish gloss is a translation and its explanation, so the box opens with the
// mane box rather than waiting to be asked for.
var open = offer || izahOpen || $.trim(izahArea.val()) !== "";
izahWrap.toggle(open);
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from data-main.json.
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", wiktkuUtil.msg.searchTemplate);
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data-main.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so the word-list groups (hevmane, dijmane, formên
// alternatîv…) make no sense for them — the class hides both those groups and their
// entries in the "Hevmane û Bêhtir" menu, so they cannot be asked for either. Etîket, Kategorî,
// Nimûne and Jêgirtin stay available.
function updateFieldVisibility() {
block.toggleClass("wiktku-sense-is-tpl", !!tplSelect.val());
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
izahWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şaşnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// Dever/Şîwe, Etîket and Kategorî as chip+toggle fields, so a sense that leaves them empty
// stays short. getOutput() folds Dever/Şîwe into {{dever|}} for ku, {{e|}} otherwise.
// Each closes the other two on open; onOpen runs from a click, so forward references hold.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîwe",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
// Kurmancî's pills are the handful in data-main.json; another language gets the regions
// Modul:labels tags for it. The rest are found by typing.
quickPicks: function () {
var lang = getValues().lang || "ku";
if (lang === "ku") return ctx.DEVER_QUICK_PICKS;
return ctx.wiktkuLabelSets ? wiktkuEdit.labels.taggedRegionsFor(ctx.wiktkuLabelSets, lang) : [];
},
suggestions: function () { return wiktkuLabelSuggestions("dever"); },
onOpen: function () { etiketField.close(); kategoriField.close(); wiktkuEnsureLabels(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
suggestions: function () { return wiktkuLabelSuggestions("etiket"); },
onOpen: function () { deverField.close(); kategoriField.close(); wiktkuEnsureLabels(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
suggestions: function () { return wiktkuLabelSuggestions("kategori"); },
onOpen: function () { deverField.close(); etiketField.close(); wiktkuEnsureLabels(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Maneid -- the anchor another entry links a sense by, written "{{maneid|lang|x}}" at the
// head of the line. One value rather than a list, so it is a plain box and not a chip
// field; it still behaves like one to the "Hevmane û Bêhtir" menu through the small api below.
var maneidInput = $("<input type='text'>")
.addClass("wiktku-dfn-maneid-input wiktku-start-narrow")
.attr("placeholder", "id");
var maneidWrap = $("<div class='wiktku-dfn-chipfield-wrap wiktku-dfn-maneid-wrap'></div>")
.append($("<label class='wiktku-dfn-maneid-label'></label>")
.text("Maneid ")
.append(maneidInput))
.hide();
var maneidField = {
wrap: maneidWrap,
open: function () { maneidWrap.show(); maneidInput.focus(); },
values: function () {
var v = $.trim(maneidInput.val() || "");
return v ? [v] : [];
}
};
// Dever/Şîwe starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
//
// Shown only for a language with regions: every Kurdish variety, and any other language
// Modul:labels tags one for (read once the labels arrive). One that already has a value stays.
function updateDeverOpenState() {
var lang = getValues().lang || "";
var hasRegions = !lang || wiktkuEdit.labels.isKurdish(lang) ||
(ctx.wiktkuLabelSets && wiktkuEdit.labels.regionalFor(ctx.wiktkuLabelSets, lang).length > 0);
if (!hasRegions && !ctx.wiktkuLabelSets) wiktkuEnsureLabels();
var show = hasRegions || deverField.values().length > 0;
deverField.wrap.toggle(show);
if (!show) deverField.panel.hide();
deverField.setOpenDefault(lang === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
wiktkuLabelListeners.push(updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
// The widget is still built on a reconstruction page so every reference to it stays valid;
// it just never reaches the DOM, so it shows nothing and contributes nothing to getOutput().
//
// The row is the same shape DefSideBoxes puts under a sense -- a few chips for the common
// things, everything else behind "Hevmane û Bêhtir" -- so the two gadgets are learned once. Etîket
// and Kategorî start in the menu and their toggle only joins the row once picked.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>");
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>");
if (!isReconstruction) {
chipFieldRow.append(deverField.wrap);
chipFieldPanels.append(deverField.panel);
}
etiketField.wrap.hide();
kategoriField.wrap.hide();
chipFieldRow.append(etiketField.wrap).append(kategoriField.wrap).append(maneidWrap);
chipFieldPanels.append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(izahWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// Sub-field groups. Every one of them is built hidden and asked for by name -- from a chip
// in the row above (Nimûne, Jêgirtin) or from the "Hevmane û Bêhtir" menu -- so a sense that needs
// none of them is four boxes tall instead of ten. Jêgirtin is built after the loop as a
// compound widget and inserted after Nimûne, so the visible order matches this list.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false, attested: true }
].concat($.map(wordGroups(), function (g) {
return { cls: g.cls, label: g.label, rows: 1, narrow: true, izah: g.izah };
}));
if (isReconstruction) {
allGroups = $.grep(allGroups, function (g) { return !g.attested; });
}
$.each(allGroups, function (_, g) {
// wiktku-dfn-wordgroup marks the six word lists, which a şablon sense hides -- both the
// group and its entry in the menu (see the .wiktku-sense-is-tpl rules).
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-subgroup-hidden " + g.cls + "-group");
if (g.cls !== "wiktku-dfn-nimune") groupDiv.addClass("wiktku-dfn-wordgroup");
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
// The language the example is to be written in, named in the box itself. Read fresh
// rather than fixed at build time: the entry's language is often chosen later.
function nimunePlaceholder() {
var code = getValues().lang || "";
return wiktkuUtil.examplePrompt(code && getLangLabel(code), false);
}
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", nimunePlaceholder());
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", nimuneTranslationPrompt(lang))
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
// Refreshed with the word groups' own boxes when the entry's language changes.
list.data("updatePlaceholders", function () {
list.find("> li > textarea.wiktku-dfn-nimune-text")
.attr("placeholder", nimunePlaceholder());
});
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
block.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = isWordGroupCls(g.cls);
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
block.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
if (!isReconstruction) (function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-subgroup-hidden wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
// Refreshed with every other sub-box when the entry's language changes.
jederList.data("updatePlaceholders", function () {
jederList.find("> li > textarea.wiktku-dfn-nimune-quote")
.attr("placeholder", jederPlaceholder());
});
// Same shape as the nimûne box: the language belongs in the prompt, and is read fresh
// because the entry's language is often chosen after the block was built.
function jederPlaceholder() {
var code = getValues().lang || "";
return wiktkuUtil.examplePrompt(code && getLangLabel(code), true);
}
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", jederPlaceholder());
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// The source, in the add-examples form's order and wording: the template, then the link
// with its Citoid button, then the template's own fields.
var jederDefs = steps.dfn.jederDefs || {};
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
// "" is auto-detect: a Citoid fetch picks the template.
srcSel.append($("<option value=''></option>").text(wiktkuUtil.msg.citoidAuto));
srcSel.append($("<option></option>").val("__manual__").text(wiktkuUtil.msg.sourceByHand));
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
srcSel.append($("<option></option>").val("__category__").text(wiktkuUtil.msg.sourceFromCategory));
// Paste a citation built on another page into the quote box and have it saved exactly
// as typed -- the quickest way to reuse one jêgirtin across many entries.
srcSel.append($("<option></option>").val("__raw__").text(wiktkuUtil.msg.sourceRaw));
var srcSelWrap = $("<div></div>").addClass("wiktku-dfn-quote-field")
.append($("<label></label>").text("Şablona çavkaniyê"), srcSel);
// Saved as url= in every mode but Kategorî and raw, which have no box for it.
var urlLbl = $("<label></label>");
var urlInput = $("<input type='text'>").addClass("wiktku-dfn-quote-url");
var citoidBtn = new OO.ui.ButtonWidget({
label: wiktkuUtil.msg.citoidFill,
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
var urlHint = $("<p></p>").addClass("wiktku-step-hint wiktku-dfn-quote-url-hint");
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-field wiktku-dfn-quote-citoid")
.append(urlLbl, urlInput, citoidBtn.$element, citoidStatus, urlHint);
// The add-examples form's labels: plain "attach a link" wording for a hand-written
// source, Citoid wording otherwise.
function setUrlWording(chosen) {
if (chosen === "__manual__") {
urlLbl.text("Adresa webê ya nimûneyê");
urlInput.attr("placeholder", "URLyeke wekî https://…");
urlHint.text("Tenê binivîse eger lînk jixwe di referansa çavkaniyê de tinebe.");
} else {
urlLbl.text("Lînk, DOI an ISBNya çavkaniyê");
urlInput.attr("placeholder", "URLyek wekî https://… an jî DOI/ISBN");
urlHint.text(chosen
? "Ev dibe lînka çavkaniyê (url=). Ji bo şablonên jêder-*, dikare bi Citoid an bi destan jî bên dagirtin."
: "Li qutiyê URL, DOI an ISBN binivîse û pê lê bike butona '" + wiktkuUtil.msg.citoidFill + "' da ku otomatîk çavkanî were dagirtin.");
}
}
setUrlWording("");
// Fills only empty boxes, so a repeat fetch never overwrites what was typed by hand.
function fillEmptyFields(fields) {
$.each(fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
}
// Prefills Dem from the link's own "t=" once jêder-vîdeo is active.
function autofillVideoTimestamp() {
var citoid = window.wikt && window.wikt.citoid;
var dem = srcFields.find("[data-field-key='dem']");
if (srcSel.val() !== "jêder-vîdeo" || !dem.length || $.trim(dem.val()) ||
!(citoid && citoid.extractVideoTimestamp)) {
return;
}
var t = citoid.extractVideoTimestamp($.trim(urlInput.val()));
if (t) dem.val(t);
}
function runCitoidFetch() {
var query = $.trim(urlInput.val());
if (!query) {
mw.notify(wiktkuUtil.msg.citoidNeedsUrl);
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê barkirin…");
// Through wikt.cite rather than straight to the citoid gadget: the wrapper adds what
// Citoid returns but citoid-helper's mapping drops -- the access date, the ISSN and
// the DOI.
wiktkuCite.fetchFromCitoid(query).then(function (result) {
// Citoid picks the template only while none is chosen; a hand-picked one is kept.
var current = srcSel.val();
if (!current && jederDefs[result.jederKey]) {
srcSel.val(result.jederKey).trigger("change");
current = result.jederKey;
}
if (jederDefs[current]) {
// Citoid shaped its fields for the type it detected; re-derived for another.
fillEmptyFields(current === result.jederKey ? result.fields
: wiktkuCite.citoidFieldsFor(current, result.raw, query));
autofillVideoTimestamp();
} else {
// No template for this kind of source: a hand-written source line from what
// Citoid returned, only while that box is still empty.
if (current !== "__manual__") {
srcSel.val("__manual__").trigger("change");
}
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text(wiktkuUtil.msg.citoidFilled);
}, function (errorMsg) {
// Citoid cannot read some sites (x.com): Websîte with a guessed name instead.
var citoid = window.wikt && window.wikt.citoid;
var site = citoid && citoid.websiteNameForUrl && citoid.websiteNameForUrl(query);
if (site && jederDefs["jêder-websîte"]) {
if (srcSel.val() !== "jêder-websîte") {
srcSel.val("jêder-websîte").trigger("change");
}
fillEmptyFields({ "websîte": site });
citoidStatus.text("Citoid nikare vê malperê bixwîne; şablona Websîte hate hilbijartin, ji kerema xwe mayî bi destan tijî bike.");
return;
}
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
urlInput.on("input", autofillVideoTimestamp);
urlInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
// "Malûmatên çavkaniyê" over a jêder-* template's boxes, with the button that empties
// them and the link.
var clearBtn = new OO.ui.ButtonWidget({
label: "Vala bike",
framed: false,
title: "Lînkê û qadên jêder-* yên vê beşê vala bike",
classes: [ "wiktku-dfn-quote-src-clear" ]
});
clearBtn.on("click", function () {
urlInput.val("");
citoidStatus.text("");
srcFields.find(".wiktku-dfn-quote-src-input").val("");
});
var srcHead = $("<div></div>").addClass("wiktku-dfn-quote-src-head")
.append($("<span></span>").addClass("wiktku-dfn-quote-src-head-label").text("Malûmatên çavkaniyê"),
clearBtn.$element)
.hide();
// One labelled box for a citation parameter. Wide/narrow/medium layout: sernav-type
// fields get a full-width multiline box, short ones like sal/rûpel/isbn stay narrow.
// Sizes come from wikt.cite, so a Kategorî:Çavkanî template that happens to use the
// same parameter names as jêder-* is laid out the same way rather than generically.
function buildSrcFieldRow(field) {
var size = wiktkuCite.fieldSize(field.key);
var width = field.width || size.width || "medium";
var multiline = field.multiline !== undefined ? field.multiline : size.multiline;
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + width);
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
// sal and tarîx say the same thing twice and Modul:quote refuses both; filling
// one clears the other rather than letting the citation break on save.
var partner = wiktkuCite.exclusivePartner(field.key);
if (partner) {
inp.on("input", function () {
if (!$.trim($(this).val())) return;
$(this).closest(".wiktku-dfn-quote-src-fields")
.find(".wiktku-dfn-quote-src-input[data-field-key='" + partner + "']")
.val("");
});
}
return row.append(fieldLbl).append(inp);
}
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// As in the add-examples form: no link for Kategorî or raw, no Citoid for a
// hand-written source, and the fields' header only over a jêder-* template.
citoidWrap.toggle(chosen !== "__category__" && chosen !== "__raw__");
citoidBtn.toggle(chosen !== "__manual__");
citoidStatus.text("");
setUrlWording(chosen);
srcHead.toggle(!!jederDefs[chosen]);
if (!chosen) {
return;
}
if (chosen === "__raw__") {
// Nothing to fill in: the quote box above already holds the finished
// template, and it is written out exactly as typed. Only the entry name
// inside its jêgirtin= is emboldened (see wikt.cite's boldQuoteParam).
srcFields.append(
$("<p></p>")
.addClass("wiktku-step-hint wiktku-dfn-quote-raw-hint")
.text("Şablona temam li qutiya jêgirtinê ya jorê binivîse — wek xwe tê qeydkirin.")
);
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Same [[Kategorî:Çavkanî]] search widget DefSideBoxes' own Çavkanî subsection box
// uses -- picking a result switches the row into the hidden manual mode with
// {{Şablon}} pre-filled. The picked şablon becomes the citation template itself:
// its own TemplateData parameters are drawn as fields below the picker, in the
// same markup the jêder-* branch uses, so readJederList() reads both with one loop.
var catPicker = wiktkuCite.buildCategoryTemplateSearch({
placeholder: wiktkuUtil.msg.searchTemplate,
describe: false,
onPick: function (entry) {
li.data("catTemplate", entry.target);
li.data("catLangParam", null);
catPicker.$status.text("Qutiyên şablonê tên barkirin…");
srcFields.find(".wiktku-dfn-quote-src-row").remove();
wiktkuCite.fieldSpecs(entry.target).then(function (spec) {
catPicker.$status.text("");
li.data("catLangParam", spec.langParamKey || null);
$.each(spec.fields, function (_, field) {
srcFields.append(buildSrcFieldRow(field));
});
}, function () {
catPicker.$status.text("Qutiyên şablonê nehatin anîn.");
});
}
});
srcFields.append(catPicker.$element);
return;
}
var def = jederDefs[chosen];
if (!def) return;
$.each(def.fields, function (_, field) {
srcFields.append(buildSrcFieldRow(field));
});
autofillVideoTimestamp();
});
// Exposed so draft restore can rebuild a saved category template's fields.
// A whole citation line pasted into the quote box ("#* {{jêder|…}}") means it was
// copied off another entry; switch to raw mode rather than wrapping it in a second
// {{nimûne}}. Only while no source type has been chosen by hand.
li.find(".wiktku-dfn-nimune-quote").on("input", function () {
if (srcSel.val()) return;
if (wiktkuCite.looksLikeCitationLine($(this).val())) {
srcSel.val("__raw__").trigger("change");
}
});
li.data("buildSrcFieldRow", buildSrcFieldRow);
li.append(srcSelWrap, citoidWrap, srcHead, srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── Which sub-field goes where: chips in the row above for the two everyday ones, the rest
// behind "Hevmane û Bêhtir". Same arrangement as DefSideBoxes' own sense menu, deliberately -- a
// person who has used one gadget should not have to learn the other.
(function () {
var PICK_DEFS = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", chip: true, attested: true },
{ cls: "wiktku-dfn-jeder", label: "Jêgirtin", chip: true, attested: true }
].concat($.map(wordGroups(), function (g) {
return { cls: g.cls, label: g.label, word: true };
}));
// Etîket and Kategorî are chip fields, not sub-groups, so they are revealed by showing
// their toggle rather than a whole box.
var CHIP_FIELD_DEFS = [
{ cls: "wiktku-dfn-etiket", label: "Etîket", field: etiketField },
{ cls: "wiktku-dfn-kategori", label: "Kategorî", field: kategoriField },
{ cls: "wiktku-dfn-maneid", label: "Maneid", field: maneidField }
];
function groupHasContent(groupDiv) {
return groupDiv.find("textarea, input").filter(function () {
return $.trim($(this).val() || "") !== "";
}).length > 0;
}
// Shows a group and takes its chip / menu entry out of the picker -- both carry the same
// data-pick, so one selector covers whichever of the two this group had.
// Which group carries no divider above it: the first one actually on screen, not the
// first in the DOM -- see the .wiktku-dfn-subgroup-first rule.
function updateFirstVisible() {
var $all = block.find(".wiktku-dfn-subgroup").removeClass("wiktku-dfn-subgroup-first");
$all.not(".wiktku-dfn-subgroup-hidden").first().addClass("wiktku-dfn-subgroup-first");
}
function revealGroup(cls, focus) {
var groupDiv = block.find("." + cls + "-group");
if (!groupDiv.length) return;
groupDiv.removeClass("wiktku-dfn-subgroup-hidden");
block.find("[data-pick='" + cls + "']").hide();
updateFirstVisible();
if (focus) groupDiv.find("textarea, input").first().focus();
}
block.data("revealSenseGroup", revealGroup);
// Hides it again and gives the picker its entry back. Anything typed is thrown away, so a
// group holding something asks first.
function hideGroup(cls) {
var groupDiv = block.find("." + cls + "-group");
if (!groupDiv.length) return;
if (groupHasContent(groupDiv) &&
!window.confirm("Tiştên di vê qutiyê de winda dibin. Dewam bike?")) {
return;
}
groupDiv.children("ol").children("li").not(":first").remove();
groupDiv.find("textarea, input").val("");
// The jêgirtin row's source <select> owns the fields under it; putting it back to ""
// is what clears them.
groupDiv.find("select.wiktku-dfn-quote-src-sel").val("").trigger("change");
groupDiv.addClass("wiktku-dfn-subgroup-hidden");
block.find("[data-pick='" + cls + "']").show();
updateFirstVisible();
}
// Drafts, restoreDfn() and the ⊕-grown lists all put content in without going through
// revealGroup(), so anything that ended up filled is shown on the next refresh pass.
function syncPicks() {
$.each(PICK_DEFS, function (_, def) {
var groupDiv = block.find("." + def.cls + "-group");
if (groupDiv.length && groupDiv.hasClass("wiktku-dfn-subgroup-hidden") &&
groupHasContent(groupDiv)) {
revealGroup(def.cls, false);
}
});
$.each(CHIP_FIELD_DEFS, function (_, def) {
if (def.field.values().length) {
def.field.wrap.show();
block.find("[data-pick='" + def.cls + "']").hide();
}
});
}
block.data("syncSensePicks", syncPicks);
// Every group gets an × in its label, so a box opened by mistake can go away again --
// the counterpart of DefSideBoxes' own box-remove link.
$.each(PICK_DEFS, function (_, def) {
var groupDiv = block.find("." + def.cls + "-group");
if (!groupDiv.length) return;
var $x = $("<button type='button' class='wiktku-dfn-subgroup-remove'>×</button>")
.attr("title", def.label + " veşêre");
$x.on("click", function (e) {
e.preventDefault();
hideGroup(def.cls);
});
groupDiv.children(".wiktku-dfn-sublabel").first().append($x);
});
// — the chips, beside Dever/Şîwe —
$.each(PICK_DEFS, function (_, def) {
if (!def.chip || (isReconstruction && def.attested)) return;
var $chip = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>")
.attr("data-pick", def.cls)
.text("+ " + def.label);
$chip.on("click", function (e) {
e.preventDefault();
revealGroup(def.cls, true);
});
chipFieldRow.append($chip);
});
// — "Hevmane û Bêhtir" —
var moreArrow = $("<span class='wiktku-dfn-more-arrow'>▸</span>");
var moreToggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle wiktku-dfn-more-toggle'></button>")
.append(document.createTextNode(wiktkuUtil.msg.moreMenu + " ")).append(moreArrow);
var moreMenu = $("<div class='wiktku-dfn-more-menu'></div>").hide();
function closeMenu() {
moreMenu.hide();
moreArrow.html("▸");
}
moreToggle.on("click", function (e) {
e.preventDefault();
$(".wiktku-dfn-more-menu").not(moreMenu).hide();
moreMenu.toggle();
moreArrow.html(moreMenu.is(":visible") ? "▾" : "▸");
});
function menuEntry(label, cls, extraCls) {
var $link = $("<a href='#' class='wiktku-dfn-more-item'></a>").text(label);
if (cls) $link.attr("data-pick", cls);
if (extraCls) $link.addClass(extraCls);
moreMenu.append($link);
return $link;
}
// Group 1: the two label fields. Their toggle only joins the chip row once asked for.
$.each(CHIP_FIELD_DEFS, function (_, def) {
var $link = menuEntry("+ " + def.label, def.cls);
$link.on("click", function (e) {
e.preventDefault();
closeMenu();
def.field.wrap.show();
def.field.open();
$link.hide();
});
});
// Group 2: the word lists. wiktku-dfn-pick-word so a şablon sense hides these entries
// along with the groups themselves.
var firstWord = true;
$.each(PICK_DEFS, function (_, def) {
if (!def.word) return;
var $link = menuEntry("+ " + def.label, def.cls,
"wiktku-dfn-pick-word" + (firstWord ? " wiktku-dfn-more-group-start" : ""));
firstWord = false;
$link.on("click", function (e) {
e.preventDefault();
closeMenu();
revealGroup(def.cls, true);
});
});
// Group 3: no "Maneê rake" counterpart -- the tab row already carries one bin for the
// open sense, and a second delete in here would be a second way to do the same thing.
var $newSense = menuEntry(wiktkuUtil.msg.addAnotherSense, null, "wiktku-dfn-more-group-start");
$newSense.on("click", function (e) {
e.preventDefault();
closeMenu();
steps.dfn.addSense($("#wiktku-start-dfn"), block);
});
chipFieldRow.append($("<span class='wiktku-dfn-more-wrap'></span>").append(moreToggle, moreMenu));
bindMoreMenuCloser();
syncPicks();
updateFirstVisible();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode(wiktkuUtil.msg.anotherSense));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Adds a sense, opens it, and hides the others. With afterBlock, the new one lands directly
* under that sense rather than at the end — "Maneyeke din zêde bike" in a sense's own
* "Hevmane û Bêhtir" menu is asking for one next to *this* meaning. */
addSense: function (stepDiv, afterBlock) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
if (afterBlock && afterBlock.length) newBlock.insertAfter(afterBlock);
else list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from data-main.json (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
// The mane box and the îzah box are one gloss on the page: "tercime {{îzah|…}}". Joined
// here so everything downstream (getOutput, the linkify pass, the cache) keeps seeing a
// single string, exactly as it did when the pair was two boxes.
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
var izah = $.trim(block.find("textarea.wiktku-dfn-izah").val());
if (izah) {
mane = $.trim(mane + " {{îzah|" + izah + "}}");
}
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
var url = tplName === "__raw__" || tplName === "__category__" ? ""
: $.trim(li.find(".wiktku-dfn-quote-url").val());
if (url) obj.url = url;
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__raw__") {
// The quote box already holds a finished citation template; it is written
// out untouched, so there is nothing else on this row to read.
obj.raw = true;
} else if (tplName === "__category__") {
// A picked Kategorî:Çavkanî template IS the citation template, with its own
// TemplateData parameters -- rendered into the same markup the jêder-* branch
// below uses, so one loop reads both.
var catName = li.data("catTemplate");
if (catName) {
obj.jederCatTpl = catName;
var catFields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var ck = $(this).attr("data-field-key");
var cv = $.trim($(this).val());
if (ck && cv) catFields[ck] = cv;
});
// A restore in progress has no boxes yet -- its TemplateData is still on
// the way -- so the values it is restoring stand in until they exist.
// See restoreDfn, which parks them here and clears them once they land.
if (!Object.keys(catFields).length) {
catFields = li.data("catFields") || {};
}
obj.jederCatFields = catFields;
var catLang = li.data("catLangParam");
if (catLang) obj.jederCatLangParam = catLang;
}
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = wiktkuCite.withLinkAsUrl(tplName, fields, url);
}
items.push(obj);
});
return items;
}
// Dever/Şîwe, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
var sense = {
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
maneid: $.trim(block.find("input.wiktku-dfn-maneid-input").val() || ""),
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
dever: readChipList("wiktku-dfn-dever")
};
// alternativ, hevmane, dijmane, koordine, jornav, jernav — one property each, named by
// senseGroups' "field" so getOutput() and restoreDfn() find them under the same names.
$.each(wordGroups(), function (_, g) {
sense[g.field] = readList(g.cls);
});
senses.push(sense);
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplManeidTpl = buildManeidTpl(sense, lang);
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplManeidTpl ? tplManeidTpl + " " : "") + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
//
// Every language, not just ku: a sense kept for its jêgirtin or word lists
// alone still needs the marker, or the line goes out as a bare "# ".
dfn = "{{mane?|" + lang + "}}";
}
var maneidTpl = buildManeidTpl(sense, lang);
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (maneidTpl ? maneidTpl + " " : "") + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
// Template name and sense property both come from senseGroups, so this and
// DefSideBoxes write the same "#:" lines.
$.each(ctx.SENSE_GROUPS, function (_, g) {
out += wordLine(g.key, sense[g.field] || []);
});
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
var linkifyText = wiktkuUtil.linkifyText;
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// Nothing written yet: the sense is still marked as needing one, but no example
// placeholder goes with it.
text = "# {{mane?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before. On failure the text
// comes back unchanged, so the save carries on with what the user wrote -- only the
// automatic linking is lost, and saying so is better than losing it silently.
linkifyText(text, function (mainLinked, error) {
if (error) {
mw.notify(error, { type: "warn" });
}
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + ctx.pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!ctx.pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
// The rows are wikt.edit's, shared with DefSideBoxes, which offers the same three boxes.
field: null,
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
steps.resim.field = wiktkuUtil.buildImageListField({
prefix: "wiktku-",
rowClass: "wiktku-posparams-row",
inputClass: "wiktku-start-step-field wiktku-start-narrow",
pageName: ctx.pageName
});
stepDiv.append($("<div class='wiktku-resim-wrap'></div>").append(steps.resim.field.$element));
return null;
},
/** {file, caption, alt} per filled row, with the caption wrapped for a non-Kurdish entry. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
if (!steps.resim.field) return [];
return $.map(steps.resim.field.values(), function (image) {
return {
file: image.file,
caption: wiktkuWikitext.captionForLang(image.caption, lang),
alt: image.alt,
size: image.size,
extra: image.extra
};
});
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return $.grep($.map(images, wiktkuWikitext.buildImageLine), Boolean).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var call = wiktkuWikitext.buildHbnrCall(values.bnrh);
return call ? call + "\n" : "";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language wikt.lang-metadata
// lists more than one script for (its "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data-main.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data-main.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", wiktkuUtil.msg.addTemplate);
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
$(this).val("");
sel.trigger("wiktku:refresh");
// Same param dialog DefSideBoxes' own Etîmolojî editor opens on a fresh insert: filled
// from the template's own TemplateData, with CODE/SOURCELANG already substituted above
// as the fields' starting values, so the user only has to replace the "?" placeholders
// rather than hunt for them in raw wikitext afterward.
wiktkuCite.openTemplateDialog(snippet).then(function (edited) {
if (!edited) return; // cancelled, or "Jê bibe" on a call that was never inserted
insertAtCursor(ta[0], edited);
ta.focus();
});
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// {{ji?}} (a redirect to {{bêetîmolojî}}) is only ever added automatically to Kurdish
// entries.
if (values.lang !== "ku") return "";
// Multi-word page names skip the {{ji?|lang}} placeholder entirely -- "source unknown?" doesn't apply to a phrase.
if (ctx.pageName.includes(" ")) return "";
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.grep($.map(rows, function (row) {
return wiktkuWikitext.buildDundeLine(row[0], row[1]);
}), Boolean);
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ ctx.pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
refresh();
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
refresh();
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(ctx.catSrcTemplates || [], function (_, entry) {
sel.append($("<option></option>").val(entry.target).text(entry.label));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", wiktkuUtil.msg.pickOrTypeTemplate);
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", wiktkuUtil.msg.addTemplate);
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
// Same param dialog DefSideBoxes' own Çavkanî picker opens: the chosen template's
// TemplateData becomes a form, so "rûpel=", "sernav=" and the rest are filled in before
// anything lands in the box, instead of a bare {{Ç:…}} to go back and complete by hand.
sel.on("change", function () {
var name = $(this).val();
$(this).val("");
sel.trigger("wiktku:refresh");
if (!name) return;
wiktkuCite.openTemplateDialog("{{" + name + "}}").then(function (edited) {
if (!edited) {
return; // cancelled, or removed a call that was never inserted
}
insertAtCursor(ta[0], "* " + edited + "\n");
ta.trigger("focus");
});
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (ctx.catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text(wiktkuUtil.msg.templatesLoading);
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text(wiktkuUtil.msg.templatesFailed);
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
return steps;
};
// </nowiki>
ij99mjneoydo5qik8qxxsdihpf50vw2
MediaWiki:Gadget-wikt.lang-metadata.json
8
1359515
6865643
2026-10-24T08:45:43Z
Wikihez
7344
One per-language table for every gadget (data-main.json's, plus nds and gug from the translation adder)
6865643
json
application/json
{
"aa": {
"haswikt": 1,
"sc": [
"Latn",
"Ethi"
]
},
"ab": {
"haswikt": 1,
"sc": [
"Cyrl",
"Latn",
"Geor"
]
},
"aer": {
"sc": "Latn"
},
"af": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ak": {
"haswikt": 1
},
"akk": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Xsux"
},
"als": {
"haswikt": 1
},
"am": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1,
"sc": "Ethi"
},
"an": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ang": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"ar": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"alt": 1,
"haswikt": 1,
"sc": "Arab"
},
"arc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": [
"Hebr",
"Syrc"
]
},
"are": {},
"arz": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Arab"
},
"as": {
"haswikt": 1,
"sc": "Beng"
},
"ast": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"av": {
"haswikt": 1
},
"ay": {
"haswikt": 1
},
"az": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl",
"Arab"
]
},
"ba": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"bar": {},
"bat-smg": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"bbl": {
"sc": "Geor"
},
"be": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"be-x-old": {
"sc": "Cyrl"
},
"bg": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"bh": {
"haswikt": 1,
"sc": "Deva"
},
"bhb": {
"sc": "Deva"
},
"bi": {
"haswikt": 1
},
"blt": {
"sc": "Tavt"
},
"bm": {
"haswikt": 1,
"sc": [
"Latn",
"Nkoo",
"Arab"
]
},
"bn": {
"haswikt": 1,
"sc": "Beng"
},
"bo": {
"haswikt": 1,
"sc": "Tibt"
},
"br": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ca": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"cdo": {
"sc": "Hani"
},
"ce": {
"sc": "Cyrl"
},
"ch": {
"haswikt": 1
},
"chr": {
"haswikt": 1,
"sc": "Cher"
},
"cjy": {
"sc": "Hani"
},
"ckb": {
"haswikt": 1,
"sc": "Arab",
"wsc": "ku-Arab"
},
"cmn": {
"sc": "Hani",
"wiktprefix": "zh"
},
"co": {
"haswikt": 1
},
"cpx": {
"sc": "Hani"
},
"cr": {
"haswikt": 1,
"sc": "Cans"
},
"crh": {},
"cs": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"csb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"cu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": [
"Cyrs",
"Glag"
]
},
"cv": {
"sc": "Cyrl"
},
"cy": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"czh": {
"sc": "Hani"
},
"czo": {
"sc": "Hani"
},
"da": {
"g": [
"g",
"nt",
"g-p",
"nt-p",
"p"
],
"haswikt": 1
},
"dax": {},
"de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"dhg": {},
"djb": {},
"dji": {},
"djr": {},
"dng": {
"sc": "Cyrl"
},
"dsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"dsx": {},
"duj": {},
"dv": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Thaa"
},
"dz": {
"haswikt": 1,
"sc": "Tibt"
},
"el": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"en": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"eo": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"es": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"et": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ett": {
"g": [
"y",
"p"
],
"sc": "Ital"
},
"eu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fa": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-cls": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-ira": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fi": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fil": {},
"fj": {
"haswikt": 1
},
"fo": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1
},
"fr": {
"g": [
"n",
"m",
"mnmane",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"frm": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fro": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fur": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fy": {
"g": [
"g",
"nt",
"p"
],
"haswikt": 1
},
"ga": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"gan": {
"sc": "Hani"
},
"gd": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gez": {
"sc": "Ethi"
},
"gl": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gmw-ecg": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gmy": {
"sc": "Linb"
},
"gn": {
"haswikt": 1
},
"gnn": {
"sc": "Latn"
},
"got": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"sc": "Goth"
},
"grc": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
]
},
"gsw": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Gujr"
},
"guf": {
"sc": "Latn"
},
"gv": {
"haswikt": 1
},
"ha": {
"haswikt": 1
},
"hak": {
"sc": "Hani"
},
"har": {
"sc": "Ethi"
},
"he": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"hi": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Deva"
},
"hif": {
"sc": [
"Latn",
"Deva"
]
},
"hit": {
"sc": "Xsux"
},
"hrx": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"hsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"hsn": {
"sc": "Hani"
},
"hu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"hy": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Armn"
},
"ia": {
"haswikt": 1
},
"id": {
"haswikt": 1
},
"ie": {
"haswikt": 1
},
"ik": {
"haswikt": 1
},
"ike": {
"sc": "Cans"
},
"ikt": {
"sc": "Cans"
},
"io": {
"haswikt": 1
},
"is": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"it": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"iu": {
"haswikt": 1,
"sc": "Cans"
},
"ja": {
"haswikt": 1,
"sc": "Jpan"
},
"jay": {
"sc": "Latn"
},
"jbo": {
"haswikt": 1
},
"jv": {
"haswikt": 1
},
"ka": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"khb": {
"sc": [
"Talu",
"Lana",
"Thai"
]
},
"kjh": {
"sc": "Cyrl"
},
"kk": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"kkh": {
"sc": [
"Lana",
"Thai"
]
},
"kl": {
"haswikt": 1
},
"km": {
"haswikt": 1,
"sc": "Khmr"
},
"kn": {
"haswikt": 1,
"sc": "Knda"
},
"ku": {
"g": [
"n",
"m",
"mnmane",
"mndudil",
"n-p",
"m-p",
"p"
],
"wiktprefix": "ku"
},
"ko": {
"haswikt": 1,
"sc": "Kore"
},
"krc": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"ks": {
"haswikt": 1,
"sc": [
"Arab",
"Deva"
],
"wsc": "ks-Arab"
},
"kw": {
"haswikt": 1
},
"ky": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"la": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"lb": {
"g": [
"n",
"m",
"nt",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"lez": {
"sc": "Cyrl"
},
"li": {
"haswikt": 1
},
"lki": {
"wiktprefix": "ku"
},
"lld": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lmo": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ln": {
"haswikt": 1
},
"lo": {
"haswikt": 1,
"sc": "Laoo"
},
"lt": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lv": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lzz": {
"sc": "Geor"
},
"mg": {
"haswikt": 1
},
"mh": {
"haswikt": 1
},
"mi": {
"g": 0,
"haswikt": 1
},
"mk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ml": {
"haswikt": 1,
"sc": "Mlym"
},
"mn": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Cyrl",
"Mong"
]
},
"mnp": {
"sc": "Hani"
},
"mo": {
"haswikt": 1,
"sc": "Cyrl"
},
"mol": {
"sc": "Cyrl"
},
"mr": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1,
"sc": "Deva"
},
"ms": {
"haswikt": 1,
"sc": [
"Latn",
"Arab"
]
},
"mt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"mwp": {
"sc": "Latn"
},
"my": {
"haswikt": 1,
"sc": "Mymr"
},
"na": {
"haswikt": 1
},
"nah": {
"haswikt": 1
},
"nan": {
"wiktprefix": "zh-min-nan",
"sc": "Hani"
},
"nb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"wiktprefix": "no"
},
"nds-de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"nds-nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"ne": {
"haswikt": 1,
"sc": "Deva"
},
"nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nn": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"no": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nod": {
"sc": "Lana"
},
"non": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"oc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"om": {
"haswikt": 1
},
"or": {
"haswikt": 1,
"sc": "Orya"
},
"orv": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrs"
},
"osc": {
"sc": "Ital"
},
"ota": {
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "ota-Arab"
},
"pa": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": [
"Guru",
"Arab"
]
},
"pdt": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"peo": {
"sc": "Xpeo"
},
"phn": {
"sc": "Phnx"
},
"pi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"pjt": {
"sc": "Latn"
},
"pl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"pms": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"pox": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"împf",
"pf"
]
},
"prs": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"ps": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ps-Arab"
},
"pt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"qu": {
"haswikt": 1
},
"rgn": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"rit": {
"sc": "Latn"
},
"rm": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"rn": {
"haswikt": 1
},
"ro": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"roa-rup": {
"haswikt": 1
},
"ru": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"rue": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrl"
},
"ruo": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rup": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "roa-rup"
},
"ruq": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rw": {
"haswikt": 1
},
"sa": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Deva"
},
"sah": {
"sc": "Cyrl"
},
"sc": {
"haswikt": 1
},
"scn": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"sco": {
"sc": "Latn"
},
"sd": {
"haswikt": 1,
"sc": "Arab",
"wsc": "sd-Arab"
},
"sdh": {
"sc": "Arab",
"wiktprefix": "ku"
},
"sg": {
"haswikt": 1
},
"sh": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"si": {
"haswikt": 1,
"sc": "Sinh"
},
"simple": {
"haswikt": 1
},
"sk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sl": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sm": {
"haswikt": 1
},
"sn": {
"haswikt": 1
},
"so": {
"haswikt": 1
},
"spx": {
"sc": "Ital"
},
"sq": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ss": {
"haswikt": 1
},
"st": {
"haswikt": 1
},
"su": {
"haswikt": 1
},
"sux": {
"sc": "Xsux"
},
"sv": {
"g": [
"g",
"nt",
"g-p",
"nt-p"
],
"haswikt": 1
},
"sva": {
"sc": "Geor"
},
"sw": {
"nclass": 1,
"haswikt": 1
},
"syc": {
"sc": "Syrc"
},
"syr": {
"sc": "Syrc"
},
"szl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"ta": {
"haswikt": 1,
"sc": "Taml"
},
"tdd": {
"sc": "Tale"
},
"te": {
"haswikt": 1,
"sc": "Telu"
},
"tg": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Cyrl"
},
"th": {
"haswikt": 1,
"sc": "Thai"
},
"ti": {
"haswikt": 1,
"sc": "Ethi"
},
"tig": {
"sc": "Ethi"
},
"tiw": {
"sc": "Latn"
},
"tk": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tl": {
"haswikt": 1,
"sc": [
"Latn",
"Tglg"
]
},
"tmr": {
"sc": "Hebr"
},
"tn": {
"haswikt": 1
},
"to": {
"haswikt": 1
},
"tpi": {
"haswikt": 1
},
"tr": {
"g": [
"y",
"p"
],
"alt": 1,
"haswikt": 1
},
"ts": {
"haswikt": 1
},
"tt": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tts": {
"sc": "Thai"
},
"tw": {
"haswikt": 1
},
"udi": {
"sc": [
"Cyrl",
"Latn",
"Armn",
"Geor"
]
},
"ug": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ug-Arab"
},
"uga": {
"sc": "Ugar"
},
"uk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ulk": {
"sc": "Latn"
},
"ur": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ur-Arab"
},
"uz": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"vec": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"vi": {
"haswikt": 1
},
"vls": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"vo": {
"haswikt": 1
},
"wa": {
"haswikt": 1
},
"wbp": {
"sc": "Latn"
},
"wo": {
"haswikt": 1
},
"wuu": {
"sc": "Hani"
},
"xae": {
"sc": "Ital"
},
"xcr": {
"sc": "Cari"
},
"xfa": {
"sc": "Ital"
},
"xh": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"xlc": {
"sc": "Lyci"
},
"xld": {
"sc": "Lydi"
},
"xlu": {
"sc": "Xsux"
},
"xmf": {
"sc": "Geor"
},
"xno": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"xrr": {
"sc": "Ital"
},
"xst": {
"sc": "Ethi"
},
"xum": {
"sc": "Ital"
},
"xve": {
"sc": "Ital"
},
"xvo": {
"sc": "Ital"
},
"yi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"yo": {
"haswikt": 1
},
"yua": {
"g": [
"y",
"p"
],
"alt": 1
},
"yue": {
"sc": "Hani"
},
"za": {
"haswikt": 1,
"sc": "Latn"
},
"zh-classical": {
"sc": "Hani"
},
"zh-min-nan": {
"haswikt": 1
},
"zh-yue": {
"sc": "Hani"
},
"zu": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"zza": {
"g": [
"n",
"m",
"mn",
"n-p",
"m-p",
"p"
],
"wiktprefix": "diq"
},
"nds": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"gug": {
"wiktprefix": "gn"
}
}
bhr197xmg0uyf3gcpmtap8qjux11fwo
MediaWiki:Gadget-wikt.lang-metadata.js
8
1359516
6865644
2026-10-24T08:45:48Z
Wikihez
7344
Per-language table as a module of its own
6865644
javascript
text/javascript
// <nowiki>
/*
* Page: [[MediaWiki:Gadget-wikt.lang-metadata.js]]
*
* Per-language data in ku.wiktionary's codes, one copy for every gadget: genders (g), scripts (sc, wsc), the
* language's own Wiktionary (haswikt, wiktprefix), and the spelling rules the translation adder applies (alt,
* allowCaps, nclass). The table is [[MediaWiki:Gadget-wikt.lang-metadata.json]]; wikt.edit hands it out as
* data.metadata, and the translation adder reads it directly.
*
* var metadata = require("ext.gadget.wikt.lang-metadata");
*/
module.exports = require("./wikt.lang-metadata.json");
// </nowiki>
0jurpcytdfs3nv8jb87yu2pmlqr07ki