Wikiwordbōc
angwiktionary
https://ang.wiktionary.org/wiki/H%C4%93afodtramet
MediaWiki 1.47.0-wmf.22
case-sensitive
Media
Syndrig
Mōtung
Brūcend
Brūcendmōtung
Wikiwordbōc
Wikiwordbōcmōtung
Ymele
Ymelmōtung
MediaWiki
MediaWikimōtung
Bysen
Bysenmōtung
Help
Helpmōtung
Flocc
Floccmōtung
Ætēaca
Ætēacmōtung
TimedText
TimedText talk
Module
Module talk
Event
Event talk
death
0
2061
54960
53046
2026-09-30T17:20:03Z
Deadend0914
7211
/* {{sprǣc|en}} */
54960
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* /dɛθ/
===Wordstǣr===
*Of Engliscum [[dēaþ]]
===Nama===
{{en-noun|~}}
# [[dēaþ]], [[līflēast]]
# [[cwealm]]
#: ''The king of '''death''' is now here.''
[[Flocc:Nīwenglisce naman]]
mx04igg3ebkmbupikyzhaw9iuq3nla8
55007
54960
2026-09-30T18:33:09Z
Deadend0914
7211
/* Nama */
55007
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* /dɛθ/
===Wordstǣr===
*Of Engliscum [[dēaþ]]
===Nama===
{{en-noun|~}}
# [[dēaþ]], [[līflēast]]
# [[cwealm]]
#* ''The king of '''death''' is now here.''
#*: ''Sē cyning of '''dēaþ''' is nū hēr.''
[[Flocc:Nīwenglisce naman]]
ahdtht0ijbyjpi3f1jcdwip7lcggcud
55017
55007
2026-09-30T18:59:59Z
Deadend0914
7211
55017
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* /dɛθ/
===Wordstǣr===
*Of Engliscum [[dēaþ]]
===Nama===
{{en-noun|~}}
# [[dēaþ]], [[līflēast]]
# [[cwealm]]
#* ''The king of '''death''' is now here.''
#*: ''Sē cyning of '''dēaþ''' is nū hēr.''
nyadzzuj1rx86hgnwmp4e7kihwp5110
muoter
0
2655
55033
51432
2026-09-30T19:32:04Z
Deadend0914
7211
/* Ealdhēahþēodisc */
55033
wikitext
text/x-wiki
==Ealdhēahþēodisc==
===Rihtstefn===
* IPA: //
===Wordstǣr===
{{inh+|goh|gmw-pro|*mōder}}
=== {{Wordcynn|Nama}} {{f}} ===
{{goh-noun|f}}
{{EHTH-Naman-Tabule|
Hwā oþþe hwæt? (Ānfeald)=diu muoter
|Hwā oþþe hwæt? (Manigfeald)=deo muoter
|Hwæs? (Ānfeald)=dëra muoter
|Hwæs? (Manigfeald)=dëro muotero
|Hwǣm? (Ānfeald)=dëru muoter
|Hwǣm? (Manigfeald)=dēm muoterum
|Hwȳ? (Ānfeald)=-
|Hwȳ? (Manigfeald)=-
|Hwone? (Ānfeald)=dea muoter
|Hwone? (Manigfeald)=deo muoter
}}
#[[mōdor]]
[[Flocc:Eald Hēah Þēodisc nama]]
[[Flocc:Ealde Hēah Þēodisce lēode]]
mp2h78ixl0iewu2spohr0j6mnn09r1w
55034
55033
2026-09-30T19:32:24Z
Deadend0914
7211
55034
wikitext
text/x-wiki
==Ealdhēahþēodisc==
===Rihtstefn===
* IPA: //
===Wordstǣr===
{{inh+|goh|gmw-pro|*mōder}}
=== {{Wordcynn|Nama}} {{f}} ===
{{goh-noun|f}}
{{EHTH-Naman-Tabule|
Hwā oþþe hwæt? (Ānfeald)=diu muoter
|Hwā oþþe hwæt? (Manigfeald)=deo muoter
|Hwæs? (Ānfeald)=dëra muoter
|Hwæs? (Manigfeald)=dëro muotero
|Hwǣm? (Ānfeald)=dëru muoter
|Hwǣm? (Manigfeald)=dēm muoterum
|Hwȳ? (Ānfeald)=-
|Hwȳ? (Manigfeald)=-
|Hwone? (Ānfeald)=dea muoter
|Hwone? (Manigfeald)=deo muoter
}}
#[[mōdor]]
dwfx29yijnyqtmm4o7fffcnghpo45yx
twēgen
0
3137
55101
54628
2026-10-01T00:10:29Z
Deadend0914
7211
/* Wordstǣr */
55101
wikitext
text/x-wiki
==Englisc==
===Wordstǣr===
{{etymon|ang|:der|gmw-pro:*twai-}}
{{dercat|ang|gem-pro|ine-pro|inh=2}}
Of {{inh|ang|gmw-pro|*twai-}}
=== {{Wordcynn|Getæl}} ===
{{Getæl-Naman2-Tabule|
Hwá oþþe hwæt? (Werlic)=twēgen
|Hwá oþþe hwæt? (Wīflic)=[[twā]]
|Hwá oþþe hwæt? (Nāhwæðer)=[[tū]]
|Hwæs? (Werlic)=twēgra
|Hwæs? (Wīflic)=twēgra
|Hwæs? (Nāhwæðer)=twēgra
|Hwǽm? (Werlic)=twām, twǣm
|Hwǽm? (Wīflic)=twām, twǣm
|Hwǽm? (Nāhwæðer)=twām, twǣm
|Hwý? (Werlic)=twām, twǣm
|Hwý? (Wīflic)=twām, twǣm
|Hwý? (Nāhwæðer)=twām, twǣm
|Hwone? (Werlic)=twēgen
|Hwone? (Wīflic)=[[twā]]
|Hwone? (Nāhwæðer)=[[tū]]
}}
'''twēgen'''
[[Category:Hēafodgetalu]]
*[[w:Arabic numerals|Arabisc getalu]]: 2
*[[w:Chinese numerals|Cīnisc getalu]]: 貳, 二
*[[w:Roman numerals|Rōmānisc getalu]]: II, ii
**Beforan: [[ān]], 1
**Æfter: [[þrīe]], 3
# Þæt rím æfter þrīe and beforan ān
====Wendunga====
'''ān and ān'''
*Denisc: {{Wendung|da|to}}
*[[Fresisc]]: {{Wendung|fy|twa}}
*Giddisc: (tsvay)
*Īslendisc: {{Wendung|is|tveir}} ''wr'', {{Wendung|is|tvær}} ''wf'', {{Wendung|is|tvö}} ''nā''
*Lǣden: {{Wendung|la|duo}}
*[[Nīwenglisc]]: {{Wendung|en|two}}
*Niðerlendisc: {{Wendung|nl|twee}}
*Norþwegisc: {{Wendung|no|to}}
*Swēonisc: {{Wendung|sw|två}}
*Þēodisc: {{Wendung|de|zwei}}, {{Wendung|de|zwo}}
<!--
=== {{Wordcynn|Nama}} ===
{{Getæl-Naman2-Tabule|
Hwá oþþe hwæt? (Werlic)=[[twēgen]]
|Hwá oþþe hwæt? (Wīflic)=[[twā]]
|Hwá oþþe hwæt? (Nāhwæðer)=[[tū]]
|Hwæs? (Werlic)=twēgra
|Hwæs? (Wīflic)=twēgra
|Hwæs? (Nāhwæðer)=twēgra
|Hwǽm? (Werlic)=twām, twǣm
|Hwǽm? (Wīflic)=twām, twǣm
|Hwǽm? (Nāhwæðer)=twām, twǣm
|Hwý? (Werlic)=twām, twǣm
|Hwý? (Wīflic)=twām, twǣm
|Hwý? (Nāhwæðer)=twām, twǣm
|Hwone? (Werlic)=twēgen
|Hwone? (Wīflic)=[[twā]]
|Hwone? (Nāhwæðer)=[[tū]]
}}
'''two''' (''plural:'' '''[[twos]]''')
# The digit/figure 2.
#:''The number 2202 contains three twos.''
====Wendunga====
-->
===Tōgeīecendlic===
'''twēgen''' ''werlic''
# twēgen
'''twi-''' (in gesamnuge swā tōgeīecendlic)
# twinihte, in ielde twēgra daga
# twiwintre, in ielde twēgra gēara
===Sēo ēac===
*[[2]]
[[Category:100 Engliscra grundworda]]
[[Category:Cartangamen]]
03ym54oshohc4qysezo11gbidpr16rl
55102
55101
2026-10-01T00:13:43Z
Deadend0914
7211
55102
wikitext
text/x-wiki
==Englisc==
===Wordstǣr===
Of {{inh|ang|gmw-pro|*twai-}}
=== {{Wordcynn|Getæl}} ===
{{Getæl-Naman2-Tabule|
Hwá oþþe hwæt? (Werlic)=twēgen
|Hwá oþþe hwæt? (Wīflic)=[[twā]]
|Hwá oþþe hwæt? (Nāhwæðer)=[[tū]]
|Hwæs? (Werlic)=twēgra
|Hwæs? (Wīflic)=twēgra
|Hwæs? (Nāhwæðer)=twēgra
|Hwǽm? (Werlic)=twām, twǣm
|Hwǽm? (Wīflic)=twām, twǣm
|Hwǽm? (Nāhwæðer)=twām, twǣm
|Hwý? (Werlic)=twām, twǣm
|Hwý? (Wīflic)=twām, twǣm
|Hwý? (Nāhwæðer)=twām, twǣm
|Hwone? (Werlic)=twēgen
|Hwone? (Wīflic)=[[twā]]
|Hwone? (Nāhwæðer)=[[tū]]
}}
'''twēgen'''
[[Category:Hēafodgetalu]]
*[[w:Arabic numerals|Arabisc getalu]]: 2
*[[w:Chinese numerals|Cīnisc getalu]]: 貳, 二
*[[w:Roman numerals|Rōmānisc getalu]]: II, ii
**Beforan: [[ān]], 1
**Æfter: [[þrīe]], 3
# Þæt rím æfter þrīe and beforan ān
====Wendunga====
'''ān and ān'''
*Denisc: {{Wendung|da|to}}
*[[Fresisc]]: {{Wendung|fy|twa}}
*Giddisc: (tsvay)
*Īslendisc: {{Wendung|is|tveir}} ''wr'', {{Wendung|is|tvær}} ''wf'', {{Wendung|is|tvö}} ''nā''
*Lǣden: {{Wendung|la|duo}}
*[[Nīwenglisc]]: {{Wendung|en|two}}
*Niðerlendisc: {{Wendung|nl|twee}}
*Norþwegisc: {{Wendung|no|to}}
*Swēonisc: {{Wendung|sw|två}}
*Þēodisc: {{Wendung|de|zwei}}, {{Wendung|de|zwo}}
<!--
=== {{Wordcynn|Nama}} ===
{{Getæl-Naman2-Tabule|
Hwá oþþe hwæt? (Werlic)=[[twēgen]]
|Hwá oþþe hwæt? (Wīflic)=[[twā]]
|Hwá oþþe hwæt? (Nāhwæðer)=[[tū]]
|Hwæs? (Werlic)=twēgra
|Hwæs? (Wīflic)=twēgra
|Hwæs? (Nāhwæðer)=twēgra
|Hwǽm? (Werlic)=twām, twǣm
|Hwǽm? (Wīflic)=twām, twǣm
|Hwǽm? (Nāhwæðer)=twām, twǣm
|Hwý? (Werlic)=twām, twǣm
|Hwý? (Wīflic)=twām, twǣm
|Hwý? (Nāhwæðer)=twām, twǣm
|Hwone? (Werlic)=twēgen
|Hwone? (Wīflic)=[[twā]]
|Hwone? (Nāhwæðer)=[[tū]]
}}
'''two''' (''plural:'' '''[[twos]]''')
# The digit/figure 2.
#:''The number 2202 contains three twos.''
====Wendunga====
-->
===Tōgeīecendlic===
'''twēgen''' ''werlic''
# twēgen
'''twi-''' (in gesamnuge swā tōgeīecendlic)
# twinihte, in ielde twēgra daga
# twiwintre, in ielde twēgra gēara
===Sēo ēac===
*[[2]]
[[Category:100 Engliscra grundworda]]
[[Category:Cartangamen]]
4hv359uq8e9d3e3pz618jvj6okcgso7
Affrica
0
5199
55088
54528
2026-09-30T23:44:02Z
Deadend0914
7211
/* {{f}} {{Wordcynn|Nama}} */
55088
wikitext
text/x-wiki
=={{sprǣc|ang}}==
=== Rihtstefn ===
*[[w:SAMPA|SAMPA]]: /'/
*[[w:IPA|IPA]]: /'ɑf·fri·kɑ/
=== {{f}} {{Wordcynn|Nama}} ===
#se worulddǣl súþ of Európe and betwēonum þām [[Indiscum gārsecge]] and þām [[Altantiscum gārsecge]]
====Wendunga====
*[[Nīwenglisc]]: {{Wendung|en|Africa}}
*[[Þēodisc]]: {{Wendung|de|Afrika}}
*[[Hebrēisc]]: {{Wendung|he|אפריקה}}
*[[Crēacisc]]: [[Αφρική]]
6w3b9m7safds9uvgzmwynxt4ozm9uee
55089
55088
2026-09-30T23:44:37Z
Deadend0914
7211
55089
wikitext
text/x-wiki
=={{sprǣc|ang}}==
=== Rihtstefn ===
*[[w:SAMPA|SAMPA]]: /'/
*[[w:IPA|IPA]]: /'ɑf·fri·kɑ/
===Wiflic Nama===
#se worulddǣl súþ of Európe and betwēonum þām [[Indiscum gārsecge]] and þām [[Altantiscum gārsecge]]
====Wendunga====
*[[Nīwenglisc]]: {{Wendung|en|Africa}}
*[[Þēodisc]]: {{Wendung|de|Afrika}}
*[[Hebrēisc]]: {{Wendung|he|אפריקה}}
*[[Crēacisc]]: [[Αφρική]]
cy20nt90lgqwg85pjmedei85j63782u
55090
55089
2026-09-30T23:44:52Z
Deadend0914
7211
/* Wiflic Nama */
55090
wikitext
text/x-wiki
=={{sprǣc|ang}}==
=== Rihtstefn ===
*[[w:SAMPA|SAMPA]]: /'/
*[[w:IPA|IPA]]: /'ɑf·fri·kɑ/
===Wiflic Nama===
#se [[worulddǣl]] súþ of Európe and betwēonum þām [[Indiscum gārsecge]] and þām [[Altantiscum gārsecge]]
====Wendunga====
*[[Nīwenglisc]]: {{Wendung|en|Africa}}
*[[Þēodisc]]: {{Wendung|de|Afrika}}
*[[Hebrēisc]]: {{Wendung|he|אפריקה}}
*[[Crēacisc]]: [[Αφρική]]
f4udrhbb32n4k5y639q2330nlu3vda9
55091
55090
2026-09-30T23:45:35Z
Deadend0914
7211
/* {{sprǣc|ang}} */
55091
wikitext
text/x-wiki
=={{sprǣc|ang}}==
=== Rihtstefn ===
*[[w:SAMPA|SAMPA]]: /'/
*[[w:IPA|IPA]]: /'ɑf·fri·kɑ/
===Agennama===
{{ang-noun|f|head=Affrica}}
#se [[worulddǣl]] súþ of Európe and betwēonum þām [[Indiscum gārsecge]] and þām [[Altantiscum gārsecge]]
====Wendunga====
*[[Nīwenglisc]]: {{Wendung|en|Africa}}
*[[Þēodisc]]: {{Wendung|de|Afrika}}
*[[Hebrēisc]]: {{Wendung|he|אפריקה}}
*[[Crēacisc]]: [[Αφρική]]
87ehgngqwmfhn09bj7xcb6dnkl1baf3
bed
0
5800
55013
52440
2026-09-30T18:42:05Z
Deadend0914
7211
/* Nama */
55013
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
IPA:/bɛd/
===Nama===
{{en-noun|~}}
#[[bedd]]
====Fruma====
*Ofgangen of Engliscum [[bedd]]
ond1wwxfobkqkmsy6okvz8q6yyb7zcm
dēaþ
0
6423
55067
54946
2026-09-30T22:32:38Z
Deadend0914
7211
55067
wikitext
text/x-wiki
=={{sprǣc|ang}}==
===Āwendednessa===
* deað
* dǣþ
===Rihtstefn===
* /dæːɑθ/
===Wordstǣr===
Of {{inh|ang|gmw-pro|*dauþu}}, of {{inh|ang|gem-pro|*dauþuz}}, of {{inh|ang|ine-pro|*dʰautus}}.
====Sweostorword====
*Denisc: {{Wendung|da|død}}
*[[Fresisc]]: {{Wendung|fy|dea}} ''wr''
*Īslendisc: {{Wendung|is|dauði}} ''wr''
*Niðerlendisc: {{Wendung|nl|dood}} ''wr''
*Norþwegisc: {{Wendung|no|død}} ''wr''
*[[Seaxisc]]: {{Wendung|nds|Dood}} ''wr''
*Swēonisc: {{Wendung|sv|död}} ''gm''
*Þēodisc: {{Wendung|de|Tod}} ''wr''
===Werlīc Nama===
{{ang-noun|m|head=dēaþ}}
# Þæs līfes ende.
#: '''''Dēaþ''' nis nān belimp on līfe. '''Dēaðes''' man ne gebītt. Gif man ēcnesse understent nā tō endelēasre tīdlengðe, ac tō tīdlēasnesse, þonne leofaþ sē on ēcnesse þe leofaþ on andweardnesse. Ūre līf is endelēas ealswā ūre gesihtfeld gemǣrelēas biþ.''
====Nīwenglisc Andgiet====
# [[death]]
====Declīnung====
{{ang-decl-noun-a-m|dēaþ}}
====Gesetnyssum====
[[ær]]-dēaþ - early death, death before one's time, premature death <br>
dēaþ-[[bēam]] "wr" - death tree <br>
dēaþ-[[bedd]] "nw" - death bed, grave, bed of death, <br>
dēaþ-[[cwalu]] "wf" - plague death, violent death, <br>
dēaþ-[[dæg]] "wr" - death day, day of death, <br>
dēaþ-denu "wf" - valley of death, <br>
fær-dēaþ, dēaþ-ræs "wr" - sudden death, <br>
dēaþ-scūa "wr", dēaþ-scufa "wr" - death shadow, shadow of death, <br>
dēaþ-[[scyld]] "wf" - crime worthy of death <br>
dēaþ-stede "wr" - place of death <br>
dēaþ-[[wang]] "wr" - death plain, <br>
dēaþ-wīc "nw" - death's place, dwelling of the dead, <br>
dēaþ-[[wyrd]] "wf" - death fate, fate of death <br>
[[ende]]-dēað -death as the end of life <br>
[[gúð]]-dēað - battle death, death in battle <br>
[[mere]]-dēaþ - sea death, death at sea, <br>
[[swylt]]-dēaþ - death (die-death) <br>
[[wæl]]-dēaþ - death in battle, <br>
[[wundor]]-dēaþ - wondrous death <br>
===Tudru===
* {{desctree|enm|deth}}
====Wendunga====
*Frencisc: {{Wendung|fr|mort}} ''wf''
*Grēcisc: {{Wendung|el|θάνατος}} [ˈθa.na.to̞s] ''wr'', {{Wendung|el|θανατάς}} [θa.na.ˈtas] ''wr'',
{{Wendung|el|πεθαμός}} [pe̞.θa.ˈmo̞s] ''wr'', {{Wendung|el|αποθαμός}} [a.po̞.θa.ˈmo̞s] {{m}}, {{Wendung|el|χάρος}} [ˈxa.ro̞s] ''wr''
*Hebrēisc: {{Wendung|he|מוות}} (mavet) ''wf''
*Italisc: {{Wendung|it|morte}} ''wf''
*Lǣden: {{Wendung|la|mors}} ''wf'', {{Wendung|la|exitium}} ''nā'', {{Wendung|la|quietus}} ''wr''
*Persisc: {{Wendung|fa|مرگ}}
*Scyttisc Gǣlisc: {{Wendung|gd|bàs}} ''wr''
jxw3rdcuelrio4145wz51emizztlc9m
bedd
0
7606
55008
53211
2026-09-30T18:34:52Z
Deadend0914
7211
/* {{n}} {{Wordcynn|Nama}} */
55008
wikitext
text/x-wiki
==Englisc==
[[File:Illustrated catalogue of the exceedingly rare and valuable art treasures and antiquities formerly contained in the famous Davanzati Palace, Florence, Italy (1916) (14780435285).jpg|thumb|Ān bedd]]
===Rihtstefn===
*[[IPA]]: //
==={{n}} {{Wordcynn|Nama}}===
{{ang-noun|n|head=dēaþ}}
# Ān þing þe is for swefende and is ġewunelīċe hnesce.
[[Category:Englisc nama]]
3y8401kwnfhc8dh0ebu2udxkv7y14yt
55009
55008
2026-09-30T18:35:55Z
Deadend0914
7211
55009
wikitext
text/x-wiki
==Englisc==
[[File:Illustrated catalogue of the exceedingly rare and valuable art treasures and antiquities formerly contained in the famous Davanzati Palace, Florence, Italy (1916) (14780435285).jpg|thumb|Ān bedd]]
===Rihtstefn===
*[[IPA]]: //
==={{n}} {{Wordcynn|Nama}}===
{{ang-noun|n|head=dēaþ}}
# Ān þing þe is for swefende and is ġewunelīċe hnesce.
===Declīnung===
{{ang-decl-noun-a-n|bedd}}
===Wendunga===
* Nīwenglisc: [[bed]]
onal7ghhmoz626pn7m0udltvzdc8l5t
55086
55009
2026-09-30T23:04:34Z
Deadend0914
7211
/* {{n}} {{Wordcynn|Nama}} */
55086
wikitext
text/x-wiki
==Englisc==
[[File:Illustrated catalogue of the exceedingly rare and valuable art treasures and antiquities formerly contained in the famous Davanzati Palace, Florence, Italy (1916) (14780435285).jpg|thumb|Ān bedd]]
===Rihtstefn===
*[[IPA]]: //
==={{n}} {{Wordcynn|Nama}}===
{{ang-noun|n|head=bedd}}
# Ān þing þe is for swefende and is ġewunelīċe hnesce.
===Declīnung===
{{ang-decl-noun-a-n|bedd}}
===Wendunga===
* Nīwenglisc: [[bed]]
ga43d3kbe0chczb833zxrr7v5dyqi4l
ærn
0
7628
55103
53263
2026-10-01T00:18:19Z
Deadend0914
7211
/* {{n}} {{Wordcynn|Nama}} */
55103
wikitext
text/x-wiki
==Englisc==
[[File:Spannarboda banvaktstuga_2.jpg|thumb|ān ærn]]
===Rihtstefn===
*[[IPA]]: //
==={{n}} {{Wordcynn|Nama}}===
# ān [[timbrung]] for [[menn]] tō libbenne in
[[Category:Englisc nama]]
[[Category:Englisc nāhwæðer nama]]
800686u5hogc60aibl6w785oseg4zk3
Module:en-headword
828
8056
55004
54932
2026-09-30T18:15:18Z
Deadend0914
7211
55004
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
mw.title.getCurrentTitle().fullText == "Template:en-head" and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, "Long " .. langname .. " words")
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " comparative-only " .. plpos)
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " superlative-only " .. plpos)
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " uncomparable " .. plpos)
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "sg", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("uncountable nouns")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("uncountable nouns")
inscat("countable nouns")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("uncountable nouns")
inscat("countable nouns")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("uncountable nouns")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("countable nouns")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("indeclinable nouns")
end
insert_inflection(data, plurals, "plural", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["nouns"] = {
params = noun_params,
func = do_nouns,
}
pos_functions["proper nouns"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "proper nouns")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "third-person singular simple present", "s-verb-form")
insert_inflection(data, pres_ptcs, "present participle", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "simple past and past participle", "ed-form",
"no simple past or past participle")
else
insert_inflection(data, pasts, "simple past", "spast")
insert_inflection(data, past_ptcs, "past participle", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' terms wiþ placeholder "it"')
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " phrasal verbs")
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' phrasal verbs formed wiþ "' .. seen_adverbs[i] ..
'"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
0pr0m96lw09bogexw1yeqsgg96stl2m
55006
55004
2026-09-30T18:25:43Z
Deadend0914
7211
55006
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
mw.title.getCurrentTitle().fullText == "Template:en-head" and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, "Long " .. langname .. " words")
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " comparative-only " .. plpos)
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " superlative-only " .. plpos)
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " uncomparable " .. plpos)
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "anf.", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("uncountable nouns")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("uncountable nouns")
inscat("countable nouns")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("uncountable nouns")
inscat("countable nouns")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("uncountable nouns")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("countable nouns")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("indeclinable nouns")
end
insert_inflection(data, plurals, "mnf.", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["nouns"] = {
params = noun_params,
func = do_nouns,
}
pos_functions["proper nouns"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "proper nouns")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "third-person singular simple present", "s-verb-form")
insert_inflection(data, pres_ptcs, "present participle", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "simple past and past participle", "ed-form",
"no simple past or past participle")
else
insert_inflection(data, pasts, "simple past", "spast")
insert_inflection(data, past_ptcs, "past participle", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' terms wiþ placeholder "it"')
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " phrasal verbs")
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' phrasal verbs formed wiþ "' .. seen_adverbs[i] ..
'"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
k7b60n7hioqcf71urtf8o7t1b1oekdz
55018
55006
2026-09-30T19:05:03Z
Deadend0914
7211
55018
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
mw.title.getCurrentTitle().fullText == "Template:en-head" and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, "Long " .. langname .. " words")
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " comparative-only " .. plpos)
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " superlative-only " .. plpos)
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " uncomparable " .. plpos)
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "anf.", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīc naman")
end
insert_inflection(data, plurals, "mnf.", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["nouns"] = {
params = noun_params,
func = do_nouns,
}
pos_functions["proper nouns"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "proper nouns")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "third-person singular simple present", "s-verb-form")
insert_inflection(data, pres_ptcs, "present participle", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "simple past and past participle", "ed-form",
"no simple past or past participle")
else
insert_inflection(data, pasts, "simple past", "spast")
insert_inflection(data, past_ptcs, "past participle", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' terms wiþ placeholder "it"')
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " phrasal verbs")
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' phrasal verbs formed wiþ "' .. seen_adverbs[i] ..
'"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
osb4kfl21hdj0yyrx7i63pqxluek83h
55047
55018
2026-09-30T19:53:13Z
Deadend0914
7211
55047
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
mw.title.getCurrentTitle().fullText == "Template:en-head" and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, "Long " .. langname .. " words")
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " comparative-only " .. plpos)
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " superlative-only " .. plpos)
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " uncomparable " .. plpos)
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "anf.", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīc naman")
end
insert_inflection(data, plurals, "mnf.", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["nouns"] = {
params = noun_params,
func = do_nouns,
}
pos_functions["proper nouns"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "proper nouns")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "þridda had anfeald ānfeald andweard", "s-verb-form")
insert_inflection(data, pres_ptcs, "andweard dǣlnimend", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "ānfeald forþgewiten and forþgewiten dǣlnimend", "ed-form",
"na ānfeald forþgewiten oþþe forþgewiten dǣlnimend")
else
insert_inflection(data, pasts, "ānfeald forþgewiten", "spast")
insert_inflection(data, past_ptcs, "forþgewiten dǣlnimend", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' terms wiþ placeholder "it"')
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " phrasal verbs")
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' phrasal verbs formed wiþ "' .. seen_adverbs[i] ..
'"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
gqdd0x9n0oyiwg0iohbahujvi9xxwpm
55050
55047
2026-09-30T20:09:25Z
Deadend0914
7211
55050
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
-- Old English localization: category wordings come from [[Module:floccnaman]]
-- (see that module); the English wording is kept when a translation is missing.
-- Loaded lazily and optionally, so a missing module can never break a page.
local floccnaman_module = "Module:floccnaman"
local floccnaman
local function get_floccnaman()
if floccnaman == nil then
local ok, mod = pcall(require, floccnaman_module)
floccnaman = ok and mod or false
end
return floccnaman or nil
end
-- Old English wording for one category word, or the English wording unchanged.
local function cat_word(l, text)
local mod = get_floccnaman()
return mod and mod.get(l, text) or text
end
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- On English Wiktionary, {{tl|en-head}} is the template that means "interjection"; it invokes this module without
-- a part of speech. This wiki's template namespace is named "Bysen", not "Template", so the namespace id and the
-- base page title are compared instead of the full title text with its namespace prefix.
local function is_en_head_template()
local title = mw.title.getCurrentTitle()
return title:getNamespace() == 10 and title:getBaseText() == "en-head"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
is_en_head_template() and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, langname .. " " .. cat_word(lang, "Long words"))
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "comparative-only " .. plpos))
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "superlative-only " .. plpos))
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " " .. cat_word(lang, "uncomparable " .. plpos))
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "sg", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīce naman")
end
insert_inflection(data, plurals, "plural", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["naman"] = {
params = noun_params,
func = do_naman,
}
pos_functions["agennaman"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "agennama")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "third-person singular simple present", "s-verb-form")
insert_inflection(data, pres_ptcs, "present participle", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "simple past and past participle", "ed-form",
"no simple past or past participle")
else
insert_inflection(data, pasts, "simple past", "spast")
insert_inflection(data, past_ptcs, "past participle", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' ' .. cat_word(lang, 'terms with placeholder "it"'))
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " " .. cat_word(lang, "phrasal verbs"))
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' ' .. cat_word(lang, "phrasal verbs formed with") .. ' "' .. seen_adverbs[i] .. '\"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
flfnyrsa9a9yvant9ksx7owv6yhgvye
55051
55050
2026-09-30T20:15:04Z
Deadend0914
7211
55051
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
mw.title.getCurrentTitle().fullText == "Template:en-head" and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, "Long " .. langname .. " words")
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " comparative-only " .. plpos)
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " superlative-only " .. plpos)
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " uncomparable " .. plpos)
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "anf.", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat)
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīc naman")
end
insert_inflection(data, plurals, "mnf.", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["nouns"] = {
params = noun_params,
func = do_nouns,
}
pos_functions["proper nouns"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "proper nouns")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "þridda had anfeald ānfeald andweard", "s-verb-form")
insert_inflection(data, pres_ptcs, "andweard dǣlnimend", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "ānfeald forþgewiten and forþgewiten dǣlnimend", "ed-form",
"na ānfeald forþgewiten oþþe forþgewiten dǣlnimend")
else
insert_inflection(data, pasts, "ānfeald forþgewiten", "spast")
insert_inflection(data, past_ptcs, "forþgewiten dǣlnimend", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' terms wiþ placeholder "it"')
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " phrasal verbs")
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' phrasal verbs formed wiþ "' .. seen_adverbs[i] ..
'"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
gqdd0x9n0oyiwg0iohbahujvi9xxwpm
55052
55051
2026-09-30T20:19:39Z
Deadend0914
7211
55052
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
-- Old English localization: category wordings come from [[Module:floccnaman]]
-- (see that module); the English wording is kept when a translation is missing.
-- Loaded lazily and optionally, so a missing module can never break a page.
local floccnaman_module = "Module:floccnaman"
local floccnaman
local function get_floccnaman()
if floccnaman == nil then
local ok, mod = pcall(require, floccnaman_module)
floccnaman = ok and mod or false
end
return floccnaman or nil
end
-- Old English wording for one category word, or the English wording unchanged.
local function cat_word(l, text)
local mod = get_floccnaman()
return mod and mod.get(l, text) or text
end
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- On English Wiktionary, {{tl|en-head}} is the template that means "interjection"; it invokes this module without
-- a part of speech. This wiki's template namespace is named "Bysen", not "Template", so the namespace id and the
-- title without its namespace prefix are compared instead of a hard-coded "Template:en-head".
-- Scribunto's title object exposes these as the fields `namespace`, `nsText` and `fullText`; it has no
-- getNamespace()/getBaseText() methods, so those must not be used here.
local function is_en_head_template()
local title = mw.title.getCurrentTitle()
return title.namespace == 10 and title.fullText == title.nsText .. ":en-head"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
is_en_head_template() and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, langname .. " " .. cat_word(lang, "Long words"))
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "comparative-only " .. plpos))
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "superlative-only " .. plpos))
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " " .. cat_word(lang, "uncomparable " .. plpos))
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "sg", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīce naman")
end
insert_inflection(data, plurals, "plural", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["naman"] = {
params = noun_params,
func = do_naman,
}
pos_functions["agennaman"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "agennama")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "third-person singular simple present", "s-verb-form")
insert_inflection(data, pres_ptcs, "present participle", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "simple past and past participle", "ed-form",
"no simple past or past participle")
else
insert_inflection(data, pasts, "simple past", "spast")
insert_inflection(data, past_ptcs, "past participle", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' ' .. cat_word(lang, 'terms with placeholder "it"'))
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " " .. cat_word(lang, "phrasal verbs"))
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' ' .. cat_word(lang, "phrasal verbs formed with") .. ' "' .. seen_adverbs[i] .. '\"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
5duit8o5s5658vqhagczvdy4r9pg8bv
55053
55052
2026-09-30T21:58:33Z
Deadend0914
7211
55053
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
-- Old English localization: category wordings come from [[Module:floccnaman]]
-- (see that module); the English wording is kept when a translation is missing.
-- Loaded lazily and optionally, so a missing module can never break a page.
local floccnaman_module = "Module:floccnaman"
local floccnaman
local function get_floccnaman()
if floccnaman == nil then
local ok, mod = pcall(require, floccnaman_module)
floccnaman = ok and mod or false
end
return floccnaman or nil
end
-- Old English wording for one category word, or the English wording unchanged.
local function cat_word(l, text)
local mod = get_floccnaman()
return mod and mod.get(l, text) or text
end
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- On English Wiktionary, {{tl|en-head}} is the template that means "interjection"; it invokes this module without
-- a part of speech. This wiki's template namespace is named "Bysen", not "Template", so the namespace id and the
-- title without its namespace prefix are compared instead of a hard-coded "Template:en-head".
-- Scribunto's title object exposes these as the fields `namespace`, `nsText` and `fullText`; it has no
-- getNamespace()/getBaseText() methods, so those must not be used here.
local function is_en_head_template()
local title = mw.title.getCurrentTitle()
return title.namespace == 10 and title.fullText == title.nsText .. ":en-head"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
is_en_head_template() and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if not pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, langname .. " " .. cat_word(lang, "Long words"))
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "comparative-only " .. plpos))
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "superlative-only " .. plpos))
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " " .. cat_word(lang, "uncomparable " .. plpos))
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "sg", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīce naman")
end
insert_inflection(data, plurals, "plural", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["naman"] = {
params = noun_params,
func = do_naman,
}
pos_functions["agennaman"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "agennama")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "third-person singular simple present", "s-verb-form")
insert_inflection(data, pres_ptcs, "present participle", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "simple past and past participle", "ed-form",
"no simple past or past participle")
else
insert_inflection(data, pasts, "simple past", "spast")
insert_inflection(data, past_ptcs, "past participle", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' ' .. cat_word(lang, 'terms with placeholder "it"'))
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " " .. cat_word(lang, "phrasal verbs"))
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' ' .. cat_word(lang, "phrasal verbs formed with") .. ' "' .. seen_adverbs[i] .. '\"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
aczogv58cwqd8atwgt0t7mi5n61gzdv
55059
55053
2026-09-30T22:19:21Z
Deadend0914
7211
55059
Scribunto
text/plain
local export = {}
local pos_functions = {}
--[==[
Author from 2020 on: mostly Benwing2, with significant contributions from Theknightwho. Based on a prior version by Rua
(by now mostly rewritten), with contributions from Erutuon and others (see history for full attribution).
]==]
local force_cat = false -- for testing; if true, categories appear in non-mainspace pages
local require = require
local require_when_needed = require("Module:require when needed")
local en_utilities_module = "Module:en-utilities"
local headword_utilities_module = "Module:headword utilities"
local headword_module = "Module:headword"
local inflection_utilities_module = "Module:inflection utilities"
local parse_utilities_module = "Module:parse utilities"
local JSON_module = "Module:JSON"
local labels_module = "Module:labels"
local links_module = "Module:links"
local parameters_module = "Module:parameters"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local utilities_module = "Module:utilities"
local yesno_module = "Module:yesno"
local iut = require_when_needed(inflection_utilities_module)
local put = require_when_needed(parse_utilities_module)
local m_headword_utilities = require_when_needed(headword_utilities_module)
local add_links_to_multiword_term = require_when_needed(headword_utilities_module, "add_links_to_multiword_term")
local add_suffix = require_when_needed(en_utilities_module, "add_suffix")
local apply_link_modifiers = require_when_needed(headword_utilities_module, "apply_link_modifiers")
local concat = table.concat
local deepEquals = require_when_needed(table_module, "deepEquals")
local dump = mw.dumpObject
local format_categories = require_when_needed(utilities_module, "format_categories")
local full_headword = require_when_needed(headword_module, "full_headword")
local get_label_info = require_when_needed(labels_module, "get_label_info")
local get_link_page = require_when_needed(links_module, "get_link_page")
local glossary_link = require_when_needed(headword_utilities_module, "glossary_link")
local insert = table.insert
local insertIfNot = require_when_needed(table_module, "insertIfNot")
local ipairs = ipairs
local is_regular_plural = require_when_needed(en_utilities_module, "is_regular_plural")
local list_to_set = require_when_needed(table_module, "listToSet")
local pairs = pairs
local process_params = require_when_needed(parameters_module, "process")
local remove = table.remove
local remove_links = require_when_needed(links_module, "remove_links")
local replacement_escape = require_when_needed(string_utilities_module, "replacement_escape")
local shallowCopy = require_when_needed(table_module, "shallowCopy")
local singularize = require_when_needed(en_utilities_module, "singularize")
local split = require_when_needed(string_utilities_module, "split")
local toJSON = require_when_needed(JSON_module, "toJSON")
local toNFD = mw.ustring.toNFD
local type = type
local ulen = require_when_needed(string_utilities_module, "len")
local ulower = require_when_needed(string_utilities_module, "lower")
local umatch = require_when_needed(string_utilities_module, "match")
local u = require_when_needed(string_utilities_module, "char")
local ugsub = require_when_needed(string_utilities_module, "gsub")
local lang = require("Module:languages").getByCode("en")
local langname = lang:getCanonicalName()
-- Old English localization: category wordings come from [[Module:floccnaman]]
-- (see that module); the English wording is kept when a translation is missing.
-- Loaded lazily and optionally, so a missing module can never break a page.
local floccnaman_module = "Module:floccnaman"
local floccnaman
local function get_floccnaman()
if floccnaman == nil then
local ok, mod = pcall(require, floccnaman_module)
floccnaman = ok and mod or false
end
return floccnaman or nil
end
-- Old English wording for one category word, or the English wording unchanged.
local function cat_word(l, text)
local mod = get_floccnaman()
return mod and mod.get(l, text) or text
end
local list_param = {list = true, disallow_holes = true}
local list_allow_holes = {list = true, allow_holes = true}
local boolean_param = {type = "boolean"}
local function ine(val)
if val == "" then return nil else return val end
end
local function track(page)
require("Module:debug/track")("en-headword/" .. page)
return true
end
------------------------------------------- UTILITY FUNCTIONS ------------------------------------------
-- Parse and return an inflection not requiring additional processing. The raw arguments come from `args[field]`, which
-- is parsed for inline modifiers.
local function parse_inflection(args, field, is_head)
local argfield = field
if type(argfield) == "table" then
argfield = argfield[1]
end
return m_headword_utilities.parse_term_list_with_modifiers {
paramname = field,
forms = args[argfield],
splitchar = ",",
is_head = is_head,
}
end
-- Insert the parsed inflections in `terms` (as parsed by `parse_inflection`) into `data.inflections`, with label
-- `label` and optional accelerator spec `accel`.
local function insert_inflection(data, terms, label, accel, no_label)
for _, termobj in ipairs(terms) do
m_headword_utilities.remove_termobj_field_modifiers(termobj)
end
m_headword_utilities.insert_inflection {
headdata = data,
terms = terms,
label = label,
no_label = no_label,
accel = accel and {form = accel} or nil,
}
end
-- Insert a fixed label `label` into the inflections for `data`. If `originating_term` is supplied, copy the decorations
-- from it into the fixed label.
local function insert_fixed_inflection(data, label, originating_term)
m_headword_utilities.insert_fixed_inflection {
headdata = data,
originating_term = originating_term,
label = label,
}
end
-- Parse and insert an inflection not requiring additional processing into `data.inflections`. The raw arguments come
-- from `args[field]`, which is parsed for inline modifiers. `label` is the label that the inflections are given;
-- `accel` is the accelerator form, or nil.
local function parse_and_insert_inflection(data, args, field, label, accel)
m_headword_utilities.parse_and_insert_inflection {
headdata = data,
forms = args[field],
paramname = field,
splitchar = ",",
label = label,
accel = accel and {form = accel} or nil,
}
end
-- These functions are used directly in the <> format as well as in the utility functions #2 below.
local function compute_double_last_cons_stem(term)
local last_cons = term:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Verb stem '" .. term .. "' must end in a consonant to use ++")
end
return term .. last_cons
end
local function compute_plusplus_s_form(term, default_s_form)
if term:find("[szx]$") then
-- regas -> regasses, derez -> derezzes
return compute_double_last_cons_stem(term) .. "es"
else
return default_s_form
end
end
-- On English Wiktionary, {{tl|en-head}} is the template that means "interjection"; it invokes this module without
-- a part of speech. This wiki's template namespace is named "Bysen", not "Template", so the namespace id and the
-- title without its namespace prefix are compared instead of a hard-coded "Template:en-head".
-- Scribunto's title object exposes these as the fields `namespace`, `nsText` and `fullText`; it has no
-- getNamespace()/getBaseText() methods, so those must not be used here.
local function is_en_head_template()
local title = mw.title.getCurrentTitle()
return title.namespace == 10 and title.fullText == title.nsText .. ":en-head"
end
-- The main entry point.
-- This is the only function that can be invoked from a template.
function export.show(frame)
local iparams = {
[1] = true,
}
local iargs = require("Module:parameters").process(frame.args, iparams)
local parargs = frame:getParent().args
local poscat = iargs[1]
local pos_in_1 = not poscat
if pos_in_1 then
poscat = ine(parargs[1]) or
is_en_head_template() and "interjection" or
error("Part of speech must be specified in 1=")
poscat = require(headword_module).canonicalize_pos(poscat)
end
local indexing_poscat = pos_in_1 and "head" or poscat
local params = {
["head"] = list_param,
["id"] = true,
["json"] = boolean_param,
["sort"] = true,
["splithyph"] = boolean_param,
["nosplithyph"] = boolean_param,
["hyphspace"] = boolean_param,
["nolink"] = boolean_param,
["nolinkhead"] = {type = "boolean_param", alias_of = "nolink"},
["suffix"] = boolean_param,
["nosuffix"] = boolean_param,
["nomultiwordcat"] = boolean_param,
["abbr"] = list_param,
["the"] = true,
["def"] = {alias_of = "the"},
["pagename"] = true, -- for testing
}
if not pos_in_1 then
params[1] = {required = true} -- required but ignored as already processed above
end
local pos_data = pos_functions[indexing_poscat]
local pos_func
if pos_data then
local pos_params = pos_data.params
if pos_params then
for key, val in pairs(pos_params) do
params[key] = val
end
end
pos_func = pos_data.func
end
local args = process_params(parargs, params)
-- Account for unsupported titles, e.g. 'C|N>K' instead of 'Unsupported titles/C through N to K'.
local pagename = args.pagename or mw.loadData("Module:headword/data").pagename
local user_specified_heads = parse_inflection(args, "head", "is_head")
local heads = user_specified_heads
local autohead
if args.nolink or not pagename:find("[ '%-]") then
autohead = pagename
else
local en_no_split_apostrophe_words = list_to_set {
"one's",
"someone's",
"he's",
"she's",
"it's",
}
local en_include_hyphen_prefixes = list_to_set {
-- We don't include things that are also words even though they are often (perhaps mostly) prefixes, e.g.
-- "be", "counter", "cross", "extra", "half", "mid", "over", "pan", "under".
"acro",
"acousto",
"Afro",
"agro",
"anarcho",
"angio",
"Anglo",
"ante",
"anti",
"arch",
"auto",
"bi",
"bio",
"cis",
"co",
"cryo",
"crypto",
"de",
"demi",
"eco",
"electro",
"Euro",
"ex",
"Greco",
"hemi",
"hydro",
"hyper",
"hypo",
"infra",
"Indo",
"inter",
"intra",
"Judeo",
"macro",
"meta",
"micro",
"mini",
"multi",
"neo",
"neuro",
"non",
"para",
"peri",
"post",
"pre",
"pro",
"proto",
"pseudo",
"re",
"semi",
"sub",
"super",
"trans",
"un",
"vice",
}
local function is_english(term)
local title = mw.title.new(term)
if title and title.exists then
local content = title:getContent()
if content and content:find("==English==\n") then
return true
end
end
return false
end
local function en_split_hyphen_when_space(word)
if not word:find("-", nil, true) then
return nil
end
if args.hyphspace then
return "[[" .. word:gsub("%-+", " ") .. "|" .. word .. "]]"
end
if args.nosplithyph then
return "[[" .. word .. "]]"
end
if not args.splithyph then
local space_word = word:gsub("%-+", " ")
if is_english(space_word) then
return "[[" .. space_word .. "|" .. word .. "]]"
end
if is_english(word) then
return "[[" .. word .. "]]"
end
end
return nil
end
local function en_split_apostrophe(word)
local base = word:match("^(.*)'s$")
if base then
return "[[" .. base .. "]][[-'s|'s]]"
end
-- Only treat final apostrophe as possessive if preceded by something that looks like a plural ending in /z/.
-- In particular we don't want to do it for words like [[truckin']].
base = word:match("^(.*[sxz])'$")
if base then
if base:find("s$") then
local sg = singularize(base)
if is_english(sg) then
return "[[" .. sg .. "|" .. base .. "]][[-'|']]"
end
end
return "[[" .. base .. "]][[-'|']]"
end
return "[[" .. word .. "]]"
end
autohead = add_links_to_multiword_term(pagename, {
split_hyphen_when_space = en_split_hyphen_when_space,
split_apostrophe = en_split_apostrophe,
no_split_apostrophe_words = en_no_split_apostrophe_words,
include_hyphen_prefixes = en_include_hyphen_prefixes,
})
end
if not heads[1] then
heads = {{term = autohead}}
else
for _, headobj in ipairs(heads) do
local head = headobj.term
if head:find("^~") then
head = apply_link_modifiers(autohead, head:sub(2), lang)
headobj.term = head
elseif head:find("^[!?]$") then
-- If explicit head= just consists of ! or ?, add it to the end of the default head.
headobj.term = autohead .. head
end
if head == autohead then
track("redundant-head")
end
end
end
-- handle the=/def=
if args.the == "~" then
local newheads = {}
for _, headobj in ipairs(heads) do
local barehead = shallowCopy(headobj)
insert(newheads, barehead)
headobj.term = "the " .. headobj.term
insert(newheads, headobj)
end
heads = newheads
elseif args.the then
local the = require(yesno_module)(args.the)
if the then
for _, headobj in ipairs(heads) do
headobj.term = "the " .. headobj.term
end
end
end
local data = {
lang = lang,
pos_category = poscat,
categories = {},
heads = heads,
user_specified_heads = user_specified_heads,
-- We use our own splitting algorithm so the redundant head cat will be inaccurate.
no_redundant_head_cat = true,
inflections = {},
nomultiwordcat = args.nomultiwordcat,
sort_key = args.sort,
pagename = pagename,
id = args.id,
force_cat_output = force_cat,
}
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local is_suffix = false
if args.suffix or not args.nosuffix and pagename:find("^%-") and not pagename:find("^%-%-") and poscat ~= "suffix forms" then
is_suffix = true
data.pos_category = "suffixes"
local singular_poscat = singularize(poscat)
inscat(singular_poscat .. "-forming suffixes")
insert(data.inflections, {label = singular_poscat .. "-forming suffix"})
end
if pos_func then
pos_func(args, data, is_suffix)
end
local extra_categories = {}
if pagename:find("[Qq]") then
-- Check for q not followed by u. We want to exclude things like [[13q deletion syndrome]] and [[BFOQ]] that
-- don't have a lowercase letter on either side, as well as things like [[& seq.]] and [[acq.]] that are
-- abbreviations for words containing a following u.
--
-- Approximate range of combining diacritics; we want to remove them so the checks below for
-- a lowercase letter next to the q aren't tripped up by diacritics on the letter.
local u300 = u(0x0300)
local u36F = u(0x036F)
local pagename_no_diacritics = ugsub(toNFD(pagename), "[" .. u300 .. "-" .. u36F .. "]", "")
if pagename_no_diacritics:find("[Qq][a-tv-z]") or pagename_no_diacritics:find("[a-z]q[^u.]") or
pagename_no_diacritics:find("[a-z]q$") then
inscat("words containing Q not followed by U")
end
end
-- toNFD performs decomposition, so letters that decompose to an ASCII
-- vowel and a diacritic, such as é, are counted as vowels and do not do not
-- need to be included in the pattern.
if not umatch(ulower(toNFD(pagename)), "[aeiouyæœøəªºαεηιουω]") then
inscat("words spelled wiþūtan vowels")
end
if pagename:find("yre$") then
inscat('words ending in "-yre"')
end
if not pagename:find(" ") and ulen(pagename) >= 25 then
insert(extra_categories, langname .. " " .. cat_word(lang, "Long words"))
end
if pagename:find("^[^aeiou ]*a[^aeiou ]*e[^aeiou ]*i[^aeiou ]*o[^aeiou ]*u[^aeiou ]*$") then
inscat("words that use all vowels in alphabetical order")
end
parse_and_insert_inflection(data, args, "abbr", "abbreviation")
if args.json then
return toJSON(data)
end
return full_headword(data)
.. (extra_categories[1]
and format_categories(extra_categories, lang, args.sort)
or "")
end
local function make_default_comparative(word)
if word == "good" or word == "well" then
return {"better"}
elseif word == "bad" or word == "badly" then
return {"worse"}
elseif word == "far" then
return {"further", "farther"}
else
return {add_suffix(word, "r")}
end
end
local function make_default_superlative(word)
if word == "good" or word == "well" then
return {"best"}
elseif word == "bad" or word == "badly" then
return {"worst"}
elseif word == "far" then
return {"furthest", "farthest"}
else
return {add_suffix(word, "st.superlative")}
end
end
-- This function does the common work between adjectives and adverbs.
local function process_comparative_args(data, args, plpos)
local pagename = data.pagename
local comps = parse_inflection(args, 1)
local sups = parse_inflection(args, "sup")
local outcomps, outsups
if args.componly then
if comps[1] then
error("Can't specify comparatives of comparative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("comparative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "comparative-only " .. plpos))
-- Set to empty list so we don't get any comparatives output, but process superlatives if specified.
outcomps = {}
if not sups[1] then
-- Set to empty list so we don't get any superlatives output unless explicitly given.
outsups = {}
end
elseif args.suponly then
if comps[1] or sups[1] then
error("Can't specify comparatives or superlatives of or superlative-only " .. plpos)
end
insert(data.inflections, {label = glossary_link("superlative") .. " form only"})
insert(data.categories, langname .. " " .. cat_word(lang, "superlative-only " .. plpos))
return
end
-- If the first parameter is ?, then don't show anything, just return.
if comps[1] and comps[1].term == "?" then
if comps[2] then
error("Can't specify additional comparatives along wiþ '?'")
end
if sups[1] then
error("Can't specify superlatives along wiþ '?' for the comparative")
end
return
end
if comps[1] and comps[1].term == "-" then
local hyphencomp = remove(comps, 1) -- Remove the "-" but retain for decorations.
-- Not (generally) comparable; may occasionally have a comparative
if comps[1] then
insert_fixed_inflection(data, "not generally <<comparable>>", hyphencomp)
elseif not sups[1] then
insert_fixed_inflection(data, "not <<comparable>>", hyphencomp)
insert(data.categories, langname .. " " .. cat_word(lang, "uncomparable " .. plpos))
return
else
-- No comparative, but a superlative. insert_inflection() will correctly generate 'no comparative' if we
-- pass in "-" as the value.
outcomps = {hyphencomp}
end
elseif not comps[1] then
comps = {{term = "more"}}
end
if not outcomps then -- not if we set `outcomps` to "-" above or processed a comparative-only term
outcomps = {}
-- Go over each parameter given and create a comparative and superlative form.
for _, compobj in ipairs(comps) do
local comp = compobj.term
if comp == "-" then
error("Comparative of '-' only allowed as first comparative")
end
if comp == "+" then
comp = "+more"
elseif comp == "more" and pagename ~= "many" and pagename ~= "much" then
comp = "+more"
elseif comp == "further" and pagename ~= "far" then
comp = "+further"
elseif comp == "better" and pagename ~= "good" and pagename ~= "well" then
comp = "+better"
elseif comp:find("~") then
comp = comp:gsub("~", replacement_escape(pagename))
end
compobj.origterm = comp
if comp == "+more" then
comp = "more [[" .. pagename .. "]]"
elseif comp == "+further" then
comp = {"further [[" .. pagename .. "]]", "farther [[" .. pagename .. "]]"}
elseif comp == "+better" then
comp = "better [[" .. pagename .. "]]"
elseif comp == "er" then
-- Add -er.
comp = add_suffix(pagename, "r")
elseif comp == "ier" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'ier' comparative unless the term ends wiþ 'y': " .. pagename)
end
comp = pagename:gsub("e?y$", "ier")
elseif comp:find("^%+") then
local special = m_headword_utilities.get_special_indicator(comp, "noerror")
if special then
comp = m_headword_utilities.handle_multiword(pagename, special, make_default_comparative)
end
end
if type(comp) == "table" and not comp[2] then
comp = comp[1]
end
if type(comp) == "table" then
for i = 1, #comp - 1 do
local outobj = shallowCopy(compobj)
outobj.term = comp[i]
insert(outcomps, outobj)
end
compobj.term = comp[#comp]
insert(outcomps, compobj)
else
compobj.term = comp
insert(outcomps, compobj)
end
end
end
if sups[1] and sups[1].term == "-" then
if sups[2] then
error("Can't specify '-' as superlative followed by further values")
end
-- No superlative. insert_inflection() will correctly generate 'no superlative' if we pass in "-" as the value.
outsups = sups
else
if not sups[1] then
sups = {{term = "+"}}
end
end
-- `outsups` will be set if we set `outsups` to "-" above or processed a comparative-only term without superlatives.
if not outsups then
outsups = {}
local function process_sup(sup, special, supobj, compobj)
if special then
sup = m_headword_utilities.handle_multiword(pagename, special, make_default_superlative)
elseif sup == "-" or sup == "+" then
error(("Internal error: Superlative value of '%s' should have been handled earlier"):format(sup))
elseif sup == "+most" then
sup = "most [[" .. pagename .. "]]"
elseif sup == "+furthest" then
sup = {"furthest [[" .. pagename .. "]]", "farthest [[" .. pagename .. "]]"}
elseif sup == "+best" then
sup = "best [[" .. pagename .. "]]"
elseif sup == "est" then
-- Add -est.
sup = add_suffix(pagename, "st.superlative")
elseif sup == "iest" then
if pagename:sub(-1) ~= "y" then
error("Can't specify 'iest' superlative unless the term ends wiþ 'y': " .. pagename)
end
sup = pagename:gsub("e?y$", "iest")
end
if type(sup) == "table" and not sup[2] then
sup = sup[1]
end
if compobj then
supobj = shallowCopy(supobj)
supobj = m_headword_utilities.combine_termobj_decorations(supobj, compobj)
end
if type(sup) == "table" then
for i = 1, #sup - 1 do
local outobj = shallowCopy(supobj)
outobj.term = sup[i]
insert(outsups, outobj)
end
supobj.term = sup[#sup]
insert(outsups, supobj)
else
supobj.term = sup
insert(outsups, supobj)
end
end
for _, supobj in ipairs(sups) do
local sup = supobj.term
if sup == "-" then
error("Superlative of '-' only allowed as first superlative")
end
if sup == "+" then
if not comps[1] then
error("Superlative of '+' can't be specified when there are no comparatives")
end
for _, compobj in ipairs(comps) do
local comp = compobj.origterm
local special
if comp == "+more" then
sup = "+most"
elseif comp == "+further" then
sup = "+furthest"
elseif comp == "+better" then
sup = "+best"
elseif comp == "er" then
sup = "est"
elseif comp == "ier" then
sup = "iest"
else
if comp:find("^%+") then
special = m_headword_utilities.get_special_indicator(comp, "noerror")
end
if not special then
-- If the full comparative was given, then derive the superlative by replacing -er with
-- -est.
if comp:sub(-2) == "er" then
sup = comp:sub(1, -3) .. "est"
else
error(("The superlative cannot be derived automatically from comparative '%s' because it doesn't end in -er"):format(comp))
end
end
end
process_sup(sup, special, supobj, compobj)
end
else
local special = m_headword_utilities.get_special_indicator(sup, "noerror")
-- Do some work here rather than in process_sup() so we don't end up double-processing a term with a '~'
-- in it or a term that happens to be 'most' or similar after substitution of ~ in the comparative.
if not special then
if sup == "most" and pagename ~= "many" and pagename ~= "much" then
sup = "+most"
elseif sup == "furthest" and pagename ~= "far" then
sup = "+furthest"
elseif sup == "best" and pagename ~= "good" and pagename ~= "well" then
sup = "+best"
elseif sup:find("~") then
sup = sup:gsub("~", replacement_escape(pagename))
end
end
process_sup(sup, special, supobj)
end
end
end
insert_inflection(data, outcomps, "<<comparative>>", "comparative")
insert_inflection(data, outsups, "<<superlative>>", "superlative")
end
pos_functions["adjectives"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adjectives")
end,
}
pos_functions["adverbs"] = {
params = {
[1] = list_param,
["comp_qual"] = {list = "comp\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the comparative value",
},
["sup"] = list_param,
["sup_qual"] = {list = "sup\1_qual", allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the superlative value",
},
["componly"] = boolean_param,
["suponly"] = boolean_param,
},
func = function(args, data)
-- Process the comparatives and superlatives.
process_comparative_args(data, args, "adverbs")
end,
}
local function escape(str)
return (str:gsub("\\([:#])", "\\\\%1")
:gsub("[:#]", "\\%0"))
end
local function canonicalize_plural(pl, pagename, pos)
if pl == "+" then
return escape(add_suffix(pagename, "s.plural", pos))
elseif pl == "++" then
return escape(compute_plusplus_s_form(pagename, add_suffix(pagename, "s.plural", pos)))
elseif pl == "*" then
return escape(pagename)
elseif pl == "ies" then
if pagename:sub(-1) == "y" then
return escape(pagename:gsub("e?y$", pl))
end
error("Can't specify 'ies' plural unless the term ends wiþ 'y'.")
elseif pl == "s" or pl == "es" or pl == "'s" then
return escape(pagename .. pl)
end
end
local function do_nouns(args, data, pos)
local pagename = data.pagename
pos = pos or "noun"
local plurals = parse_inflection(args, 1)
local function insert_plurale_tantum_inflections(is_plural_only, originating_label)
if args.sg[1] then
insert_fixed_inflection(data, "normally plural", originating_label)
parse_and_insert_inflection(data, args, "sg", "singular")
elseif is_plural_only then
insert_fixed_inflection(data, "plural only", originating_label)
end
if args.attr[1] then
parse_and_insert_inflection(data, args, "attr", "attributive")
end
end
local function first_pl_term()
return plurals[1] and plurals[1].term or nil
end
if first_pl_term() == "p" then
-- plurale tantum
if plurals[2] then
error("Wiþ plurale tantum nama, can't specify more than one plural")
end
data.genders = {"p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections("plural only", plurals[1])
return
end
local function inscat(cat)
insert(data.categories, langname .. " " .. cat_word(lang, cat))
end
local need_default_plural = pos == "noun"
if first_pl_term() == "sp" then
-- construed as singular or plural
sp = remove(plurals, 1) -- Remove the "sp" but retain it for its decorations.
inscat("naman construed as singular or plural")
data.genders = {"s", "p"} -- this should auto-insert the correct 'pluralia tantum' category
insert_plurale_tantum_inflections(nil, sp)
need_default_plural = false
elseif first_pl_term() == "-" then
-- Untellendlīc noun; may occasionally have a plural
local hyphpl = remove(plurals, 1) -- Remove the "-" but retain for decorations.
inscat("untellendlīc naman")
-- If plural forms were given explicitly, then show "oft"
if plurals[1] then
insert_fixed_inflection(data, "oft <<untellendlīc>>", hyphpl)
else
insert_fixed_inflection(data, "<<untellendlīc>>", hyphpl)
end
need_default_plural = false
elseif first_pl_term() == "#" then
-- oft tellendlīc (e.g., "grilled cheese")
local hashpl = remove(plurals, 1) -- Remove the "#" but retain for decorations.
insert_fixed_inflection(data, "oft <<tellendlīc>>", hashpl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
elseif first_pl_term() == "~" then
-- Mixed tellendlīc/untellendlīc noun, always has a plural
local tildepl = remove(plurals, 1) -- Remove the "~" but retain for decorations.
insert_fixed_inflection(data, "<<tellendlīc>> and <<untellendlīc>>", tildepl)
inscat("untellendlīc naman")
inscat("tellendlīc naman")
-- If no plural was given, add a default one now
if not plurals[1] then
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
end
-- Plural is unknown
if first_pl_term() == "?" then
local questionpl = remove(plurals, 1) -- Remove the "?" but retain for decorations.
-- Not desired; see [[Wiktionary:Tea_room/2021/August#"Plural unknown or uncertain"]]
-- insert_fixed_inflection(data, "manigfeald unġecnāwen oþþe ungewiss", questionpl)
inscat("naman wiþ unġecnāwen oþþe ungewiss manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '?' of unġecnāwen/ungewiss manigfeald")
end
return
end
-- Plural is not attested
if first_pl_term() == "!" then
local exclampl = remove(plurals, 1) -- Remove the "!" but retain for decorations.
insert_fixed_inflection(data, "plural not attested", exclampl)
inscat("naman wiþ unattested manigfealde")
if plurals[1] then
error("Ne cann āmearcian sweotole manigfealde andlang wiþ '!' of gecȳþed manigfeald")
end
return
end
-- If no plural was given, maybe add a default one, otherwise (when "-" was given or proper noun) return.
if not plurals[1] then
if not need_default_plural then
inscat("untellendlīc naman")
return
end
plurals[1] = {term = escape(add_suffix(pagename, "s.plural", pos))}
end
-- There are plural forms to show, so show them.
inscat("tellendlīc naman")
local irregular, indeclinable
for i, pl in ipairs(plurals) do
local canon_pl = canonicalize_plural(pl.term, pagename, pos)
if canon_pl then
pl.term = canon_pl
end
local pl_term = get_link_page(pl.term, lang)
if not (pagename:find(" ") or is_regular_plural(pl_term, pagename)) then
irregular = true
if pl_term == pagename then
indeclinable = true
end
end
end
if irregular then
inscat("naman wiþ fremde manigfealde")
end
if indeclinable then
inscat("undeclīnigendlīce naman")
end
insert_inflection(data, plurals, "plural", "p")
end
-- Return the parameters to be used for nouns and proper nouns. Currently the same.
local noun_params = {
[1] = list_param,
["pl\1qual"] = {list = true, allow_holes = true, replaced_by = false,
instead = "use <l:...> or <q:...> inline modifier on the plural",
},
-- The following four only used for pluralia tantum (1=p)
["sg"] = list_param,
["attr"] = list_param,
}
pos_functions["naman"] = {
params = noun_params,
func = do_naman,
}
pos_functions["agennaman"] = {
params = noun_params,
func = function(args, data)
return do_nouns(args, data, "agennama")
end,
}
local function base_default_verb_forms(verb)
return escape(add_suffix(verb, "s.verb")), escape(add_suffix(verb, "ing")), escape(add_suffix(verb, "d"))
end
local function default_verb_forms(verb)
local full_s_form, full_ing_form, full_ed_form = base_default_verb_forms(verb)
if verb:find(" ") then
local first, rest = verb:match("^(.-)( .*)$")
local first_s_form, first_ing_form, first_ed_form = base_default_verb_forms(first)
return full_s_form, full_ing_form, full_ed_form, first_s_form .. rest, first_ing_form .. rest,
first_ed_form .. rest, first, rest
else
return full_s_form, full_ing_form, full_ed_form, nil, nil, nil, nil, nil
end
end
local function compute_double_last_cons_stem_of_split_verb(verb, ending)
local first, rest = verb:match("^(.-)( .*)$")
if not first then
error("Word '" .. verb .. "' mōt hæfþ rum in sīde tō notienne **")
end
local last_cons = first:match("([bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ])$")
if not last_cons then
error("Fyrest word '" .. first .. "' mōt ende wiþ samod swēgend tō notienne **")
end
return first .. last_cons .. ending .. rest
end
local function check_non_nil_star_form(form, pagename)
if form == nil then
error("Word '" .. pagename .. "' mōt hæfþ rum in sīde tō notienne *, **, *l, *! or *'")
end
return form
end
local function sub_tilde(form, pagename)
if not form then
return nil
end
if form:find("~") then
form = form:gsub("~", replacement_escape(pagename))
end
return form
end
local deprecated_qual_replaced_by_inline_modifier = {
list = true, allow_holes = true, replaced_by = false,
instead = "use an inline modifier <q:...> or <l:...> on the value"
}
pos_functions["verbs"] = {
params = {
[1] = {list = "pres_3sg", disallow_holes = true},
["pres_3sg\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[2] = {list = "pres_ptc", disallow_holes = true},
["pres_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[3] = {list = "past", disallow_holes = true},
["past\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
[4] = {list = "past_ptc", allow_holes = true},
["past_ptc\1_qual"] = deprecated_qual_replaced_by_inline_modifier,
["noautolinkverb"] = boolean_param,
["angle_bracket"] = boolean_param,
},
func = function(args, data)
-- Get parameters
local par1s
local par2s = parse_inflection(args, {2, "pres_ptc"})
local par3s = parse_inflection(args, {3, "past"})
local par4s = parse_inflection(args, {4, "past_ptc"})
local pres_3sgs, pres_ptcs, pasts, past_ptcs
local pagename = data.pagename
------------------------------------------- UTILITY FUNCTIONS #2 ------------------------------------------
-- These functions are used in both in the separate-parameter format and in the override params such as past_ptc2=.
local full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed
local lemma
local function set_lemma_and_default_forms(the_lemma)
lemma = the_lemma
full_default_s, full_default_ing, full_default_ed, split_default_s, split_default_ing, split_default_ed,
lemma_first, lemma_rest = default_verb_forms(the_lemma)
end
local function canonicalize_s_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_s
elseif form == "*" then
return check_non_nil_star_form(split_default_s, lemma)
elseif form == "++" then
return compute_plusplus_s_form(lemma, full_default_s)
elseif form == "**" then
if lemma:find("^[^ ]*[szx] ") then
return compute_double_last_cons_stem_of_split_verb(lemma, "es")
else
return check_non_nil_star_form(split_default_s, lemma)
end
elseif form == "+!" then
return lemma .. "s"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "s" .. lemma_rest
elseif form == "+'" then
return lemma .. "'s"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'s" .. lemma_rest
elseif form == "+l" then
if lemma:find("[szx]$") then
return {{term = full_default_s, l = {"US"}},
{term = compute_plusplus_s_form(lemma, full_default_s), l = {"UK"}}}
else
return compute_plusplus_s_form(lemma, full_default_s)
end
elseif form == "*l" then
if lemma:find("^[^ ]*[szx] ") then
return {{term = check_non_nil_star_form(split_default_s, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "es"), l = {"UK"}}}
else
return check_non_nil_star_form(split_default_s, lemma)
end
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ing_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ing
elseif form == "*" then
return check_non_nil_star_form(split_default_ing, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ing"
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ing")
elseif form == "+!" then
return lemma .. "ing"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ing" .. lemma_rest
elseif form == "+'" then
return lemma .. "'ing"
elseif form == "*'" then
return check_non_nil_star_form(lemma_first) .. "'ing" .. lemma_rest
elseif form == "+l" then
return {{term = full_default_ing, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ing", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ing, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ing"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
local function canonicalize_ed_form(form)
if form == "+" then
error("Internal error: Should not see '+' here")
elseif form == "^" then
return full_default_ed
elseif form == "*" then
return check_non_nil_star_form(split_default_ed, lemma)
elseif form == "++" then
return compute_double_last_cons_stem(lemma) .. "ed"
elseif form == "+!" then
return lemma .. "ed"
elseif form == "*!" then
return check_non_nil_star_form(lemma_first) .. "ed" .. lemma_rest
elseif form == "+'" then
return {{term = lemma .. "'d"}, {term = lemma .. "'ed"}}
elseif form == "*'" then
return {{term = check_non_nil_star_form(lemma_first) .. "'d" .. lemma_rest},
{term = check_non_nil_star_form(lemma_first) .. "'ed" .. lemma_rest}}
elseif form == "**" then
return compute_double_last_cons_stem_of_split_verb(lemma, "ed")
elseif form == "+l" then
return {{term = full_default_ed, l = {"US"}},
{term = compute_double_last_cons_stem(lemma) .. "ed", l = {"UK"}}}
elseif form == "*l" then
return {{term = check_non_nil_star_form(split_default_ed, lemma), l = {"US"}},
{term = compute_double_last_cons_stem_of_split_verb(lemma, "ed"), l = {"UK"}}}
else
return sub_tilde(form, lemma)
end
end
-- FIXME: options should be "+", "*", "++", "**", "+n", "*n", "++n" and "**n", but not "n"
local function canonicalize_en_form(form)
if form == "n" then
track("n4")
return add_suffix(lemma, "n")
end
return canonicalize_ed_form(form)
end
--------------------------------- MAIN PARSING/CONJUGATING CODE --------------------------------
local is_angle_bracket = args.angle_bracket
if is_angle_bracket then
if par2s[1] or par3s[1] or par4s[1] then
error("Can't specify explicit values for 2=, 3= or 4= along wiþ the angle-bracket format")
end
elseif is_angle_bracket == nil and not par2s[1] and not par3s[1] and not par4s[1] and not args[1][2] and
args[1][1] and args[1][1]:find("<") then
if put.term_contains_top_level_html(args[1][1]) then
-- Often, term_contains_top_level_html() returns true on the angle-bracket format, which would
-- make the pcall() below succeed but leave the angle brackets as-is. Check for this and only do the
-- pcall() if term_contains_top_level_html() returns false.
is_angle_bracket = true
else
-- If it's ambiguous whether it's an angle-bracket format or separate params with an inline modifier,
-- try to parse as the latter. If an error occurs, treat as the former.
local ok
ok, par1s = pcall(parse_inflection, args, {1, "pres_3sg"})
if not ok then
par1s = nil
is_angle_bracket = true
end
end
end
if is_angle_bracket then
-------------------------- ANGLE-BRACKET FORMAT --------------------------
-- (0) Expand multiword term with angle brackets just on the first word.
local arg11 = args[1][1]
if arg11:find("^<.*>$") and pagename:find(" ") then
local first, rest = pagename:match("^(.-)( .*)$")
arg11 = first .. arg11 .. rest
end
-- (1) Parse the indicator specs inside of angle brackets.
local function parse_indicator_spec(angle_bracket_spec)
local inside = angle_bracket_spec:match("^<(.*)>$")
assert(inside)
local segments = put.parse_balanced_segment_run(inside, "[", "]")
local comma_separated_groups = put.split_alternating_runs(segments, ",")
if #comma_separated_groups > 4 then
error("Too many comma-separated parts in indicator spec, expected at most 4: " ..
angle_bracket_spec)
end
local function fetch_footnotes(separated_group)
local footnotes
for j = 2, #separated_group - 1, 2 do
if separated_group[j + 1] ~= "" then
error("Extraneous text after bracketed footnotes: '" .. concat(separated_group) .. "'")
end
if not footnotes then
footnotes = {}
end
insert(footnotes, separated_group[j])
end
return footnotes
end
local function fetch_specs(comma_separated_group)
if not comma_separated_group then
return {{term = "+"}}
end
local specs = {}
local colon_separated_groups = put.split_alternating_runs(comma_separated_group, ":")
for _, colon_separated_group in ipairs(colon_separated_groups) do
local form = colon_separated_group[1]
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error("*, **, *l, *! and *' not allowed inside of indicator specs: " .. angle_bracket_spec)
end
if form == "" then
form = "+"
end
local termobj = {
term = form
}
local footnotes = fetch_footnotes(colon_separated_group)
if footnotes then
for _, footnote in ipairs(footnotes) do
m_headword_utilities.add_footnote_to_termobj(termobj, footnote)
end
end
insert(specs, termobj)
end
return specs
end
local s_specs = fetch_specs(comma_separated_groups[1])
local ing_specs = fetch_specs(comma_separated_groups[2])
local ed_specs = fetch_specs(comma_separated_groups[3])
local en_specs = fetch_specs(comma_separated_groups[4])
return {
forms = {},
s_specs = s_specs,
ing_specs = ing_specs,
ed_specs = ed_specs,
en_specs = en_specs,
}
end
local parse_props = {
parse_indicator_spec = parse_indicator_spec,
}
local alternant_multiword_spec = iut.parse_inflected_text(arg11, parse_props)
-- (2) Check for user-specified brackets; remove any links from the lemma, but remember the original
-- form so we can use it below in the 'lemma_linked' form.
-- Check to see if there are brackets in the pre-text or post-text. If so, use the linked lemma (with the
-- verb autolinked unless noautolinkverb is given). Otherwise, use the default headword algorithm.
local function check_bracket(val)
if val:find("%[%[") then
alternant_multiword_spec.saw_bracket = true
end
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
check_bracket(alternant_or_word_spec.before_text)
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
check_bracket(word_spec.before_text)
end
check_bracket(multiword_spec.post_text)
end
end
end
check_bracket(alternant_multiword_spec.post_text)
iut.map_word_specs(alternant_multiword_spec, function(base)
if base.lemma == "" then
base.lemma = pagename
end
base.orig_lemma = base.lemma
base.lemma = remove_links(base.lemma)
if args.noautolinkverb or base.orig_lemma:find("%[%[") then
base.linked_lemma = base.orig_lemma
else
base.linked_lemma = "[[" .. base.orig_lemma .. "]]"
end
end)
-- (3) Conjugate the verbs according to the indicator specs parsed above.
local all_verb_slots = {
lemma = "infinitive",
lemma_linked = "infinitive",
s_form = "3|s|pres",
ing_form = "pres|ptcp",
ed_form = "past",
en_form = "past|ptcp",
}
local function conjugate_verb(base)
local function process_specs(slot, specs, canon_func, default_values, default_already_formobj)
local function insert_termobj_into_slot(termobj)
local formobj = m_headword_utilities.convert_termobj_to_formobj(termobj)
-- If the form is -, don't insert any forms, which will result in there being no overall forms
-- (in fact it will be nil). We check for that down below and substitute a single "-" as the
-- form, which in turn gets turned into special labels like "no present participle".
if formobj.form == "-" then
if formobj.footnotes then
error("Unable to preserve footnotes specified on missing form '-': FIXME: " ..
dump(formobj.footnotes))
end
else
iut.insert_form(base.forms, slot, formobj)
end
end
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
insert_termobj_into_slot(arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
insert_termobj_into_slot(canon)
end
end
end
for _, arg in ipairs(specs) do
if arg.term == "+" then
if default_values then -- will be nil if past tense specified as - and no past ptc given
for _, val in ipairs(default_values) do
val = shallowCopy(val)
if default_already_formobj then
local argformobj = m_headword_utilities.convert_termobj_to_formobj(arg)
val.footnotes = iut.combine_footnotes(val.footnotes, argformobj.footnotes)
iut.insert_form(base.forms, slot, val)
else
m_headword_utilities.combine_termobj_decorations(val, arg)
canonicalize_and_insert(val)
end
end
end
else
canonicalize_and_insert(arg)
end
end
end
set_lemma_and_default_forms(base.lemma)
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
error(("Internal error: '%s' should have already thrown an error"):format(form))
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_specs("s_form", base.s_specs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_specs("ing_form", base.ing_specs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_specs("ed_form", base.ed_specs, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_specs("en_form", base.en_specs, function(arg) return canonicalize_en_form(arg.term) end,
base.forms.ed_form, "default already formobj")
iut.insert_form(base.forms, "lemma", {form = base.lemma})
-- Add linked version of lemma for use in head=. We write this in a general fashion in case
-- there are multiple lemma forms (which isn't possible currently at this level, although it's
-- possible overall using the ((...,...)) notation).
iut.insert_forms(base.forms, "lemma_linked", iut.map_forms(base.forms.lemma, function(form)
if form == base.lemma and base.linked_lemma:find("%[%[") then
return base.linked_lemma
else
return form
end
end))
end
local inflect_props = {
slot_table = all_verb_slots,
inflect_word_spec = conjugate_verb,
}
iut.inflect_multiword_or_alternant_multiword_spec(alternant_multiword_spec, inflect_props)
-- (4) Fetch the forms and put the conjugated lemmas in data.heads if not explicitly given.
local function fetch_termobjs(slot)
local forms = alternant_multiword_spec.forms[slot]
-- See above. This should only occur if the user explicitly used - for a spec.
if not forms or not forms[1] then
return {{term = "-"}}
end
local termobjs = {}
for _, formobj in ipairs(forms) do
insert(termobjs, m_headword_utilities.convert_formobj_to_termobj(formobj))
end
return termobjs
end
pres_3sgs = fetch_termobjs("s_form")
pres_ptcs = fetch_termobjs("ing_form")
pasts = fetch_termobjs("ed_form")
past_ptcs = fetch_termobjs("en_form")
-- Use the "linked" form of the lemma as the head if no head= explicitly given and the user specified
-- brackets in one of the lemmas. Otherwise we use the default headword-linking algorithm.
if not data.user_specified_heads[1] and alternant_multiword_spec.saw_bracket then
data.heads = {}
for _, lemma_obj in ipairs(alternant_multiword_spec.forms.lemma_linked) do
insert(data.heads, m_headword_utilities.convert_formobj_to_termobj(lemma_obj))
end
end
else
-------------------------- SEPARATE-PARAM FORMAT --------------------------
set_lemma_and_default_forms(pagename)
par1s = par1s or parse_inflection(args, {1, "pres_3sg"})
pres_3sgs = {}
pres_ptcs = {}
pasts = {}
past_ptcs = {}
if not par1s[1] then
par1s = {{term = "+"}}
end
if not par2s[1] then
par2s = {{term = "+"}}
end
if not par3s[1] then
par3s = {{term = "+"}}
end
if not par4s[1] then
par4s = {{term = "+"}}
end
local function process_argument(args, dest, canon_func, default_values, default_already_canonicalized)
local function canonicalize_and_insert(arg)
local canon_arg = canon_func(arg)
if type(canon_arg) == "string" then
arg.term = canon_arg
m_headword_utilities.insert_termobj_combining_duplicates(dest, arg)
else
for _, canon in ipairs(canon_arg) do
m_headword_utilities.combine_termobj_decorations(canon, arg)
m_headword_utilities.insert_termobj_combining_duplicates(dest, canon)
end
end
end
for _, arg in ipairs(args) do
if arg.term == "+" then
for _, val in ipairs(default_values) do
val = shallowCopy(val)
m_headword_utilities.combine_termobj_decorations(val, arg)
if default_already_canonicalized then
m_headword_utilities.insert_termobj_combining_duplicates(dest, val)
else
canonicalize_and_insert(val)
end
end
else
canonicalize_and_insert(arg)
end
end
end
local all_part_default_specs = {}
local function process_and_canonicalize_s_form(arg)
local form = arg.term
if form == "+" then
error("Internal error: '+' should have been converted to '^' by now")
end
if form == "^" or form == "++" or form == "+l" or form == "+!" or form == "+'" or
form == "*" or form == "**" or form == "*l" or form == "*!" or form == "*'" then
insert(all_part_default_specs, shallowCopy(arg))
end
return canonicalize_s_form(form)
end
process_argument(par1s, pres_3sgs, process_and_canonicalize_s_form, {{term = "^"}})
if not all_part_default_specs[1] then
all_part_default_specs[1] = {term = "^"}
end
process_argument(par2s, pres_ptcs, function(arg) return canonicalize_ing_form(arg.term) end,
all_part_default_specs)
process_argument(par3s, pasts, function(arg) return canonicalize_ed_form(arg.term) end,
all_part_default_specs)
process_argument(par4s, past_ptcs, function(arg) return canonicalize_en_form(arg.term) end,
pasts, "default already canonicalized")
end
------------------------------------------- INSERT INFLECTIONS ------------------------------------------
insert_inflection(data, pres_3sgs, "þridda had anfeald ānfeald andweard", "s-verb-form")
insert_inflection(data, pres_ptcs, "andweard dǣlnimend", "ing-form")
if deepEquals(pasts, past_ptcs) then
insert_inflection(data, pasts, "ānfeald forþgewiten and forþgewiten dǣlnimend", "ed-form",
"na ānfeald forþgewiten oþþe forþgewiten dǣlnimend")
else
insert_inflection(data, pasts, "ānfeald forþgewiten", "spast")
insert_inflection(data, past_ptcs, "forþgewiten dǣlnimend", "past|part")
end
if pagename:find(" ") then
-- Check for placeholder "it"
local words = split(pagename, " ")
for _, word in ipairs(words) do
if word == "it" or word == "its" or word == "it's" then
insert(data.categories, langname .. ' ' .. cat_word(lang, 'terms with placeholder "it"'))
break
end
end
-- Check for phrasal verbs
local phrasal_adverbs = list_to_set{
-- NOTE: This should only contain common phrasal adverbs, not random words like [[low]],
-- [[adrift]], etc.
"aback",
"about",
"above",
"across",
"after",
"against",
"ahead",
"along",
"apart",
"around",
"as",
"aside",
"at",
"away",
"back",
"before",
"behind",
"below",
"between",
"beyond",
"by",
"down",
"for",
"forth",
"from",
"in",
"into",
"of",
"off",
"on",
"onto",
"out",
"over",
"past",
"round",
"through",
"to",
"together",
"towards",
"under",
"up",
"upon",
"with",
"without",
}
local allowed_non_adverb_words = list_to_set{
"it",
"one",
"oneself",
"someone",
}
local base = pagename
local seen_adverbs = {}
-- Only consider a verb to be phrasal if it consists of a single base verb followed exclusively by either
-- adverbs from `phrasal_adverbs` or placeholder words from `allowed_non_adverb_words`, where at
-- least one following word is from `phrasal_adverbs` (hence [[can it]] is not a phrasal verb).
while true do
local prev, word = base:match("^(.+) (.-)$")
if not prev then
break
end
if phrasal_adverbs[word] then
insert(seen_adverbs, word)
elseif allowed_non_adverb_words[word] then
-- do nothing
else
break
end
base = prev
end
if not base:find(" ") and seen_adverbs[1] then
insert(data.categories, langname .. " " .. cat_word(lang, "phrasal verbs"))
for i = #seen_adverbs, 1, -1 do
insert(data.categories, langname .. ' ' .. cat_word(lang, "phrasal verbs formed with") .. ' "' .. seen_adverbs[i] .. '\"')
end
end
end
end,
}
-----------------------------------------------------------------------------------------
-- Suffix forms --
-----------------------------------------------------------------------------------------
pos_functions["suffix forms"] = {
params = {
[1] = {required = true, list = true, disallow_holes = true},
},
func = function(args, data, is_suffix)
local suffix_type = {}
for _, typ in ipairs(args[1]) do
insert(suffix_type, typ .. "-forming suffix")
end
insert(data.inflections, {label = "non-lemma form of " .. m_table.serialCommaJoin(suffix_type, {conj = "or"})})
end,
}
return export
7z87v7bded7ojeuopcxgwkrmozglh1b
Bysen:ang-decl-noun-a-n
10
8057
55011
54867
2026-09-30T18:37:36Z
Deadend0914
7211
55011
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{ang-decl-noun<!--
-->|type=strang ''a''-stefn<!--
-->|1={{{nomsg|{{{1}}}}}}<!--
-->|3={{{nomsg|{{{1}}}}}}<!--
-->|5={{{1}}}{{#if:{{{vowel|}}}||e}}s<!--
-->|7={{{datsg|{{{1}}}{{#if:{{{vowel|}}}||e}}}}}<!--
-->|2={{#if:{{{short|}}}|{{{2|{{{1}}}}}}{{#if:{{{vowel|}}}||u}}|{{{nomsg|{{{2|{{{1}}}}}}}}}}}<!--
-->|4={{#if:{{{short|}}}|{{{2|{{{1}}}}}}{{#if:{{{vowel|}}}||u}}|{{{nomsg|{{{2|{{{1}}}}}}}}}}}<!--
-->|6={{{2|{{{1}}}}}}{{#if:{{{vowel|}}}|n}}a<!--
-->|8={{{2|{{{1}}}}}}{{#if:{{{vowel|}}}||u}}m{{#if:{{{vowel|}}}|,{{{2|{{{1}}}}}}um}}<!--
-->|num={{{num|}}}<!--
-->|title={{{title|}}}<!--
-->}}<!--
-->{{cln|ang|neuter a-stem nouns}}<!--
--><noinclude>{{documentation}}</noinclude>
9tq2txa2h1alrtegp0dr2erc87sz4ef
food
0
8071
55035
54888
2026-09-30T19:34:08Z
Deadend0914
7211
/* Nama */
55035
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /fuːd/
===Wordstǣr===
* Of Middelenglisce ''[[fode]]'', of Englisce ''[[fōda]]''
===Nama===
{{en-noun|-|s}}
# [[fōda]]
[[Flocc: Nīwenglisc nama]]
[[Flocc: Nīwenglisc nama ofgangen of Middelenglisce]]
nnbsymtd8eg38fyizuybucu41wi3o6s
55038
55035
2026-09-30T19:36:35Z
Deadend0914
7211
/* Wordstǣr */
55038
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /fuːd/
===Wordstǣr===
Of {{inh|en|enm|fode}}, of {{inh|en|ang|fōda}}, of {{inh|en|gmw-pro|*fōdō}}, of {{inh|en|gem-pro|*fōdô}}, of {{der|en|ine-pro|*peh₂-|}}.
===Nama===
{{en-noun|-|s}}
# [[fōda]]
[[Flocc: Nīwenglisc nama]]
[[Flocc: Nīwenglisc nama ofgangen of Middelenglisce]]
b0i8shnrrscgtiflpv9xju1ofvhov0n
55039
55038
2026-09-30T19:36:53Z
Deadend0914
7211
55039
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /fuːd/
===Wordstǣr===
Of {{inh|en|enm|fode}}, of {{inh|en|ang|fōda}}, of {{inh|en|gmw-pro|*fōdō}}, of {{inh|en|gem-pro|*fōdô}}, of {{der|en|ine-pro|*peh₂-|}}.
===Nama===
{{en-noun|-|s}}
# [[fōda]]
9675arrxjcklda8hmimhvj00oir336m
Module:sprǣcnaman
828
8074
54958
54957
2026-09-30T17:14:19Z
Deadend0914
7211
54958
Scribunto
text/plain
--[=[
Rēgister of sprǣcnaman (language names) for the Old English Wiktionary.
This is the single place where the Old English name of a language is entered. Everything that
displays a language name on this wiki reads it from here:
* [[Bysen:sprǣc]] (the ==L2== heading template) is a thin wrapper around sprǣc() below.
* [[Module:etymology]] and [[Module:etymology/templates]] use get_name() for headings and
category names, and get_of() for the "of"-form, because Old English uses "of" + genitive
when an ancestor or source language is mentioned, e.g. "Of Englisce and".
Names are entered without leading or trailing spaces. get_of() applies the following rules in
order:
1. an explicit entry in `of_names` (for names that follow no rule);
2. `-isc` + "e" (Englisc -> Englisce, Nīwenglisc -> Nīwenglisce, Þēodisc -> Þēodisce);
3. otherwise the nominative name is returned unchanged and the miss is tracked in
[[Wiktionary:Tracking/sprǣcnaman/no of-form]] so that an entry can be supplied.
Codes with no name at all display "?" (as [[Bysen:sprǣc]] always has) and are tracked in
[[Wiktionary:Tracking/sprǣc/unknown code]], see [[Special:WhatLinksHere/Wiktionary:Tracking/sprǣc/unknown code]].
]=]
local export = {}
local names = {
aa = "Cafarisc",
ab = "Apscwisc",
af = "Affricanisc",
ak = "Ækanisc",
ang = "Englisc",
bn = "Bengalisc",
ca = "Catalānisc",
cs = "Bæmisc",
da = "Denisc",
de = "Þēodisc",
el = "Crēacisc",
en = "Nīwenglisc",
eo = "Esperantisc",
es = "Spēonisc",
et = "Eotolisc",
eu = "Wascisc",
fa = "Persisc",
fi = "Finnisc",
fo = "Faerisc",
fr = "Frencisc",
fra = "Frencisc",
frk = "Frencisc",
frp = "Profencalisc",
ga = "Īrisc",
gem = "Germanisc",
["gem-pro"] = "Ealdoric Germanisc",
gl = "Galizisc",
gn = "Awaisc",
["gmq-pro"] = "Ealdoric Noren",
gmw = "Westerngermanisc",
["gmw-pro"] = "Ealdoric Westerngermanisc",
goh = "Ealdhēahþēodisc",
got = "Gotisc",
gv = "Manisc",
haw = "Hawaiisc",
hi = "Hindisc",
hu = "Ungrisc",
id = "Indonesisc",
io = "Idoisc",
is = "Īslendisc",
it = "Italisc",
iu = "Inuctitutisc",
la = "Lǣden",
li = "Limburgisc",
lt = "Litwisc",
lv = "Lettisc",
mul = "Þēodeorþe",
nap = "Neapolitisc",
nl = "Niðerlendisc",
no = "Norþwegisc",
non = "Eald Noren",
ovd = "Ælfdælisc",
pl = "Polisc",
pt = "Portugisc",
qu = "Kescwisc",
ro = "Rumǣnisc",
ru = "Rusisc",
sa = "Sanscritisc",
sco = "Niderscyttisc",
sq = "Albānisc",
sv = "Swēonisc",
tpi = "Tok Pisin",
vo = "Volapük",
xpi = "Pihtisc"
}
--[=[
Codes that are needed in etymology lines (ancestors and source languages) but that have no
name yet. They currently display their '''English''' name (via [[Module:languages]]) and should
be given an Old English name here. The forms below are suggestions only and are deliberately
commented out until they have been checked against a source.
-- gem-pro = "?", -- Proto-Germanic
-- gmw-pro = "?", -- Proto-West Germanic
-- ine-pro = "?", -- Proto-Indo-European
-- enm = "Middelenglisc", -- Middle English
-- gmh = "Middelhēahþēodisc", -- Middle High German
-- odt = "Ealdniðerlendisc", -- Old Dutch
-- dum = "Middelniðerlendisc", -- Middle Dutch
-- ofs = "?", -- Old Frisian
-- osx = "Ealdseaxisc", -- Old Saxon
-- gml = "Middelniðersaxisc", -- Middle Low German
-- cel-pro = "?", -- Proto-Celtic
-- gmq-pro = "?", -- Proto-North Germanic
]=]
--[=[
Explicit "of"-forms, for names that the `-isc` rule cannot handle. Add an entry whenever
[[Wiktionary:Tracking/sprǣcnaman/no of-form]] lists a code that needs one. Suggestions
(commented out until checked against a source):
-- la = "Lǣdenes", -- Latin
-- non = "Eald Norðrena", -- Old Norse
-- mul = "Þēodeorðan", -- Translingual
-- vo = "Volapük", -- Volapük (no ending to inflect)
-- tpi = "Tok Pisin", -- Tok Pisin (no ending to inflect)
]=]
local of_names = {}
-- Spellings that may be passed to {{sprǣc}} or found in a ==L2== heading, mapped to their code.
local aliases = {
["Ænglisc"] = "ang",
}
-- Codes with no name display "?" and are tracked, so that the gaps are visible.
local function track(key)
local ok, track_module = pcall(require, "Module:debug/track")
if ok then
track_module(key)
end
end
function export.track_missing_name(code)
code = tostring(code)
if code == "" then
code = "(empty)"
end
track("sprǣc/unknown code/" .. code)
end
local function track_no_of_form(code)
track("sprǣcnaman/no of-form/" .. tostring(code))
end
-- Accept either a language code or a language object (from [[Module:languages]]).
local function code_candidates(x)
if type(x) ~= "table" then
return { x }
end
local codes = {}
local code = x.getCode and x:getCode()
if code then
codes[#codes + 1] = code
end
local full_code = x.getFullCode and x:getFullCode()
if full_code and full_code ~= code then
codes[#codes + 1] = full_code
end
return codes
end
--[==[
Return the Old English name of the given language (code or language object), or nil if the
language has no name yet. Used for ==L2== headings and for category names.
]==]
function export.get_name(x)
for _, code in ipairs(code_candidates(x)) do
if names[code] then
return names[code]
end
end
return nil
end
--[==[
Return the "of"-form of the given language (code or language object), or nil if the language
has no name yet. Used wherever Old English needs "of" + genitive, e.g. "Of Englisce and".
Names that follow no rule fall back to the nominative and are tracked.
]==]
function export.get_of(x)
for _, code in ipairs(code_candidates(x)) do
if names[code] then
return export.get_of_for_code(code)
end
end
return nil
end
function export.get_of_for_code(code)
local name = names[code]
if not name then
return nil
end
if of_names[code] then
return of_names[code]
end
if name:sub(-3) == "isc" then
return name .. "e"
end
track_no_of_form(code)
return name
end
local code_of_name, lower_code_of_name
local function make_reverse_maps()
if code_of_name then
return
end
code_of_name, lower_code_of_name = {}, {}
for alias, code in pairs(aliases) do
code_of_name[alias] = code
lower_code_of_name[mw.ustring.lower(alias)] = code
end
for code, name in pairs(names) do
code_of_name[name] = code_of_name[name] or code
local lower = mw.ustring.lower(name)
lower_code_of_name[lower] = lower_code_of_name[lower] or code
end
end
--[==[
Return the code of the language with the given Old English name, or nil. Used by
[[Module:etymology/templates]] to match an ==L2== heading back to its language.
]==]
function export.get_code(name)
if type(name) ~= "string" then
return nil
end
name = mw.text.trim(name)
if name == "" then
return nil
end
make_reverse_maps()
return code_of_name[name] or lower_code_of_name[mw.ustring.lower(name)]
end
--[==[
Entry point for [[Bysen:sprǣc]]: {{sprǣc|CODE}} gives the name, {{sprǣc|CODE|of}} the
"of"-form. A code with no name gives "?" and is tracked, exactly as the template always has.
]==]
export["sprǣc"] = function(frame)
local args = frame and frame.args or {}
local code = mw.text.trim(args[1] or "")
local mode = mw.text.trim(args[2] or "")
if code == "" then
export.track_missing_name("")
return "?"
end
local name
if mode == "of" or mode == "gen" or mode == "genitive" then
name = export.get_of(code)
else
name = export.get_name(code)
if not name then
-- The argument may be a name rather than a code (e.g. "Englisc").
local matched_code = export.get_code(code)
name = matched_code and names[matched_code] or nil
end
end
if not name then
export.track_missing_name(code)
return "?"
end
return name
end
--[==[
Entry point for templates that need the "of"-form only:
{{#invoke:sprǣcnaman|of|CODE}}.
]==]
function export.of(frame)
local args = frame and frame.args or {}
local code = mw.text.trim(args[1] or "")
if code == "" then
export.track_missing_name("")
return "?"
end
local name = export.get_of(code)
if not name then
export.track_missing_name(code)
return "?"
end
return name
end
-- The tables are exposed for tests and maintenance scripts; use the getters elsewhere.
export.names = names
export.of_names = of_names
return export
1h5b29x7uq35jufstfrux1s78efp7zz
54959
54958
2026-09-30T17:17:37Z
Deadend0914
7211
54959
Scribunto
text/plain
--[=[
Rēgister of sprǣcnaman (language names) for the Old English Wiktionary.
This is the single place where the Old English name of a language is entered. Everything that
displays a language name on this wiki reads it from here:
* [[Bysen:sprǣc]] (the ==L2== heading template) is a thin wrapper around sprǣc() below.
* [[Module:etymology]] and [[Module:etymology/templates]] use get_name() for headings and
category names, and get_of() for the "of"-form, because Old English uses "of" + genitive
when an ancestor or source language is mentioned, e.g. "Of Englisce and".
Names are entered without leading or trailing spaces. get_of() applies the following rules in
order:
1. an explicit entry in `of_names` (for names that follow no rule);
2. `-isc` + "e" (Englisc -> Englisce, Nīwenglisc -> Nīwenglisce, Þēodisc -> Þēodisce);
3. otherwise the nominative name is returned unchanged and the miss is tracked in
[[Wiktionary:Tracking/sprǣcnaman/no of-form]] so that an entry can be supplied.
Codes with no name at all display "?" (as [[Bysen:sprǣc]] always has) and are tracked in
[[Wiktionary:Tracking/sprǣc/unknown code]], see [[Special:WhatLinksHere/Wiktionary:Tracking/sprǣc/unknown code]].
]=]
local export = {}
local names = {
aa = "Cafarisc",
ab = "Apscwisc",
af = "Affricanisc",
ak = "Ækanisc",
ang = "Englisc",
bn = "Bengalisc",
ca = "Catalānisc",
cs = "Bæmisc",
da = "Denisc",
de = "Þēodisc",
el = "Crēacisc",
en = "Nīwenglisc",
eo = "Esperantisc",
es = "Spēonisc",
et = "Eotolisc",
eu = "Wascisc",
fa = "Persisc",
fi = "Finnisc",
fo = "Faerisc",
fr = "Frencisc",
fra = "Frencisc",
frk = "Frencisc",
frp = "Profencalisc",
ga = "Īrisc",
gem = "Germanisc",
["gem-pro"] = "Ealdoric Germanisc",
gl = "Galizisc",
gn = "Awaisc",
["gmq-pro"] = "Ealdoric Noren",
gmw = "Westerngermanisc",
["gmw-pro"] = "Ealdoric Westerngermanisc",
goh = "Ealdhēahþēodisc",
got = "Gotisc",
gv = "Manisc",
haw = "Hawaiisc",
hi = "Hindisc",
hu = "Ungrisc",
id = "Indonesisc",
io = "Idoisc",
ine = "Indoeuropisc",
["ine-pro"] = "Ealdoric Indoeuropisc",
is = "Īslendisc",
it = "Italisc",
iu = "Inuctitutisc",
la = "Lǣden",
li = "Limburgisc",
lt = "Litwisc",
lv = "Lettisc",
mul = "Þēodeorþe",
nap = "Neapolitisc",
nl = "Niðerlendisc",
no = "Norþwegisc",
non = "Eald Noren",
ovd = "Ælfdælisc",
pl = "Polisc",
pt = "Portugisc",
qu = "Kescwisc",
ro = "Rumǣnisc",
ru = "Rusisc",
sa = "Sanscritisc",
sco = "Niderscyttisc",
sq = "Albānisc",
sv = "Swēonisc",
tpi = "Tok Pisin",
vo = "Volapük",
xpi = "Pihtisc"
}
--[=[
Codes that are needed in etymology lines (ancestors and source languages) but that have no
name yet. They currently display their '''English''' name (via [[Module:languages]]) and should
be given an Old English name here. The forms below are suggestions only and are deliberately
commented out until they have been checked against a source.
-- gem-pro = "?", -- Proto-Germanic
-- gmw-pro = "?", -- Proto-West Germanic
-- ine-pro = "?", -- Proto-Indo-European
-- enm = "Middelenglisc", -- Middle English
-- gmh = "Middelhēahþēodisc", -- Middle High German
-- odt = "Ealdniðerlendisc", -- Old Dutch
-- dum = "Middelniðerlendisc", -- Middle Dutch
-- ofs = "?", -- Old Frisian
-- osx = "Ealdseaxisc", -- Old Saxon
-- gml = "Middelniðersaxisc", -- Middle Low German
-- cel-pro = "?", -- Proto-Celtic
-- gmq-pro = "?", -- Proto-North Germanic
]=]
--[=[
Explicit "of"-forms, for names that the `-isc` rule cannot handle. Add an entry whenever
[[Wiktionary:Tracking/sprǣcnaman/no of-form]] lists a code that needs one. Suggestions
(commented out until checked against a source):
-- la = "Lǣdenes", -- Latin
-- non = "Eald Norðrena", -- Old Norse
-- mul = "Þēodeorðan", -- Translingual
-- vo = "Volapük", -- Volapük (no ending to inflect)
-- tpi = "Tok Pisin", -- Tok Pisin (no ending to inflect)
]=]
local of_names = {}
-- Spellings that may be passed to {{sprǣc}} or found in a ==L2== heading, mapped to their code.
local aliases = {
["Ænglisc"] = "ang",
}
-- Codes with no name display "?" and are tracked, so that the gaps are visible.
local function track(key)
local ok, track_module = pcall(require, "Module:debug/track")
if ok then
track_module(key)
end
end
function export.track_missing_name(code)
code = tostring(code)
if code == "" then
code = "(empty)"
end
track("sprǣc/unknown code/" .. code)
end
local function track_no_of_form(code)
track("sprǣcnaman/no of-form/" .. tostring(code))
end
-- Accept either a language code or a language object (from [[Module:languages]]).
local function code_candidates(x)
if type(x) ~= "table" then
return { x }
end
local codes = {}
local code = x.getCode and x:getCode()
if code then
codes[#codes + 1] = code
end
local full_code = x.getFullCode and x:getFullCode()
if full_code and full_code ~= code then
codes[#codes + 1] = full_code
end
return codes
end
--[==[
Return the Old English name of the given language (code or language object), or nil if the
language has no name yet. Used for ==L2== headings and for category names.
]==]
function export.get_name(x)
for _, code in ipairs(code_candidates(x)) do
if names[code] then
return names[code]
end
end
return nil
end
--[==[
Return the "of"-form of the given language (code or language object), or nil if the language
has no name yet. Used wherever Old English needs "of" + genitive, e.g. "Of Englisce and".
Names that follow no rule fall back to the nominative and are tracked.
]==]
function export.get_of(x)
for _, code in ipairs(code_candidates(x)) do
if names[code] then
return export.get_of_for_code(code)
end
end
return nil
end
function export.get_of_for_code(code)
local name = names[code]
if not name then
return nil
end
if of_names[code] then
return of_names[code]
end
if name:sub(-3) == "isc" then
return name .. "e"
end
track_no_of_form(code)
return name
end
local code_of_name, lower_code_of_name
local function make_reverse_maps()
if code_of_name then
return
end
code_of_name, lower_code_of_name = {}, {}
for alias, code in pairs(aliases) do
code_of_name[alias] = code
lower_code_of_name[mw.ustring.lower(alias)] = code
end
for code, name in pairs(names) do
code_of_name[name] = code_of_name[name] or code
local lower = mw.ustring.lower(name)
lower_code_of_name[lower] = lower_code_of_name[lower] or code
end
end
--[==[
Return the code of the language with the given Old English name, or nil. Used by
[[Module:etymology/templates]] to match an ==L2== heading back to its language.
]==]
function export.get_code(name)
if type(name) ~= "string" then
return nil
end
name = mw.text.trim(name)
if name == "" then
return nil
end
make_reverse_maps()
return code_of_name[name] or lower_code_of_name[mw.ustring.lower(name)]
end
--[==[
Entry point for [[Bysen:sprǣc]]: {{sprǣc|CODE}} gives the name, {{sprǣc|CODE|of}} the
"of"-form. A code with no name gives "?" and is tracked, exactly as the template always has.
]==]
export["sprǣc"] = function(frame)
local args = frame and frame.args or {}
local code = mw.text.trim(args[1] or "")
local mode = mw.text.trim(args[2] or "")
if code == "" then
export.track_missing_name("")
return "?"
end
local name
if mode == "of" or mode == "gen" or mode == "genitive" then
name = export.get_of(code)
else
name = export.get_name(code)
if not name then
-- The argument may be a name rather than a code (e.g. "Englisc").
local matched_code = export.get_code(code)
name = matched_code and names[matched_code] or nil
end
end
if not name then
export.track_missing_name(code)
return "?"
end
return name
end
--[==[
Entry point for templates that need the "of"-form only:
{{#invoke:sprǣcnaman|of|CODE}}.
]==]
function export.of(frame)
local args = frame and frame.args or {}
local code = mw.text.trim(args[1] or "")
if code == "" then
export.track_missing_name("")
return "?"
end
local name = export.get_of(code)
if not name then
export.track_missing_name(code)
return "?"
end
return name
end
-- The tables are exposed for tests and maintenance scripts; use the getters elsewhere.
export.names = names
export.of_names = of_names
return export
chm0sq5y4oq13xd0n7z7ah449j2t2qs
55040
54959
2026-09-30T19:37:16Z
Deadend0914
7211
55040
Scribunto
text/plain
--[=[
Rēgister of sprǣcnaman (language names) for the Old English Wiktionary.
This is the single place where the Old English name of a language is entered. Everything that
displays a language name on this wiki reads it from here:
* [[Bysen:sprǣc]] (the ==L2== heading template) is a thin wrapper around sprǣc() below.
* [[Module:etymology]] and [[Module:etymology/templates]] use get_name() for headings and
category names, and get_of() for the "of"-form, because Old English uses "of" + genitive
when an ancestor or source language is mentioned, e.g. "Of Englisce and".
Names are entered without leading or trailing spaces. get_of() applies the following rules in
order:
1. an explicit entry in `of_names` (for names that follow no rule);
2. `-isc` + "e" (Englisc -> Englisce, Nīwenglisc -> Nīwenglisce, Þēodisc -> Þēodisce);
3. otherwise the nominative name is returned unchanged and the miss is tracked in
[[Wiktionary:Tracking/sprǣcnaman/no of-form]] so that an entry can be supplied.
Codes with no name at all display "?" (as [[Bysen:sprǣc]] always has) and are tracked in
[[Wiktionary:Tracking/sprǣc/unknown code]], see [[Special:WhatLinksHere/Wiktionary:Tracking/sprǣc/unknown code]].
]=]
local export = {}
local names = {
aa = "Cafarisc",
ab = "Apscwisc",
af = "Affricanisc",
ak = "Ækanisc",
ang = "Englisc",
bn = "Bengalisc",
ca = "Catalānisc",
cs = "Bæmisc",
da = "Denisc",
de = "Þēodisc",
el = "Crēacisc",
en = "Nīwenglisc",
enm = "Middelenglisc",
eo = "Esperantisc",
es = "Spēonisc",
et = "Eotolisc",
eu = "Wascisc",
fa = "Persisc",
fi = "Finnisc",
fo = "Faerisc",
fr = "Frencisc",
fra = "Frencisc",
frk = "Frencisc",
frp = "Profencalisc",
ga = "Īrisc",
gem = "Germanisc",
["gem-pro"] = "Ealdoric Germanisc",
gl = "Galizisc",
gn = "Awaisc",
["gmq-pro"] = "Ealdoric Noren",
gmw = "Westerngermanisc",
["gmw-pro"] = "Ealdoric Westerngermanisc",
goh = "Ealdhēahþēodisc",
got = "Gotisc",
gv = "Manisc",
haw = "Hawaiisc",
hi = "Hindisc",
hu = "Ungrisc",
id = "Indonesisc",
io = "Idoisc",
ine = "Indoeuropisc",
["ine-pro"] = "Ealdoric Indoeuropisc",
is = "Īslendisc",
it = "Italisc",
iu = "Inuctitutisc",
la = "Lǣden",
li = "Limburgisc",
lt = "Litwisc",
lv = "Lettisc",
mul = "Þēodeorþe",
nap = "Neapolitisc",
nl = "Niðerlendisc",
no = "Norþwegisc",
non = "Eald Noren",
ovd = "Ælfdælisc",
pl = "Polisc",
pt = "Portugisc",
qu = "Kescwisc",
ro = "Rumǣnisc",
ru = "Rusisc",
sa = "Sanscritisc",
sco = "Niderscyttisc",
sq = "Albānisc",
sv = "Swēonisc",
tpi = "Tok Pisin",
vo = "Volapük",
xpi = "Pihtisc"
}
--[=[
Codes that are needed in etymology lines (ancestors and source languages) but that have no
name yet. They currently display their '''English''' name (via [[Module:languages]]) and should
be given an Old English name here. The forms below are suggestions only and are deliberately
commented out until they have been checked against a source.
-- gem-pro = "?", -- Proto-Germanic
-- gmw-pro = "?", -- Proto-West Germanic
-- ine-pro = "?", -- Proto-Indo-European
-- enm = "Middelenglisc", -- Middle English
-- gmh = "Middelhēahþēodisc", -- Middle High German
-- odt = "Ealdniðerlendisc", -- Old Dutch
-- dum = "Middelniðerlendisc", -- Middle Dutch
-- ofs = "?", -- Old Frisian
-- osx = "Ealdseaxisc", -- Old Saxon
-- gml = "Middelniðersaxisc", -- Middle Low German
-- cel-pro = "?", -- Proto-Celtic
-- gmq-pro = "?", -- Proto-North Germanic
]=]
--[=[
Explicit "of"-forms, for names that the `-isc` rule cannot handle. Add an entry whenever
[[Wiktionary:Tracking/sprǣcnaman/no of-form]] lists a code that needs one. Suggestions
(commented out until checked against a source):
-- la = "Lǣdenes", -- Latin
-- non = "Eald Norðrena", -- Old Norse
-- mul = "Þēodeorðan", -- Translingual
-- vo = "Volapük", -- Volapük (no ending to inflect)
-- tpi = "Tok Pisin", -- Tok Pisin (no ending to inflect)
]=]
local of_names = {}
-- Spellings that may be passed to {{sprǣc}} or found in a ==L2== heading, mapped to their code.
local aliases = {
["Ænglisc"] = "ang",
}
-- Codes with no name display "?" and are tracked, so that the gaps are visible.
local function track(key)
local ok, track_module = pcall(require, "Module:debug/track")
if ok then
track_module(key)
end
end
function export.track_missing_name(code)
code = tostring(code)
if code == "" then
code = "(empty)"
end
track("sprǣc/unknown code/" .. code)
end
local function track_no_of_form(code)
track("sprǣcnaman/no of-form/" .. tostring(code))
end
-- Accept either a language code or a language object (from [[Module:languages]]).
local function code_candidates(x)
if type(x) ~= "table" then
return { x }
end
local codes = {}
local code = x.getCode and x:getCode()
if code then
codes[#codes + 1] = code
end
local full_code = x.getFullCode and x:getFullCode()
if full_code and full_code ~= code then
codes[#codes + 1] = full_code
end
return codes
end
--[==[
Return the Old English name of the given language (code or language object), or nil if the
language has no name yet. Used for ==L2== headings and for category names.
]==]
function export.get_name(x)
for _, code in ipairs(code_candidates(x)) do
if names[code] then
return names[code]
end
end
return nil
end
--[==[
Return the "of"-form of the given language (code or language object), or nil if the language
has no name yet. Used wherever Old English needs "of" + genitive, e.g. "Of Englisce and".
Names that follow no rule fall back to the nominative and are tracked.
]==]
function export.get_of(x)
for _, code in ipairs(code_candidates(x)) do
if names[code] then
return export.get_of_for_code(code)
end
end
return nil
end
function export.get_of_for_code(code)
local name = names[code]
if not name then
return nil
end
if of_names[code] then
return of_names[code]
end
if name:sub(-3) == "isc" then
return name .. "e"
end
track_no_of_form(code)
return name
end
local code_of_name, lower_code_of_name
local function make_reverse_maps()
if code_of_name then
return
end
code_of_name, lower_code_of_name = {}, {}
for alias, code in pairs(aliases) do
code_of_name[alias] = code
lower_code_of_name[mw.ustring.lower(alias)] = code
end
for code, name in pairs(names) do
code_of_name[name] = code_of_name[name] or code
local lower = mw.ustring.lower(name)
lower_code_of_name[lower] = lower_code_of_name[lower] or code
end
end
--[==[
Return the code of the language with the given Old English name, or nil. Used by
[[Module:etymology/templates]] to match an ==L2== heading back to its language.
]==]
function export.get_code(name)
if type(name) ~= "string" then
return nil
end
name = mw.text.trim(name)
if name == "" then
return nil
end
make_reverse_maps()
return code_of_name[name] or lower_code_of_name[mw.ustring.lower(name)]
end
--[==[
Entry point for [[Bysen:sprǣc]]: {{sprǣc|CODE}} gives the name, {{sprǣc|CODE|of}} the
"of"-form. A code with no name gives "?" and is tracked, exactly as the template always has.
]==]
export["sprǣc"] = function(frame)
local args = frame and frame.args or {}
local code = mw.text.trim(args[1] or "")
local mode = mw.text.trim(args[2] or "")
if code == "" then
export.track_missing_name("")
return "?"
end
local name
if mode == "of" or mode == "gen" or mode == "genitive" then
name = export.get_of(code)
else
name = export.get_name(code)
if not name then
-- The argument may be a name rather than a code (e.g. "Englisc").
local matched_code = export.get_code(code)
name = matched_code and names[matched_code] or nil
end
end
if not name then
export.track_missing_name(code)
return "?"
end
return name
end
--[==[
Entry point for templates that need the "of"-form only:
{{#invoke:sprǣcnaman|of|CODE}}.
]==]
function export.of(frame)
local args = frame and frame.args or {}
local code = mw.text.trim(args[1] or "")
if code == "" then
export.track_missing_name("")
return "?"
end
local name = export.get_of(code)
if not name then
export.track_missing_name(code)
return "?"
end
return name
end
-- The tables are exposed for tests and maintenance scripts; use the getters elsewhere.
export.names = names
export.of_names = of_names
return export
g4edz5mk1jj7gemx5f3pv3aglvc4taq
Bysen:inh+
10
8076
55031
54904
2026-09-30T19:29:44Z
Deadend0914
7211
55031
wikitext
text/x-wiki
<includeonly>{{#invoke:Wordstǣr|inh_plus|of=Of}}</includeonly><noinclude>{{Documentation}}</noinclude>
94fiuxe03agab16ol9gqbfvwhez5zh7
Module:floccnaman
828
8082
55005
54944
2026-09-30T18:16:50Z
Deadend0914
7211
55005
Scribunto
text/plain
--[=[
Floccnaman (category names) for the Old English Wiktionary.
Every category that a reader sees on an entry page is built as "<sprǣc> <word>" or
"<sprǣc> <word> <word>", exactly like the categories that already exist by hand on this wiki
("Englisc nama", "Nīwenglisc fégung", "Eald Noren werlic nama"), so only the words need
translating, never the word order.
The modules that build such categories ([[Module:headword]], [[Module:headword utilities]],
[[Module:form of]], [[Module:gender and number]], [[Module:affix]], [[Module:etymology]],
[[Module:etymology/specialized]], [[Module:ang-headword]]) ask this module for the Old English
wording first and keep the English wording if it is not here yet. Every missing wording is
tracked in [[Wiktionary:Tracking/flocc/untranslated/<wording>]], so the list of what still needs
to be added is always up to date:
[[Special:WhatLinksHere/Wiktionary:Tracking/flocc/untranslated]]
Words are entered without leading or trailing spaces, in the singular for parts of speech
("nama", not "naman"), because that is what the pre-existing categories use. The plural forms
used for the lemma categories are entered separately ("lemmas", "non-lemma forms", ...).
Only categories a reader sees on an entry page are translated here. Maintenance and tracking
categories (sortkeys, script codes, requests, pages with ...), the categories of the template
namespace and the ones produced by MediaWiki itself stay English on purpose.
]=]
local export = {}
--[=[
English wording -> Old English wording.
The words in this first group are already used by categories and template names that exist on
this wiki, so their spelling is established ("Flocc:Nama", "Flocc:Bīnaman", "Flocc:Nīwenglisc
fégung", "Bysen:Tōgeīecendlic-Tabule", "Cynn 1 strang forþgewiten dǣlnimend"), and the gender
words match the existing "Eald Noren werlic nama" style.
]=]
local words = {
-- Parts of speech.
["nouns"] = "naman",
["verbs"] = "word",
["adjectives"] = "tōgeīecendlice word",
["adverbs"] = "bīword",
["conjunctions"] = "fégunga",
["pronouns"] = "bīnaman",
["prepositions"] = "foresetnessa",
["participles"] = "dǣlnimend",
["past participles"] = "forþgewiten dǣlnimend",
["present participles"] = "andweard dǣlnimend",
["masculine"] = "werlic",
["masculine nouns"] = "werlic naman",
["masculine a-stem nouns"] = "werlic a-stefn naman",
["feminine"] = "wīflic",
["feminine nouns"] = "wīflic naman",
["neuter"] = "nāhwæðer",
["neuter POS"] = "nāhwæðer",
["lemmas"] = "frumcennede word",
["non-lemma forms"] = "gebīegede word",
["variant lemmas"] = "ōþre frumcennede word",
["variant non-lemma forms"] = "ōþre gebīegede word",
["proper nouns"] = "āgennama",
["numerals"] = "getælu",
["letters"] = "stafas",
["proverbs"] = "gecyndesprǣce",
["other languages"] = "ōþre sprǣce",
["terms borrowed from"] = "word borgodon of",
["terms borrowed back into"] = "word borgodon tō",
["terms inherited from"] = "word ierfde of",
["terms derived from"] = "word ofgangende fram",
["learned borrowings"] = "leornode borgiende",
["compound words"] = "manigfeald word",
["abbreviations"] = "scortnessa",
["plural"] = "manigfeald",
["phrases"] = "cwidas",
["determiners"] = "bīnama",
["symbols"] = "rūna",
["countable nouns"] = "tellendlīc naman",
["uncountable nouns"] = "untellendlīc naman",
["initialisms"] = "styrtende stæfgefēg",
["particles"] = "grot",
["pages with entries"] = "trāmetas mid word"
}
--[=[
Wording still to be translated. Anything listed here (or used in a module but not listed at
all) falls back to its English form and shows up in
[[Wiktionary:Tracking/flocc/untranslated]], so nothing breaks while the table is filled in.
Suggestions, commented out until checked against a source:
-- ["suffixes"] = "?", -- "Englisc ?"
-- ["prefixes"] = "?",
-- ["interjections"] = "?",
-- ["Translingual"] = "?",
-- ["taxonomic names"] = "?",
]=]
--[=[
Wording that carries a variable part, as a leading phrase. For example the affix templates
build categories such as "terms suffixed with -kas", where only the beginning is fixed; the
language name is prepended by the caller. Longest phrase first, so the most specific rule wins.
]=]
local prefixes = {
{"terms spelled with ", "word areahton mid "}
-- {"terms suffixed with ", "?"},
-- {"terms prefixed with ", "?"},
-- {"terms circumfixed with ", "?"},
-- {"terms infixed with ", "?"},
-- {"terms interfixed with ", "?"},
}
local function track(text)
local ok, track_module = pcall(require, "Module:debug/track")
if ok then
track_module("flocc/untranslated/" .. tostring(text))
end
end
--[==[
Return the Old English wording for one category name, or nil if it is not known yet. `lang` is
accepted so that a wording can later be made language-specific if that turns out to be needed.
]==]
function export.translate(lang, text)
if type(text) ~= "string" or text == "" then
return nil
end
if words[text] then
return words[text]
end
for _, rule in ipairs(prefixes) do
if text:sub(1, #rule[1]) == rule[1] then
return rule[2] .. text:sub(#rule[1] + 1)
end
end
track(text)
return nil
end
--[==[
Return the Old English wording for one category name, or the English wording unchanged if it is
not known yet. Use this in the modules that build categories: the language name is prepended by
the caller, so only the wording itself is passed in.
]==]
function export.get(lang, text)
return export.translate(lang, text) or text
end
-- The table is exposed for tests and maintenance scripts; use get() elsewhere.
export.words = words
return export
i7uyto2oc6fnpjv2yfhinma72oh1og3
55010
55005
2026-09-30T18:36:36Z
Deadend0914
7211
55010
Scribunto
text/plain
--[=[
Floccnaman (category names) for the Old English Wiktionary.
Every category that a reader sees on an entry page is built as "<sprǣc> <word>" or
"<sprǣc> <word> <word>", exactly like the categories that already exist by hand on this wiki
("Englisc nama", "Nīwenglisc fégung", "Eald Noren werlic nama"), so only the words need
translating, never the word order.
The modules that build such categories ([[Module:headword]], [[Module:headword utilities]],
[[Module:form of]], [[Module:gender and number]], [[Module:affix]], [[Module:etymology]],
[[Module:etymology/specialized]], [[Module:ang-headword]]) ask this module for the Old English
wording first and keep the English wording if it is not here yet. Every missing wording is
tracked in [[Wiktionary:Tracking/flocc/untranslated/<wording>]], so the list of what still needs
to be added is always up to date:
[[Special:WhatLinksHere/Wiktionary:Tracking/flocc/untranslated]]
Words are entered without leading or trailing spaces, in the singular for parts of speech
("nama", not "naman"), because that is what the pre-existing categories use. The plural forms
used for the lemma categories are entered separately ("lemmas", "non-lemma forms", ...).
Only categories a reader sees on an entry page are translated here. Maintenance and tracking
categories (sortkeys, script codes, requests, pages with ...), the categories of the template
namespace and the ones produced by MediaWiki itself stay English on purpose.
]=]
local export = {}
--[=[
English wording -> Old English wording.
The words in this first group are already used by categories and template names that exist on
this wiki, so their spelling is established ("Flocc:Nama", "Flocc:Bīnaman", "Flocc:Nīwenglisc
fégung", "Bysen:Tōgeīecendlic-Tabule", "Cynn 1 strang forþgewiten dǣlnimend"), and the gender
words match the existing "Eald Noren werlic nama" style.
]=]
local words = {
-- Parts of speech.
["nouns"] = "naman",
["verbs"] = "word",
["adjectives"] = "tōgeīecendlice word",
["adverbs"] = "bīword",
["conjunctions"] = "fégunga",
["pronouns"] = "bīnaman",
["prepositions"] = "foresetnessa",
["participles"] = "dǣlnimend",
["past participles"] = "forþgewiten dǣlnimend",
["present participles"] = "andweard dǣlnimend",
["masculine"] = "werlic",
["masculine nouns"] = "werlic naman",
["masculine a-stem nouns"] = "werlic a-stefn naman",
["feminine"] = "wīflic",
["feminine nouns"] = "wīflic naman",
["neuter"] = "nāhwæðer",
["neuter nouns"] = "nāhwæðer naman",
["lemmas"] = "frumcennede word",
["non-lemma forms"] = "gebīegede word",
["variant lemmas"] = "ōþre frumcennede word",
["variant non-lemma forms"] = "ōþre gebīegede word",
["proper nouns"] = "āgennama",
["numerals"] = "getælu",
["letters"] = "stafas",
["proverbs"] = "gecyndesprǣce",
["other languages"] = "ōþre sprǣce",
["terms borrowed from"] = "word borgodon of",
["terms borrowed back into"] = "word borgodon tō",
["terms inherited from"] = "word ierfde of",
["terms derived from"] = "word ofgangende fram",
["learned borrowings"] = "leornode borgiende",
["compound words"] = "manigfeald word",
["abbreviations"] = "scortnessa",
["plural"] = "manigfeald",
["phrases"] = "cwidas",
["determiners"] = "bīnama",
["symbols"] = "rūna",
["countable nouns"] = "tellendlīc naman",
["uncountable nouns"] = "untellendlīc naman",
["initialisms"] = "styrtende stæfgefēg",
["particles"] = "grot",
["pages with entries"] = "trāmetas mid word"
}
--[=[
Wording still to be translated. Anything listed here (or used in a module but not listed at
all) falls back to its English form and shows up in
[[Wiktionary:Tracking/flocc/untranslated]], so nothing breaks while the table is filled in.
Suggestions, commented out until checked against a source:
-- ["suffixes"] = "?", -- "Englisc ?"
-- ["prefixes"] = "?",
-- ["interjections"] = "?",
-- ["Translingual"] = "?",
-- ["taxonomic names"] = "?",
]=]
--[=[
Wording that carries a variable part, as a leading phrase. For example the affix templates
build categories such as "terms suffixed with -kas", where only the beginning is fixed; the
language name is prepended by the caller. Longest phrase first, so the most specific rule wins.
]=]
local prefixes = {
{"terms spelled with ", "word areahton mid "}
-- {"terms suffixed with ", "?"},
-- {"terms prefixed with ", "?"},
-- {"terms circumfixed with ", "?"},
-- {"terms infixed with ", "?"},
-- {"terms interfixed with ", "?"},
}
local function track(text)
local ok, track_module = pcall(require, "Module:debug/track")
if ok then
track_module("flocc/untranslated/" .. tostring(text))
end
end
--[==[
Return the Old English wording for one category name, or nil if it is not known yet. `lang` is
accepted so that a wording can later be made language-specific if that turns out to be needed.
]==]
function export.translate(lang, text)
if type(text) ~= "string" or text == "" then
return nil
end
if words[text] then
return words[text]
end
for _, rule in ipairs(prefixes) do
if text:sub(1, #rule[1]) == rule[1] then
return rule[2] .. text:sub(#rule[1] + 1)
end
end
track(text)
return nil
end
--[==[
Return the Old English wording for one category name, or the English wording unchanged if it is
not known yet. Use this in the modules that build categories: the language name is prepended by
the caller, so only the wording itself is passed in.
]==]
function export.get(lang, text)
return export.translate(lang, text) or text
end
-- The table is exposed for tests and maintenance scripts; use get() elsewhere.
export.words = words
return export
4p71zjjux08e5nutegi5120j652v3xn
55012
55010
2026-09-30T18:39:14Z
Deadend0914
7211
55012
Scribunto
text/plain
--[=[
Floccnaman (category names) for the Old English Wiktionary.
Every category that a reader sees on an entry page is built as "<sprǣc> <word>" or
"<sprǣc> <word> <word>", exactly like the categories that already exist by hand on this wiki
("Englisc nama", "Nīwenglisc fégung", "Eald Noren werlic nama"), so only the words need
translating, never the word order.
The modules that build such categories ([[Module:headword]], [[Module:headword utilities]],
[[Module:form of]], [[Module:gender and number]], [[Module:affix]], [[Module:etymology]],
[[Module:etymology/specialized]], [[Module:ang-headword]]) ask this module for the Old English
wording first and keep the English wording if it is not here yet. Every missing wording is
tracked in [[Wiktionary:Tracking/flocc/untranslated/<wording>]], so the list of what still needs
to be added is always up to date:
[[Special:WhatLinksHere/Wiktionary:Tracking/flocc/untranslated]]
Words are entered without leading or trailing spaces, in the singular for parts of speech
("nama", not "naman"), because that is what the pre-existing categories use. The plural forms
used for the lemma categories are entered separately ("lemmas", "non-lemma forms", ...).
Only categories a reader sees on an entry page are translated here. Maintenance and tracking
categories (sortkeys, script codes, requests, pages with ...), the categories of the template
namespace and the ones produced by MediaWiki itself stay English on purpose.
]=]
local export = {}
--[=[
English wording -> Old English wording.
The words in this first group are already used by categories and template names that exist on
this wiki, so their spelling is established ("Flocc:Nama", "Flocc:Bīnaman", "Flocc:Nīwenglisc
fégung", "Bysen:Tōgeīecendlic-Tabule", "Cynn 1 strang forþgewiten dǣlnimend"), and the gender
words match the existing "Eald Noren werlic nama" style.
]=]
local words = {
-- Parts of speech.
["nouns"] = "naman",
["verbs"] = "word",
["adjectives"] = "tōgeīecendlice word",
["adverbs"] = "bīword",
["conjunctions"] = "fégunga",
["pronouns"] = "bīnaman",
["prepositions"] = "foresetnessa",
["participles"] = "dǣlnimend",
["past participles"] = "forþgewiten dǣlnimend",
["present participles"] = "andweard dǣlnimend",
["masculine"] = "werlic",
["masculine nouns"] = "werlic naman",
["masculine a-stem nouns"] = "werlic a-stefn naman",
["feminine"] = "wīflic",
["feminine nouns"] = "wīflic naman",
["neuter"] = "nāhwæðer",
["neuter nouns"] = "nāhwæðer naman",
["neuter a-stem nouns"] = "nāhwæðer a-stefn naman",
["lemmas"] = "frumcennede word",
["non-lemma forms"] = "gebīegede word",
["variant lemmas"] = "ōþre frumcennede word",
["variant non-lemma forms"] = "ōþre gebīegede word",
["proper nouns"] = "āgennama",
["numerals"] = "getælu",
["letters"] = "stafas",
["proverbs"] = "gecyndesprǣce",
["other languages"] = "ōþre sprǣce",
["terms borrowed from"] = "word borgodon of",
["terms borrowed back into"] = "word borgodon tō",
["terms inherited from"] = "word ierfde of",
["terms derived from"] = "word ofgangende fram",
["learned borrowings"] = "leornode borgiende",
["compound words"] = "manigfeald word",
["abbreviations"] = "scortnessa",
["plural"] = "manigfeald",
["phrases"] = "cwidas",
["determiners"] = "bīnama",
["symbols"] = "rūna",
["countable nouns"] = "tellendlīc naman",
["uncountable nouns"] = "untellendlīc naman",
["initialisms"] = "styrtende stæfgefēg",
["particles"] = "grot",
["pages with entries"] = "trāmetas mid word"
}
--[=[
Wording still to be translated. Anything listed here (or used in a module but not listed at
all) falls back to its English form and shows up in
[[Wiktionary:Tracking/flocc/untranslated]], so nothing breaks while the table is filled in.
Suggestions, commented out until checked against a source:
-- ["suffixes"] = "?", -- "Englisc ?"
-- ["prefixes"] = "?",
-- ["interjections"] = "?",
-- ["Translingual"] = "?",
-- ["taxonomic names"] = "?",
]=]
--[=[
Wording that carries a variable part, as a leading phrase. For example the affix templates
build categories such as "terms suffixed with -kas", where only the beginning is fixed; the
language name is prepended by the caller. Longest phrase first, so the most specific rule wins.
]=]
local prefixes = {
{"terms spelled with ", "word areahton mid "}
-- {"terms suffixed with ", "?"},
-- {"terms prefixed with ", "?"},
-- {"terms circumfixed with ", "?"},
-- {"terms infixed with ", "?"},
-- {"terms interfixed with ", "?"},
}
local function track(text)
local ok, track_module = pcall(require, "Module:debug/track")
if ok then
track_module("flocc/untranslated/" .. tostring(text))
end
end
--[==[
Return the Old English wording for one category name, or nil if it is not known yet. `lang` is
accepted so that a wording can later be made language-specific if that turns out to be needed.
]==]
function export.translate(lang, text)
if type(text) ~= "string" or text == "" then
return nil
end
if words[text] then
return words[text]
end
for _, rule in ipairs(prefixes) do
if text:sub(1, #rule[1]) == rule[1] then
return rule[2] .. text:sub(#rule[1] + 1)
end
end
track(text)
return nil
end
--[==[
Return the Old English wording for one category name, or the English wording unchanged if it is
not known yet. Use this in the modules that build categories: the language name is prepended by
the caller, so only the wording itself is passed in.
]==]
function export.get(lang, text)
return export.translate(lang, text) or text
end
-- The table is exposed for tests and maintenance scripts; use get() elsewhere.
export.words = words
return export
qcyz8amhcl3qb4wdkdtl2j8o1i55mdk
Module:table/sortedPairs
828
8086
54961
2026-09-30T17:32:41Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_keys_to_list_module = "Module:table/keysToList" local function keys_to_list(...) keys_to_list = require(table_keys_to_list_module) return keys_to_list(...) end --[==[ Iterates through a table, with the keys sorted using the keysToList function. If there are only numerical keys, `export.sparseIpairs` is probably faster.]==] return function(t, key_sort) local list, i = keys_to_list(t, key_sort), 0 return function() i = i + 1 local k = list[i]...'
54961
Scribunto
text/plain
local table_keys_to_list_module = "Module:table/keysToList"
local function keys_to_list(...)
keys_to_list = require(table_keys_to_list_module)
return keys_to_list(...)
end
--[==[
Iterates through a table, with the keys sorted using the keysToList function.
If there are only numerical keys, `export.sparseIpairs` is probably faster.]==]
return function(t, key_sort)
local list, i = keys_to_list(t, key_sort), 0
return function()
i = i + 1
local k = list[i]
if k ~= nil then
return k, t[k]
end
end
end
k9kspqkam96jj5np6nfk0sg15yvnnd3
Module:table/keysToList
828
8087
54962
2026-09-30T17:33:04Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local compare_module = "Module:compare" local pairs = pairs local sort = table.sort local compare local function get_compare() compare, get_compare = require(compare_module), nil return compare end --[==[ Returns a list of the keys in a table, sorted using either the default `table.sort` function or a custom `key_sort` function. If there are only numerical keys, [[Module:table/numKeys]] is probably faster.]==] return function(t, key_sort) local list, i = {...'
54962
Scribunto
text/plain
local compare_module = "Module:compare"
local pairs = pairs
local sort = table.sort
local compare
local function get_compare()
compare, get_compare = require(compare_module), nil
return compare
end
--[==[
Returns a list of the keys in a table, sorted using either the default `table.sort` function or a custom `key_sort` function.
If there are only numerical keys, [[Module:table/numKeys]] is probably faster.]==]
return function(t, key_sort)
local list, i = {}, 0
for key in pairs(t) do
i = i + 1
list[i] = key
end
-- Use specified sort function, or otherwise `compare`.
sort(list, key_sort == nil and (compare or get_compare()) or key_sort)
return list
end
db7h2mui4n7duv8tkb39qolcbe3t8rm
Module:compare
828
8088
54963
2026-09-30T17:33:28Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local math_compare_module = "Module:math/compare" local string_compare_module = "Module:string/compare" local table_compare_module = "Module:table/compare" local require = require local type = type local types local function get_types() types, get_types = { ["nil"] = "\1", ["boolean"] = "\2", ["number"] = "\3", ["string"] = "\4", }, nil return types end local compare_funcs local function get_compare_funcs() compare_funcs = {} function compare_fu...'
54963
Scribunto
text/plain
local math_compare_module = "Module:math/compare"
local string_compare_module = "Module:string/compare"
local table_compare_module = "Module:table/compare"
local require = require
local type = type
local types
local function get_types()
types, get_types = {
["nil"] = "\1",
["boolean"] = "\2",
["number"] = "\3",
["string"] = "\4",
}, nil
return types
end
local compare_funcs
local function get_compare_funcs()
compare_funcs = {}
function compare_funcs.boolean(a, b)
return a == true and b == false
end
function compare_funcs.number(...)
local math_compare = require(math_compare_module)
compare_funcs.number = math_compare
return math_compare(...)
end
function compare_funcs.string(...)
local string_compare = require(string_compare_module)
compare_funcs.string = string_compare
return string_compare(...)
end
function compare_funcs.table(...)
local table_compare = require(table_compare_module)
compare_funcs.table = table_compare
return table_compare(...)
end
get_compare_funcs = nil
return compare_funcs
end
--[==[
A general comparison function, which returns {true} if {a} sorts before {b}, or otherwise {false}; it can be used as the sort function with {table.sort}.
This function is roughly equivalent to the {<} operator, but is capable of comparing a mix of types, using the following rules:
* When items of different types are compared, the type-order is {"nil"}, {"boolean"}, {"number"}, {"string"}, then alphabetical order by type name for all other types.
* Numbers are compared with [[Module:math/compare]].
* Strings are compared with [[Module:string/compare]].
* Tables are compared with [[Module:table/compare]].
* Boolean {true} sorts before {false}.
* All other types always return {false}.]==]
return function(a, b)
local type_a, type_b = type(a), type(b)
if type_a == type_b then
local func = (compare_funcs or get_compare_funcs())[type_a]
return func and func(a, b) or false
end
return ((types or get_types())[type_a] or type_a) < (types[type_b] or type_b)
end
fs82yur52kblfpa4j6uwsb5dbcxr7em
Module:math/compare
828
8089
54964
2026-09-30T17:33:54Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local math_module = "Module:math" local function is_NaN(...) is_NaN = require(math_module).is_NaN return is_NaN(...) end local function sign(...) sign = require(math_module).sign return sign(...) end --[==[ A comparison function for numbers, which returns {true} if {a} sorts before {b}, or otherwise {false}; it can be used as the sort function with {table.sort}. This function is roughly equivalent to the {<} operator, but contains the following special co...'
54964
Scribunto
text/plain
local math_module = "Module:math"
local function is_NaN(...)
is_NaN = require(math_module).is_NaN
return is_NaN(...)
end
local function sign(...)
sign = require(math_module).sign
return sign(...)
end
--[==[
A comparison function for numbers, which returns {true} if {a} sorts before {b}, or otherwise {false}; it can be used as the sort function with {table.sort}.
This function is roughly equivalent to the {<} operator, but contains the following special considerations in accordance with the {{w|IEEE 754}} standard:
* {{w|NaN}} is sorted as though it has a larger absolute value than infinity ({-NaN < -Inf}; {+Inf < +NaN}).
* {{w|Signed zero}} is acknowledged, with {-0 < +0}.]==]
return function(a, b)
-- <, > and == canot return true if either `a` or `b` are NaN.
if a < b then
return true
-- Use > then == instead of >=, so that the ±0 check is only done when `a`
-- and `b` are equal.
elseif a > b then
return false
elseif a == b then
-- 1/(+0) is Inf; 1/(-0) is -Inf.
return a == 0 and b == 0 and 1 / a < 1 / b or false
-- One or both must be NaN, and NaN is the only number that returns false
-- to a self-equality check, so if `a` == `a` then `b` is NaN (and vice-
-- versa). -NaN sorts before everything and +NaN after everything, so the
-- sign determines the result.
elseif not is_NaN(a) then -- `b` is NaN
return sign(b) == 1
elseif not is_NaN(b) then -- `a` is NaN
return sign(a) == -1
end
-- If both are NaN, only return true if `a` is -NaN and `b` is +NaN.
return sign(a) < sign(b)
end
632obodjr2zjfbz9zpr3erx1io9vwm4
Module:table/compare
828
8090
54965
2026-09-30T17:34:28Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local string_linesub_module = "Module:string/linesub" local table_get_metamethod_module = "Module:table/getMetamethod" local error = error local rawequal = rawequal local require = require local sub = string.sub local traceback = debug.traceback local xpcall = xpcall local function get_metamethod(...) get_metamethod = require(table_get_metamethod_module) return get_metamethod(...) end local function linesub(...) linesub = require(string_linesub_module) ret...'
54965
Scribunto
text/plain
local string_linesub_module = "Module:string/linesub"
local table_get_metamethod_module = "Module:table/getMetamethod"
local error = error
local rawequal = rawequal
local require = require
local sub = string.sub
local traceback = debug.traceback
local xpcall = xpcall
local function get_metamethod(...)
get_metamethod = require(table_get_metamethod_module)
return get_metamethod(...)
end
local function linesub(...)
linesub = require(string_linesub_module)
return linesub(...)
end
local _a, _b -- used to pass arguments to compare_for_xpcall()
local NOT_COMPARABLE = {} -- sentinel
local function compare_for_xpcall()
local a, b = _a, _b
_a, _b = nil
return a < b
end
local function err_handler(err)
-- Error message relate to comparing two table values, but the trace at the
-- start of the message needs to be trimmed. Traceback will only refer to
-- xpcall() on line 5 if the error occured in compare_for_xpcall(); if it
-- originated further up the stack (i.e. in a metamethod), xpcall() will
-- only be mentioned on a later line.
if (
sub(err, -35) == "attempt to compare two table values" and
linesub(traceback(), 5, 5) == "\t[C]: in function 'xpcall'"
) then
return NOT_COMPARABLE
end
return err
end
--[==[
A comparison function for tables, which returns {true} if {a} sorts before {b}, or otherwise {false}; it can be used as the sort function with {table.sort}.
By default, attempting to compare two tables will result in an error, unless both tables have the same {__lt} metamethod. This function will also use the {__lt} metamethod under the same circumstances, but will return {false} instead of throwing an error if the two tables are not comparable.]==]
return function(a, b)
-- If `a` and `b` are tables, they are only comparable if they both have the
-- same __lt metamethod. get_unprotected_metatable() returns nil if there is
-- no metatable, and false if the metatable is protected.
local success_a, __lt_a = get_metamethod(a, "__lt")
-- Not comparable if `a` has no __lt metamethod.
if success_a and __lt_a == nil then
return false
end
local success_b, __lt_b = get_metamethod(b, "__lt")
if success_b then
-- Not comparable if `b` has no __lt metamethod.
if __lt_b == nil then
return false
-- If `a` and `b` have known __lt metamethods, they are only comparable
-- if the two metamethods are primitively equal.
elseif __lt_a ~= nil then
return rawequal(__lt_a, __lt_b) and a < b
end
end
-- Not possible to know if `a` and `b` are comparable if either or both have
-- protected metatables, and neither are confirmed to have no __lt
-- metamethod. Use xpcall() when comparing them, with an error handler that
-- determines if the error was due to `a` and `b` not being comparable.
-- Unlike pcall(), xpcall() can't pass on arguments to the called function,
-- so use upvalues in the top-level of the module to circumvent this.
_a, _b = a, b
local success, result = xpcall(compare_for_xpcall, err_handler)
if success then
return result
elseif result == NOT_COMPARABLE then
return false
end
-- If the error wasn't due to the comparison, rethrow it.
error(result)
end
8mcw42u3tihzaq6279e3zlbjws3nhdr
Module:string/linesub
828
8091
54966
2026-09-30T17:34:52Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local index_module = "Module:index" local string_linelen_module = "Module:string/linelen" local find = string.find local require = require local sub = string.sub local function index_range(...) index_range = require(index_module).range return index_range(...) end local function linelen(...) linelen = require(string_linelen_module) return linelen(...) end return function(str, i, j) i, j = index_range(str, i, j, linelen) if j and j < i then return "" e...'
54966
Scribunto
text/plain
local index_module = "Module:index"
local string_linelen_module = "Module:string/linelen"
local find = string.find
local require = require
local sub = string.sub
local function index_range(...)
index_range = require(index_module).range
return index_range(...)
end
local function linelen(...)
linelen = require(string_linelen_module)
return linelen(...)
end
return function(str, i, j)
i, j = index_range(str, i, j, linelen)
if j and j < i then
return ""
end
local first, pos1, pos2, newline, pattern, plain = 1
if not find(str, "\r", nil, true) then
pattern, plain = "\n", true
elseif not find(str, "\n", nil, true) then
pattern, plain = "\r", true
else
pattern = "([\n\r]\n?)"
end
for _ = 1, i - 1 do
pos1, pos2, newline = find(str, pattern, first, plain)
if pos1 == nil then
return ""
end
first = newline == "\n\n" and pos2 or pos2 + 1
end
if j == nil then
return sub(str, first)
end
local last = first
for _ = i, j do
pos1, pos2, newline = find(str, pattern, last, plain)
if pos1 == nil then
return sub(str, first)
end
last = newline == "\n\n" and pos2 or pos2 + 1
end
last = pos1 - 1
return sub(str, first, last)
end
qzydmib4uoy2d53y6s4vvt1a6pynnwv
Module:index
828
8092
54967
2026-09-30T17:35:22Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local math_module = "Module:math" local error = error local format = string.format local tostring = tostring local type = type local function to_integer(...) to_integer = require(math_module).to_integer return to_integer(...) end local function input_err(i, funcname, param) local idx_type = type(i) error(format( "bad argument #%d to 'index.%s' (integer expected, got %s)", param, funcname, idx_type == "number" and tostring(i) or idx_t...'
54967
Scribunto
text/plain
local export = {}
local math_module = "Module:math"
local error = error
local format = string.format
local tostring = tostring
local type = type
local function to_integer(...)
to_integer = require(math_module).to_integer
return to_integer(...)
end
local function input_err(i, funcname, param)
local idx_type = type(i)
error(format(
"bad argument #%d to 'index.%s' (integer expected, got %s)",
param, funcname, idx_type == "number" and tostring(i) or idx_type
), 4)
end
local function get_index(i, obj, func, neg1_nil, funcname, param)
i = to_integer(i) or input_err(i, funcname, param)
-- A relative index of -1 refers to the final index. For many functions,
-- it makes no difference if the final index is nil or -1, so if the
-- `neg1_nil` flag is set and the index is -1, return nil to avoid having
-- to call `func`.
if neg1_nil and i == -1 then
return nil, obj, func
elseif i < 0 then
if func ~= nil then
obj, func = func(obj), nil
end
i = obj + i + 1
end
return i, obj, func
end
function export.absolute(obj, i, func, i_min, i_max)
i, obj, func = get_index(i, obj, func, false, "absolute", 2)
if i_min ~= nil then
i_min, obj, func = get_index(i_min, obj, func, false, "absolute", 4)
if i < i_min then
i = i_min
end
end
if i_max ~= nil then
i_max, obj, func = get_index(i_max, obj, func, false, "absolute", 5)
if i_min and i_min > i_max then
error("bad arguments #4 and #5 to 'index.absolute' (`i_min` cannot be greater than `i_max`)", 2)
elseif i > i_max then
i = i_max
end
end
return i
end
function export.range(obj, i, j, func, always_infer_upper, i_min, j_max)
-- Canonicalize `i`, or otherwise default to 1.
if i == nil then
i = 1
else
i, obj, func = get_index(i, obj, func, false, "range", 2)
end
-- Get the minimum value for `i`. This is 1 by default, because a start
-- index of 0 is always equivalent to 1 for most functions (e.g. string.byte
-- and string.sub). However, many module functions have checks like
-- "while i <= j" or "if i > j" that won't, therefore, handle (0, 0) like
-- (1, 0), so converting 0 to 1 avoids the need to special-case it. If
-- `i_min_allow_0` is set, the minimum defaults to 0 instead (including when
-- the input is nil), which may be needed if 0 represents the position
-- before the first index.
if i_min == nil then
i_min = 1
else
i_min, obj, func = get_index(i_min, obj, func, false, "range", 5)
end
-- Set `i` as the minimum value if it hasn't been specified.
if i < i_min then
i = i_min
end
-- Canonicalize `j`, which will default to the end if not given. By default,
-- this may be implied by returning nil for `j` (even if the input is -1),
-- to avoid calling `func` unnecessarily.
if j ~= nil then
j, obj, func = get_index(j, obj, func, not always_infer_upper, "range", 3)
end
-- Get the maximum value for `j` (if any).
if j_max ~= nil then
j_max, obj, func = get_index(j_max, obj, func, not always_infer_upper, "range", 6)
end
-- If `j` is not set but `j_max` has a value, use that. Otherwise, if
-- `always_infer_upper` is set then give the length of `obj`.
if not j then
j = j_max or always_infer_upper and (not func and obj or func(obj)) or nil
-- Ensure `j` does not exceed `j_max`.
elseif j_max and j > j_max then
j = j_max
end
-- The minimum value for `j` is i - 1, which is equivalent to a range of
-- 0 length (e.g. string.find("", "") returns (1, 0)).
local j_min = i - 1
if j and j < j_min then
j = j_min
end
return i, j
end
return export
oo8syi7zilb2cjg7t58khlglmo5bzyt
Module:string/linelen
828
8093
54968
2026-09-30T17:35:44Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local gmatch = string.gmatch --[==[ Counts the number of lines in the input string, treating {"\n"}, {"\r"} and {"\r\n"} as new lines.]==] return function(str) local n = 1 for newline in gmatch(str, "([\n\r]\n?)[^\n\r]*") do n = n + (newline == "\n\n" and 2 or 1) end return n end'
54968
Scribunto
text/plain
local gmatch = string.gmatch
--[==[
Counts the number of lines in the input string, treating {"\n"}, {"\r"} and {"\r\n"} as new lines.]==]
return function(str)
local n = 1
for newline in gmatch(str, "([\n\r]\n?)[^\n\r]*") do
n = n + (newline == "\n\n" and 2 or 1)
end
return n
end
jpgjm9kkpx83bv03ujdphtzhtxxjsg2
Module:labels
828
8094
54969
2026-09-30T17:37:22Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} export.lang_specific_data_list_module = "Module:labels/data/lang" export.lang_specific_data_modules_prefix = "Module:labels/data/lang/" local load_module = "Module:load" local parse_utilities_module = "Module:parse utilities" local string_utilities_module = "Module:string utilities" local utilities_module = "Module:utilities" local insert = table.insert local concat = table.concat local require_when_needed = require("Module:require when neede...'
54969
Scribunto
text/plain
local export = {}
export.lang_specific_data_list_module = "Module:labels/data/lang"
export.lang_specific_data_modules_prefix = "Module:labels/data/lang/"
local load_module = "Module:load"
local parse_utilities_module = "Module:parse utilities"
local string_utilities_module = "Module:string utilities"
local utilities_module = "Module:utilities"
local insert = table.insert
local concat = table.concat
local require_when_needed = require("Module:require when needed")
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local dump = mw.dumpObject
local m_lang_specific_data = mw.loadData(export.lang_specific_data_list_module)
local m_table = require_when_needed("Module:table")
--[==[ intro:
Labels go through several stages of processing to get from the original (raw) label specified in the Wikicode to the
final (formatted) label displayed to the user. The following terminology will help keep things straight:
* The "raw label" is the label specified in the Wikicode.
* The "non-canonical label" is the label extracted from the raw label, used for looking up in the label modules in order
to fetch the associated label data structure and determine the canonical form of the label. Normally this is the same
as the raw label, but it will be different if the raw label is of the form `!<var>label</var>` (e.g. `!Australian`)
`<var>label</var>!<var>display</var>` (e.g. `Southern US!Southern`). The former syntax indicates that the label
should display as-is instead of in its canonical form (which in the example given is `Australia`), and the latter
syntax indicates that the label should display in the form specified after the exclamation point.
* The "canonical label" is the result of applying alias resolution to the non-canonical label. Normally, the
canonical label rather than the non-canonical label is what is shown to the user.
* The "display form of the label" is what is shown to the user, not considering links and HTML that may wrap the
display form to get the formatted form of the label. The display form comes from the `.display` field of the module
label data for the label; if no such field exists in the label data, it is normally the canonical label. However, if
the display override exists (see below), it takes precedence over the `.display` field or canonical label when
determining the display form of the label.
* The "display override", if specified, overrides all other means of determining the display form of the label. It is
specified in two circumstances, i.e. in the `!<var>label</var>` and `<var>label</var>!<var>display</var>` raw label
formats (i.e. in the same cirumstances where the raw label and non-canonical label are different).
* The "formatted form of the label" is the final form of the label shown directly to the user. It generally appears to
the user as the display form of the label, but in the Wikicode, the formatted form may wrap the display form with a
link to Wikipedia, the Wiktionary glossary or another Wiktionary entry, and that link in turn may be wrapped in an
HTML span with a "deprecated" CSS class attached, causing the label to display differently (to indicate that it is
deprecated).
]==]
-- for testing
local force_cat = false
local m_headword_data = mw.loadData("Module:headword/data")
local SUBPAGENAME = m_headword_data.pagename
-- Disable tracking on heavy pages to save time.
local pages_where_tracking_is_disabled = m_headword_data.large_pages
-- Add tracking category for PAGE. The tracking category linked to is [[Wiktionary:Tracking/labels/PAGE]].
-- We also add to [[Wiktionary:Tracking/labels/PAGE/LANGCODE]] and [[Wiktionary:Tracking/labels/PAGE/MODE]] if
-- LANGCODE and/or MODE given.
local function track(page, langcode, mode)
if pages_where_tracking_is_disabled[SUBPAGENAME] then
return true
end
-- avoid including links in pages (may cause error)
page = page:gsub("%[", "("):gsub("%]", ")"):gsub("|", "!")
require("Module:debug/track")("labels/" .. page)
if langcode then
require("Module:debug/track")("labels/" .. page .. "/" .. langcode)
end
if mode then
require("Module:debug/track")("labels/" .. page .. "/" .. mode)
end
-- We don't currently add a tracking label for both langcode and mode to reduce the total number of labels, to
-- save some memory.
return true
end
local function ucfirst(txt)
return mw.getContentLanguage():ucfirst(txt)
end
local mode_to_outer_class = {
["label"] = "usage-label-sense",
["term-label"] = "usage-label-term",
["accent"] = "usage-label-accent",
["form-of"] = "usage-label-form-of",
}
local mode_to_property_prefix = {
["label"] = false,
["term-label"] = false, -- handled specially
["accent"] = "accent_",
["form-of"] = "form_of_",
}
local function validate_mode(mode)
mode = mode or "label"
if not mode_to_outer_class[mode] then
local allowed_values = {}
for key, _ in pairs(mode_to_outer_class) do
insert(allowed_values, "'" .. key .. "'")
end
table.sort(allowed_values)
error(("Invalid value '%s' for `mode`; should be one of %s"):format(mode, concat(allowed_values, ", ")))
end
return mode
end
local function getprop(labdata, mode, prop)
local mode_prefix = mode_to_property_prefix[mode]
return mode_prefix and labdata[mode_prefix .. prop] or labdata[prop]
end
local function check_type(label, lang, prop, value, expected_types)
if value == nil or expected_types == nil then
return value
end
if type(expected_types) ~= "table" then
expected_types = {expected_types}
end
local valtype = type(value)
local matches = false
for _, expected_type in ipairs(expected_types) do
if type(expected_type) == "string" then
if valtype == expected_type then
matches = true
break
end
elseif value == expected_type then
matches = true
break
end
end
if not matches then
local function join_untagged_or(elements)
return m_table.serialCommaJoin(elements, {conj = "or", dontTag = true})
end
local quoted_types = {}
local quoted_values = {}
for _, expected_type in ipairs(expected_types) do
if type(expected_type) == "string" then
insert(quoted_types, "'" .. expected_type .. "'")
else
insert(quoted_values, "'" .. dump(expected_type) .. "'")
end
end
local possible_matches = {}
if quoted_types[1] then
insert(possible_matches, ("be of type%s %s"):format(
quoted_types[2] and "s" or "", join_untagged_or(quoted_types)))
end
if quoted_values[1] then
insert(possible_matches, ("have the value%s %s"):format(
quoted_values[2] and "s" or "", join_untagged_or(quoted_values)))
end
error(("Internal error: For label '%s', langcode '%s', property '%s' should %s but is of type '%s' with value %s"):format(
label, lang and lang:getCode() or "UNKNOWN", prop, join_untagged_or(possible_matches), valtype, dump(value)))
end
end
-- HACK! For languages in any of the given families, check the specified-language Wikipedia for appropriate
-- Wikipedia articles for the language in question (esp. useful for obscure etymology-only languages that may not
-- have English articles for them, like many Chinese lects).
local families_to_wikipedia_languages = {
{"zhx", "zh"},
{"sem-arb", "ar"},
}
--[==[
Given language `lang` (a full language, etymology-language or family), fetch a list of Wikimedia languages to check
when converting a Wikidata item to a Wikipedia article. English is always first, followed by the Wikimedia language
code(s) of `lang` if `lang` is a language (which may or may not be the same as `lang`'s Wiktionary code), followed
by the macrolanguage of `lang` for certain languages and families (currently, only languages and families in the Chinese
and Arabic families). If `lang` is nil, only return English. Note that the same code may occur more than once in the
list. This is exported because it's also used by [[Module:category tree/lects]].
]==]
function export.get_langs_to_extract_wikipedia_articles_from_wikidata(lang)
local wikipedia_langs = {}
insert(wikipedia_langs, "en")
if lang then
local article_lang = lang
while article_lang do
if article_lang:hasType("language") then
local wmcodes = article_lang:getWikimediaLanguageCodes()
for _, wmcode in ipairs(wmcodes) do
insert(wikipedia_langs, wmcode)
end
end
article_lang = article_lang:getParent()
end
for _, family_to_wp_lang in ipairs(families_to_wikipedia_languages) do
local family, wp_lang = unpack(family_to_wp_lang)
if lang:inFamily(family) then
insert(wikipedia_langs, wp_lang)
end
end
end
return wikipedia_langs
end
--[==[
Fetch the categories to add to a page, given that the label whose canonical form is `canon_label` with language `lang`
has been seen. `labdata` is the label data structure for `label`, fetched from the appropriate submodule. `mode`
specifies how the label was invoked (see {get_label_info()} for more information). The return value is a list of the
actual categories, unless `for_doc` is specified, in which case the categories returned are marked up for display on a
documentation page. If `for_doc` is given, `lang` may be nil to format the categories in a language-independent fashion;
otherwise, it must be specified. If `category_types` is specified, it should be a set object (i.e. with category types
as keys and {true} as values), and only categories of the specified types will be returned.
]==]
function export.fetch_categories(canon_label, labdata, lang, mode, for_doc, category_types)
local categories = {}
mode = validate_mode(mode)
local langcode, canonical_name
if lang then
langcode = lang:getFullCode()
canonical_name = lang:getFullName()
elseif for_doc then
langcode = "<var>[langcode]</var>"
canonical_name = "<var>[language name]</var>"
else
error("Internal error: Must specify `lang` unless `for_doc` is given")
end
local function labprop(prop, expected_types)
local retval = getprop(labdata, mode, prop)
check_type(canon_label, lang, prop, retval, expected_types)
return retval
end
local empty_list = {}
local function get_cats(cat_type)
if category_types and not category_types[cat_type] then
return empty_list
end
local cats = labprop(cat_type)
if not cats then
return empty_list
end
if type(cats) ~= "table" then
return {cats}
end
return cats
end
local topical_categories = get_cats("topical_categories")
local sense_categories = get_cats("sense_categories")
local pos_categories = get_cats("pos_categories")
local regional_categories = get_cats("regional_categories")
local plain_categories = get_cats("plain_categories")
local function insert_cat(cat, sense_cat)
if for_doc then
cat = "<code>" .. cat .. "</code>"
if sense_cat then
if mode == "term-label" then
cat = cat .. " (using {{tl|tlb}})"
else
cat = cat .. " (using {{tl|lb}} or form-of template)"
end
cat = mw.getCurrentFrame():preprocess(cat)
end
end
insert(categories, cat)
end
for _, cat in ipairs(topical_categories) do
insert_cat(langcode .. ":" .. (cat == true and ucfirst(canon_label) or cat))
end
for _, cat in ipairs(sense_categories) do
if cat == true then
cat = canon_label
end
cat = mode == "term-label" and cat .. " terms" or "terms with " .. cat .. " senses"
insert_cat(canonical_name .. " " .. cat, true)
end
for _, cat in ipairs(pos_categories) do
insert_cat(canonical_name .. " " .. (cat == true and canon_label or cat))
end
for _, cat in ipairs(regional_categories) do
insert_cat((cat == true and ucfirst(canon_label) or cat) .. " " .. canonical_name)
end
for _, cat in ipairs(plain_categories) do
insert_cat(cat == true and ucfirst(canon_label) or cat)
end
return categories
end
--[==[
Return the list of all labels data modules for a label whose language is `lang`. The return value is a list of
module names, with overriding modules earlier in the list (that is, if a label occurs in two modules in the list,
the earlier-listed module takes precedence). If `lang` is nil, only return non-language-specific submodules.
]==]
function export.get_submodules(lang)
local submodules = {
"Module:labels/data",
"Module:labels/data/qualifiers",
"Module:labels/data/regional",
"Module:labels/data/topical",
}
if not lang then
return submodules
end
-- get language-specific labels from data module
local langcode = lang:getFullCode()
if m_lang_specific_data.langs_with_lang_specific_modules[langcode] then
-- prefer per-language label in order to pick subvariety labels over regional ones
insert(submodules, 1, export.lang_specific_data_modules_prefix .. langcode)
end
return submodules
end
--[==[
Return the formatted form of a label `label` (which should be the canonical form of the label; see comment at top),
given (a) the label data structure `labdata` from one of the data modules; (b) the language object `lang` of the
language being processed, or nil for no language; (c) `deprecated` (true if the label is deprecated, otherwise the
deprecation information is taken from `labdata`); (d) `override_display` (if specified, override the display form of the
label with the specified string, instead of any value in `labdata.display` or `labdata.special_display` or the canonical
label in `label` itself); (e) `mode` (same as `data.mode` passed to {get_label_info()}). Returns two values: the
formatted label form and a boolean indicating whether the label is deprecated.
'''NOTE: Under normal circumstances, do not use this.''' Instead, use {get_label_info()}, which searches all the data
modules for a given label and handles other complications.
]==]
function export.format_label(label, labdata, lang, deprecated, override_display, mode)
local formatted_label
mode = validate_mode(mode)
local function labprop(prop, expected_types)
local retval = getprop(labdata, mode, prop)
check_type(label, lang, prop, retval, expected_types)
return retval
end
deprecated = deprecated or labprop("deprecated")
if not override_display and labprop("special_display") then
local function add_language_name(str)
if str == "canonical_name" then
if lang then
return lang:getFullName()
else
return "<code><var>[language name]</var></code>"
end
else
return ""
end
end
formatted_label = labprop("special_display", "string"):gsub("<(.-)>", add_language_name)
else
--[=[
We proceed as follows:
1. The display form comes from either (a) the `override_display` variable if set (this happens when
the user uses a label like '!British'); (b) the `display` property, if set; or (c) the label iself.
2. If the display form contains a link, use it directly and ignore the other display-related settings.
(NOTE: Settings `Wikipedia` and `Wikidata` may still be used on the category page itself, by the
category tree code.)
3. Otherwise, use one of the other display-related settings, in the following order:
`glossary` > `Wiktionary` > `Wikipedia` > `Wikidata`. Specifically:
a. If any of the values is equal to `true`, that is equivalent to specifying a string consisting of
the canonical label.
b. If `glossary` is set, it specifies the anchor in [[Appendix:Glossary]].
c. If `Wiktionary` is set, it specifies an arbitrary Wiktionary page or page + anchor (e.g. a
separate Appendix entry).
d. If `Wikipedia` is set, it specifies an arbitrary Wikipedia article, or a list of such items (in
this case, we select the first one, but the category tree uses all of them).
e. If `Wikidata` is set, it specifies an arbitrary Wikidata item to retrieve a Wikipedia article from,
or a list of such items (in this case, we select the first one, but the category tree uses all of
them). If the item is of the form `wmcode:id`, the Wikipedia article corresponding to `id` in the
`wmcode`-language Wikipedia is fetched if available. Otherwise, the English-language Wikipedia
article corresponding to `id` is retrieved if available, falling back to the Wikimedia language(s)
corresponding to `lang` and then (in certain cases) to the macrolanguage that `lang` is part of.
Note that if `mode` is specified, prefixed properties (e.g. `accent_display` for `mode` == "accent",
`form_display` for `mode` == "form") are checked before the bare equivalent (e.g. `display`).
]=]
local display = override_display or labprop("display", "string") or label
-- There are several 'Foo spelling' labels specially designed for use in the |from= param in
-- {{alternative form of}}, {{standard spelling of}} and the like. Often the display includes the word
-- "spelling" at the end (e.g. if it's defaulted), which is useful when the label is used with {{tl|lb}} or
-- {{tl|tlb}}; but it causes redundancy when used with the form-of templates, which add the word "form",
-- "spelling", "standard spelling", etc. after the label.
if mode == "form-of" then
display = display:gsub(" spelling$", "")
end
if display:find("%[%[") then
formatted_label = display
else
local glossary = labprop("glossary", {"string", true})
local Wiktionary = labprop("Wiktionary", {"string", true})
local Wikipedia = labprop("Wikipedia", {"string", true, "table"})
local Wikidata = labprop("Wikidata", {"string", true, "table"})
if glossary then
local glossary_entry = glossary == true and label or glossary
formatted_label = "[[Appendix:Glossary#" .. glossary_entry .. "|" .. display .. "]]"
elseif Wiktionary then
local Wiktionary_entry = Wiktionary == true and label or Wiktionary
if Wiktionary == display then
formatted_label = "[[" .. display .. "]]"
else
formatted_label = "[[" .. Wiktionary_entry .. "|" .. display .. "]]"
end
elseif Wikipedia then
if type(Wikipedia) == "table" then
Wikipedia = Wikipedia[1]
end
local Wikipedia_entry = Wikipedia == true and label or Wikipedia
formatted_label = "[[w:" .. Wikipedia_entry .. "|" .. display .. "]]"
elseif Wikidata then
if not mw.wikibase then
error(("Unable to retrieve data from Wikidata ID for label '%s'; `mw.wikibase` not defined"
):format(label))
end
local function make_formatted_label(wmcode, id)
local article = mw.wikibase.sitelink(id, wmcode .. "wiki")
if article then
local link = wmcode == "en" and "w:" .. article or "w:" .. wmcode .. ":" .. article
return ("[[%s|%s]]"):format(link, display)
else
return nil
end
end
if type(Wikidata) == "table" then
Wikidata = Wikidata[1]
end
local wmcode, id = Wikidata:match("^(.*):(.*)$")
if wmcode then
formatted_label = make_formatted_label(wmcode, id)
else
local langs_to_check = export.get_langs_to_extract_wikipedia_articles_from_wikidata(lang)
for _, wmcode in ipairs(langs_to_check) do
formatted_label = make_formatted_label(wmcode, Wikidata)
if formatted_label then
break
end
end
end
formatted_label = formatted_label or display
else
formatted_label = display
end
end
end
if deprecated then
formatted_label = '<span class="deprecated-label">' .. formatted_label .. '</span>'
end
return formatted_label, deprecated
end
--[==[
Return information on a label. On input `data` is an object with the following fields:
* `label`: The raw label to return information on.
* `lang`: The language of the label. Must be specified unless `for_doc` is given.
* `mode`: How the label was invoked. One of the following:
** {nil} or {"label"}: invoked through {{tl|lb}} or another template whose labels in the same fashion, e.g.
{{tl|alt}}, {{tl|quote}} or {{tl|syn}};
** {"term-label"}: invoked through {{tl|tlb}};
** {"accent"}: invoked through {{tl|a}} or the {{para|a}} or {{para|aa}} parameters of other pronunciation templates,
such as {{tl|IPA}}, {{tl|rhymes}} or {{tl|homophones}};
** {"form-of"}: invoked through {{tl|alt form}}, {{tl|standard spelling of}} or other form-of template.
This changes the display and/or categorization of a minority of labels. (The majority work the same for all modes.)
* `for_doc`: Data is being fetched for documentation purposes. This causes the raw categories returned in
`categories` to be formatted for documentation display.
* `nocat`: If true, don't add the label to any categories.
* `force_cat`: Force adding categories even in namespaces that normally exclude them (e.g. userspace and discussion
pages).
* `notrack`: Disable all tracking for this label.
* `sort`: Sort key for categorization.
* `already_seen`: An object used to track labels already seen, so they aren't displayed twice. Tracking is according
to the display form of the label, so if two labels have the same display form, the second one won't be displayed
(but its categories will still be added). If `already_seen` is {nil}, this tracking doesn't happen.
The return value is an object with the following fields:
* `raw_text`: If specified, the object does not describe a label but simply raw text surrounding labels. This occurs
when double angle bracket (<<...>>) notation is used. {get_label_info()} does not currently return objects with this
field set, but {process_raw_labels()} does. The value is {"begin"} (this is the first raw text portion derived from
a double angle bracket spec, provided there are at least two raw text portions); {"end"} (this is the last raw text
portion derived from a double angle bracket spec, provided there are at least two portions); {"middle"} (this is
neither the first nor the last raw text portion); or {"only"} (this is a raw text portion standing by itself). The
particular value determines the handling of commas and spaces on one or both sides of the raw text. If this field is
specified, only the `label` field (containing the actual raw text) and the `category` field (containing an empty list)
are set; all other fields are {nil}.
* `raw_label`: The raw label that was passed in.
* `non_canonical`: The label prior to canonicalization (i.e. alias resolution). Usually this is the same as `raw_label`,
but if the raw label was preceded by an exclamation point (meaning "display the raw label as-is"), this field will
contain the label stripped of the exclamation point, and if the raw label is of the form
`<var>label</var>!<var>display</var>` (meaning "display the label in the specified form"), this field will contain the
label before the exclamation point.
* `canonical`: If the label in `non_canonical` is an alias, this contains the canonical name of the label; otherwise it
will be {nil}.
* `override_display`: If specified, this contains a string that overrides the normal display form of the label. The
display form of a label is the `.display` field of the label data if present, and otherwise is normally the canonical
form of the label (i.e. after alias resolution). (This is not the same as the formatted form of the label, found in
`label`, which is the final form shown to the user and includes links to Wikipedia, the glossary, etc. as well as an
HTML wrapper if the label is deprecated.) If `override_display` is specified, however, this is used in place of the
normal display form of the label. This currently happens in two circumstances: (1) the label was preceded by ! to
indicate that the raw label should be displayed rather than the canonical form; (2) the label was given in the form
`<var>label</var>!<var>display</var>` (meaning "display the label in the specified `<var>display</var>` form").
* `label`: The formatted form of the label. This is what is actually shown to the user. If the label is recognized
(found in some module), this will typically be in the form of a link.
* `categories`: A list of the categories to add the label to; an empty list if `nocat` was specified.
* `formatted_categories`: A string containing the formatted categories; {nil} if `nocat` or `for_doc` was specified,
or if `categories` is empty. Currently will be an empty string if there are categories to format but the namespace is
one that normally excludes categories (e.g. userspace and discussion pages), and `force_cat` isn't specified.
* `deprecated`: True if the label is deprecated.
* `recognized`: If true, the label was found in some module.
* `data`: The data structure for the label, as fetched from the label modules. For unrecognized labels, this will
be an empty object.
]==]
function export.get_label_info(data)
if not data.label then
error("`data` must now be an object containing the params")
end
local mode = validate_mode(data.mode)
local ret = {categories = {}}
local label = data.label
local raw_label = label
ret.raw_label = raw_label
local override_display
if label:find("^!") then
label = label:gsub("^!", "")
override_display = label
elseif label:find("![^%s]") then
label, override_display = label:match("^(.-)!([^%s].*)$")
if not label then
error(("Internal error: This Lua pattern should never fail to match for label '%s'"):format(raw_label))
end
end
local non_canonical = label
ret.non_canonical = non_canonical
local deprecated = false
local labdata
local submodule
local data_langcode = data.lang and data.lang:getCode() or nil
local submodules_to_check = export.get_submodules(data.lang)
for _, submodule_to_check in ipairs(submodules_to_check) do
submodule = mw.loadData(submodule_to_check)
local this_labdata = submodule[label]
local resolved_label
if type(this_labdata) == "string" then
resolved_label = this_labdata
this_labdata = submodule[this_labdata]
if not this_labdata then
error(("Internal error: Label alias '%s' points to '%s', which is undefined in module [[%s]]"):format(
label, resolved_label, submodule_to_check))
end
if type(this_labdata) == "string" then
error(("Internal error: Label alias '%s' points to '%s', which is also an alias (of '%s') in module [[%s]]"):format(
label, resolved_label, this_labdata, submodule_to_check))
end
end
if this_labdata then
-- Make sure either there's no lang restriction, or we're processing lang-independent, or our language
-- is among the listed languages. Otherwise, continue processing (which could conceivably pick up a
-- lang-appropriate version of the label in another label data module).
local lablangs = getprop(this_labdata, mode, "langs")
if not lablangs or not data_langcode then
labdata = this_labdata
label = resolved_label or label
break
end
local lang_in_list = false
for _, langcode in ipairs(lablangs) do
if langcode == data_langcode then
lang_in_list = true
break
end
end
if lang_in_list then
labdata = this_labdata
label = resolved_label or label
break
elseif not data.notrack then
-- Track use of a label that fails the lang restriction.
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label/LANGCODE]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label/LABEL]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/wrong-lang-label/LABEL/LANGCODE]]
track("wrong-lang-label", data_langcode)
track("wrong-lang-label/" .. label, data_langcode)
if resolved_label then
track("wrong-lang-label/" .. resolved_label, data_langcode)
end
end
end
end
if labdata then
ret.recognized = true
else
labdata = {}
ret.recognized = false
end
local function labprop(prop)
return getprop(labdata, mode, prop)
end
if labprop("deprecated") then
deprecated = true
end
if label ~= non_canonical then
-- Note that this is an alias and store the canonical version.
ret.canonical = label
end
if not data.notrack then -- labprop("track") then -- track all labels now
-- Track label (after converting aliases to canonical form; but also track raw label (alias) if different
-- from canonical label).
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/label/LABEL]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/label/LABEL/LANGCODE]]
-- [[Special:WhatLinksHere/Wiktionary:Tracking/labels/label/LABEL/MODE]]
track("label/" .. label, data_langcode, mode)
if label ~= non_canonical then
track("label/" .. non_canonical, data_langcode, mode)
end
end
local formatted_label
formatted_label, deprecated = export.format_label(label, labdata, data.lang, deprecated, override_display, mode)
ret.deprecated = deprecated
if deprecated then
if not data.nocat then
local depcat = "Entries with deprecated labels"
if data.for_doc then
depcat = "<code>" .. depcat .. "</code>"
end
insert(ret.categories, depcat)
end
end
local label_for_already_seen =
(labprop("topical_categories") or labprop("regional_categories")
or labprop("plain_categories") or labprop("pos_categories")
or labprop("sense_categories")) and formatted_label
or nil
-- Track label text. If label text was previously used, don't show it, but include the categories.
-- For an example, see [[hypocretin]].
if data.already_seen and data.already_seen[label_for_already_seen] then
ret.label = ""
else
if formatted_label:find("{") then
formatted_label = mw.getCurrentFrame():preprocess(formatted_label)
end
ret.label = formatted_label
end
if data.nocat then
-- do nothing
else
local cats = export.fetch_categories(label, labdata, data.lang, mode, data.for_doc)
for _, cat in ipairs(cats) do
insert(ret.categories, cat)
end
if not ret.categories[1] or data.for_doc then
-- Don't try to format categories if we're doing this for documentation ({{label/doc}}), because there
-- will be HTML in the categories.
-- do nothing
else
ret.formatted_categories = require(utilities_module).format_categories(ret.categories, data.lang,
data.sort, nil, force_cat or data.force_cat)
end
end
ret.data = labdata
if label_for_already_seen and data.already_seen then
data.already_seen[label_for_already_seen] = true
end
return ret
end
--[==[
Split a string containing comma-separated raw labels into the individual labels. This will not split on a comma
followed by whitespace, and it will not split inside of matched <...> or [...]. The code is written to be efficient, so
that it does not load modules (e.g. [[Module:parse utilities]]) unnecessarily.
]==]
function export.split_labels_on_comma(term)
if term:find("[%[<]") then
-- Do it the "hard way". We don't want to split anything inside of <...> or <<...>> even if there are commas
-- inside of the angle brackets. For good measure we do the same for [...] and [[...]]. We first parse balanced
-- segment runs involving either [...] or <...>. Then we split alternating runs on comma (but not on
-- comma+whitespace). Then we rejoin the split runs. For example, given the following:
-- "regional,older <<non-rhotic,and,non-hoarse-horse>> speakers", the first call to
-- parse_multi_delimiter_balanced_segment_run() produces
--
-- {"regional,older ", "<<non-rhotic,and,non-hoarse-horse>>", " speakers"}
--
-- After calling split_alternating_runs_on_comma(), we get the following:
--
-- {{"regional"}, {"older ", "<<non-rhotic,and,non-hoarse-horse>>", " speakers"}}
--
-- After rejoining each group, we get:
--
-- {"regional", "older <<non-rhotic,and,non-hoarse-horse>> speakers"}
--
-- which is the desired output. When processing the second "label" string, the code in process_raw_labels()
-- will do a similar process to this to pull out the labels inside of the <<...>> notation.
local put = require(parse_utilities_module)
local segments = put.parse_multi_delimiter_balanced_segment_run(term, {{"<", ">"}, {"[", "]"}})
-- This won't split on comma+whitespace.
local comma_separated_groups = put.split_alternating_runs_on_comma(segments)
for i, group in ipairs(comma_separated_groups) do
comma_separated_groups[i] = concat(group)
end
return comma_separated_groups
elseif term:find(",%s") then
-- This won't split on comma+whitespace.
return require(parse_utilities_module).split_on_comma(term)
elseif term:find(",") then
return require(string_utilities_module).split(term, ",")
else
return {term}
end
end
--[==[
Return a list of objects corresponding to a set of raw labels. Each object returned is of the format returned by
{get_label_info()}. This is similar to looping over the labels and calling {get_label_info()} on each one, but it also
correctly handles embedded double angle bracket specs <<...>> found in the labels. (In such a case, there will be more
objects returned than raw labels passed in.) On input, `data` is an object with the following fields:
* `labels`: The list of labels to process.
* `lang`: The language of the labels. Must be specified.
* `mode`: How the label was invoked; see {get_label_info()} for more information.
* `nocat`: If true, don't add the label to any categories.
* `force_cat`: Force adding categories even in namespaces that normally exclude them (e.g. userspace and discussion
pages).
* `notrack`: Disable all tracking for this label.
* `sort`: Sort key for categorization.
* `already_seen`: An object used to track labels already seen, so they aren't displayed twice. Tracking is according
to the display form of the label, so if two labels have the same display form, the second one won't be displayed
(but its categories will still be added). If `already_seen` is {nil}, this tracking doesn't happen.
* `ok_to_destructively_modify`: If set, the `data` structure will be destructively modified in the process of this
function running.
]==]
function export.process_raw_labels(data)
local label_infos = {}
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.ok_to_destructively_modify = true
end
local function get_info_and_insert(label)
-- Reuse this structure to save memory.
data.label = label
insert(label_infos, export.get_label_info(data))
end
for _, label in ipairs(data.labels) do
if label:find("<<") then
local segments = require(string_utilities_module).split(label, "<<(.-)>>")
for i, segment in ipairs(segments) do
if i % 2 == 1 then
local raw_text_type = i == 1 and "begin" or i == #segments and "end" or "middle"
insert(label_infos, {raw_text = raw_text_type, label = segment, categories = {}})
else
local segment_labels = export.split_labels_on_comma(segment)
for _, segment_label in ipairs(segment_labels) do
get_info_and_insert(segment_label)
end
end
end
else
get_info_and_insert(label)
end
end
return label_infos
end
--[==[
Split a comma-separated string of raw labels and process each label to get a list of objects suitable for passing to
{format_processed_labels()}. Each object returned is of the format returned by {get_label_info()}. This is equivalent to
calling {split_labels_on_comma()} followed by {process_raw_labels()}. On input, `data` is an object with the following
fields:
* `labels`: The string containing the raw comma-separated labels.
* `lang`: The language of the labels. Must be specified.
* `mode`: How the label was invoked; see {get_label_info()} for more information.
* `nocat`: If true, don't add the label to any categories.
* `force_cat`: Force adding categories even in namespaces that normally exclude them (e.g. userspace and discussion
pages).
* `notrack`: Disable all tracking for this label.
* `sort`: Sort key for categorization.
* `already_seen`: An object used to track labels already seen, so they aren't displayed twice. Tracking is according
to the display form of the label, so if two labels have the same display form, the second one won't be displayed
(but its categories will still be added). If `already_seen` is {nil}, this tracking doesn't happen.
* `ok_to_destructively_modify`: If set, the `data` structure will be destructively modified in the process of this
function running.
]==]
function export.split_and_process_raw_labels(data)
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.ok_to_destructively_modify = true
end
data.labels = export.split_labels_on_comma(data.labels)
return export.process_raw_labels(data)
end
--[==[
Format one or more already-processed labels for display and categorization. "Already-processed" means that
{get_label_info()} or {process_raw_labels()} has been called on the raw labels to convert them into objects containing
information on how to display and categorize the labels. This is a lower-level alternative to {show_labels()} and is
meant for modules such as [[Module:alternative forms]], [[Module:quote]] and [[Module:etymology/templates/descendant]]
that support displaying labels along with some other information.
On input `data` is an object with the following fields:
* `labels`: List of the label objects to format, in the format returned by {get_label_info()}.
* `lang`: The language of the labels.
* `open`: Open bracket or parenthesis to display before the concatenated labels. If specified, it is wrapped in the
{"ib-brac"} and {"label-brac"} CSS classes. If {nil} or {false}, no open bracket is displayed.
* `close`: Close bracket or parenthesis to display after the concatenated labels. If specified, it is wrapped in the
{"ib-brac"} and {"label-brac"} CSS classes. If {nil} or {false}, no close bracket is displayed.
* `no_ib_content`: By default, the concatenated formatted labels inside of the open/close brackets are wrapped in the
{"ib-content"} and {"label-content"} CSS classes. Specify this to suppress this wrapping.
* `raw`: Suppress all CSS wrapping of content, including open/close parentheses, content and comma delimiters (which
are normally wrapped in {"ib-comma"} and {"label-comma"} CSS classes).
* `ok_to_destructively_modify`: If set, the `data` structure, and the `data.labels` table inside of it, will be
destructively modified in the process of this function running.
* `split_output`: If not given, the return value is a concatenation of the formatted concatenated labels and formatted
categories. Otherwise, two values are returned: the formatted pronunciation and the categories. If `split_output` is
the value {"raw"}, the categories are returned in list form, where the list elements are strings f the form suitable
for passing to {format_categories()} in [[Module:utilities]]. If `split_output` is any other value besides {nil}, the
categories are returned as a pre-formatted concatenated string.
The return value (or the first return value, if `split_output` is given) is a string containing the contenated labels,
optionally surrounded by open/close brackets or parentheses. Normally, labels are separated by comma-space sequences,
but this may be suppressed for certain labels. If `nocat` wasn't given to {get_label_info()} or {process_raw_labels()},
and `split_output` wasn't given, the label objects will contain formatted categories in them, which will be inserted
into the returned text. (Use `split_output` if you need the categories returned separately.) The concatenated text
inside of the open/close brackets is normally wrapped in the {"ib-content"} CSS class, but this can be suppressed, as
mentioned above.
]==]
function export.format_processed_labels(data)
if not data.labels then
error("`data` must now be an object containing the params")
end
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.labels = m_table.deepCopy(data.labels)
data.ok_to_destructively_modify = true
end
local labels = data.labels
if not labels[1] then
error("You must specify at least one label.")
end
local split_output = data.split_output
-- Show the labels
local omit_preComma
local omit_preSpace
local omit_postComma = true
local omit_postSpace = true
for _, label in ipairs(labels) do
omit_preComma = omit_postComma
omit_preSpace = omit_postSpace
local raw_text_omit_before = label.raw_text == "middle" or label.raw_text == "end"
local raw_text_omit_after = label.raw_text == "middle" or label.raw_text == "begin"
label.omit_comma = omit_preComma or (label.data and label.data.omit_preComma) or raw_text_omit_before
omit_postComma = (label.data and label.data.omit_postComma) or raw_text_omit_after
label.omit_space = omit_preSpace or (label.data and label.data.omit_preSpace) or raw_text_omit_before
omit_postSpace = (label.data and label.data.omit_postSpace) or raw_text_omit_after
end
if data.lang then
local lang_functions_module = export.lang_specific_data_modules_prefix .. data.lang:getCode() .. "/functions"
local m_lang_functions = require(load_module).safe_require(lang_functions_module)
if m_lang_functions and m_lang_functions.postprocess_handlers then
for _, handler in ipairs(m_lang_functions.postprocess_handlers) do
handler(data)
end
end
end
local function wrap_css(txt, suffix)
if data.raw then
return txt
end
return ("<span class=\"ib-%s label-%s\">%s</span>"):format(suffix, suffix, txt)
end
local categories = nil
local formatted_categories = split_output and split_output ~= "raw" and {} or nil
for i, labelinfo in ipairs(labels) do
local label
-- Need to check for 'not raw_text' here because blank labels may legitimately occur as raw text if a double
-- angle bracket spec occurs at the beginning of a label. In this case we've already taken into account the
-- context and don't want to leave out a preceding comma and space e.g. in a case like
-- {{lb|en|rare|<<dialect>> or <<eye dialect>>}}. FIXME: We should reconsider whether we need this special case
-- at all.
if labelinfo.label == "" and not labelinfo.raw_text then
label = ""
else
label = (labelinfo.omit_comma and "" or wrap_css(",", "comma")) ..
(labelinfo.omit_space and "" or " ") ..
labelinfo.label
end
if split_output then
labels[i] = label
if split_output == "raw" then
if labelinfo.categories and labelinfo.categories[1] then
if categories then
m_table.extend(categories, labelinfo.categories)
else
categories = labelinfo.categories
end
end
elseif labelinfo.formatted_categories then
insert(formatted_categories, labelinfo.formatted_categories)
end
else
labels[i] = label .. (labelinfo.formatted_categories or "")
end
end
local function wrap_open_close(val)
if val then
return wrap_css(val, "brac")
else
return ""
end
end
local concatenated_labels = concat(labels, "")
if not data.no_ib_content then
concatenated_labels = wrap_css(concatenated_labels, "content")
end
local ret_labels = wrap_open_close(data.open) .. concatenated_labels .. wrap_open_close(data.close)
if split_output == "raw" then
return ret_labels, categories
elseif split_output then
return ret_labels, concat(formatted_categories)
else
return ret_labels
end
end
--[==[
Format one or more labels for display and categorization. This provides the implementation of the
{{tl|label}}/{{tl|lb}}, {{tl|term label}}/{{tl|tlb}} and {{tl|accent}}/{{tl|a}} templates, and can also be called from a
module. The return value is a string to be inserted into the generated page, including the display and categories. On
input `data` is an object with the following fields:
* `labels`: List of the labels to format.
* `lang`: The language of the labels.
* `mode`: How the label was invoked; see {get_label_info()} for more information.
* `nocat`: If true, don't add the labels to any categories.
* `force_cat`: Force adding categories even in namespaces that normally exclude them (e.g. userspace and discussion
pages).
* `notrack`: Disable all tracking for these labels.
* `sort`: Sort key for categorization.
* `no_track_already_seen`: Don't track already-seen labels. If not specified, already-seen labels are not displayed
again, but still categorize. See the documentation of {get_label_info()}.
* `open`: Open bracket or parenthesis to display before the concatenated labels. If {nil}, defaults to an open
parenthesis. Set to {false} to disable.
* `close`: Close bracket or parenthesis to display after the concatenated labels. If {nil}, defaults to a close
parenthesis. Set to {false} to disable.
* `no_ib_content`: As in `format_processed_labels()`.
* `raw`: As in `format_processed_labels()`. Also suppress wrapping the entire formatted result in a usage label CSS
class (see below).
* `ok_to_destructively_modify`: If set, the `data` structure will be destructively modified in the process of this
function running.
Compared with {format_processed_labels()}, this function has the following differences:
# The labels specified in `labels` are raw labels (i.e. strings) rather than formatted objects.
# The open and close brackets default to parentheses ("round brackets") rather than not being displayed by default.
# Tracking of already-seen labels is enabled unless explicitly turned off using `no_track_already_seen`.
# The entire formatted result is wrapped in a {"usage-label-<var>type</var>"} CSS class (depending on the value of
`mode`), unless `raw` is given.
]==]
function export.show_labels(data)
if not data.labels then
error("`data` must now be an object containing the params")
end
if not data.ok_to_destructively_modify then
data = m_table.shallowCopy(data)
data.ok_to_destructively_modify = true
end
local labels = data.labels
if not labels[1] then
error("You must specify at least one label.")
end
local mode = validate_mode(data.mode)
if not data.no_track_already_seen then
data.already_seen = {}
end
data.labels = export.process_raw_labels(data)
if data.open == nil then
data.open = "("
end
if data.close == nil then
data.close = ")"
end
local formatted = export.format_processed_labels(data)
if data.raw then
return formatted
else
return "<span class=\"" .. mode_to_outer_class[mode] .. "\">" .. formatted .. "</span>"
end
end
--[==[Helper function for the data modules.]==]
function export.alias(labels, key, aliases)
m_table.alias(labels, key, aliases)
end
--[==[
Split the display form of a label. Returns two values: `link` and `display`. If the display form consists of a
two-part link, `link` is the first part and `display` is the second part. If the display form consists of a
single-part link, `link` and `display` are the same. Otherwise (the display form is not a link or contains an
embedded link), `link` is the same as the passed-in `label` and `display` is nil.
]==]
function export.split_display_form(label)
if not label:find("%[%[") then
return label, nil
end
local link, display = label:match("^%[%[([^%[%]|]+)|([^%[%]|]+)%]%]$")
if link then
return link, display
end
link = label:match("^%[%[([^%[%]|])+%]%]$")
if link then
return link, link
end
return label, nil
end
--[==[
Combine the `link` and `display` parts of the display form of a label as returned by {split_display_form()}.
If `display` is nil, `link` is returned directly. Otherwise, a one-part or two-part link is constructed
depending on whether `link` and `display` are the same. (As a special case, if both consist of a blank string,
the return value is a blank string rather than a malformed link.)
]==]
function export.combine_display_form_parts(link, display)
if not display then
return link
end
if link == display then
if link == "" then
return ""
else
return ("[[%s]]"):format(link)
end
end
return ("[[%s|%s]]"):format(link, display)
end
--[==[Used to finalize the data into the form that is actually returned.]==]
function export.finalize_data(labels)
local shallow_copy = m_table.shallowCopy
local aliases = {}
for label, data in pairs(labels) do
if type(data) == "table" then
if data.aliases then
for _, alias in ipairs(data.aliases) do
aliases[alias] = label
end
data.aliases = nil
end
if data.deprecated_aliases then
local data2 = shallow_copy(data)
data2.deprecated = true
data2.canonical = label
for _, alias in ipairs(data2.deprecated_aliases) do
aliases[alias] = data2
end
data.deprecated_aliases = nil
data2.deprecated_aliases = nil
end
end
end
for label, data in pairs(aliases) do
labels[label] = data
end
return labels
end
return export
b5g3rb91z6k1folvf0qlc1gqgh8low4
Module:labels/data/lang
828
8095
54970
2026-09-30T17:37:50Z
Deadend0914
7211
Gesceop tramet þe hafaþ '-- Table listing all of the languages with lang-specific labels modules. local langs_with_lang_specific_modules = { ["ab"] = true, ["acm"] = true, ["ady"] = true, ["ae"] = true, ["af"] = true, ["afb"] = true, ["aht"] = true, ["aii"] = true, ["ain"] = true, ["ajp"] = true, ["ak"] = true, ["akk"] = true, ["amf"] = true, ["an"] = true, ["ang"] = true, ["apc"] = true, ["ar"] = true, ["arc"] = true, ["arq"] = true, ["arz"] = true, ["as"] = true,...'
54970
Scribunto
text/plain
-- Table listing all of the languages with lang-specific labels modules.
local langs_with_lang_specific_modules = {
["ab"] = true,
["acm"] = true,
["ady"] = true,
["ae"] = true,
["af"] = true,
["afb"] = true,
["aht"] = true,
["aii"] = true,
["ain"] = true,
["ajp"] = true,
["ak"] = true,
["akk"] = true,
["amf"] = true,
["an"] = true,
["ang"] = true,
["apc"] = true,
["ar"] = true,
["arc"] = true,
["arq"] = true,
["arz"] = true,
["as"] = true,
["ast"] = true,
["av"] = true,
["ayl"] = true,
["az"] = true,
["bar"] = true,
["bcl"] = true,
["be"] = true,
["bew"] = true,
["bg"] = true,
["bho"] = true,
["bn"] = true,
["bo"] = true,
["br"] = true,
["bsh"] = true,
["bua"] = true,
["byk"] = true,
["ca"] = true,
["car"] = true,
["cbk"] = true,
["ceb"] = true,
["cel-pro"] = true,
["ch"] = true,
["cho"] = true,
["chr"] = true,
["cim"] = true,
["ckb"] = true,
["cop"] = true,
["cpg"] = true,
["cpi"] = true,
["crh"] = true,
["crp-cpr"] = true,
["cs"] = true,
["csb"] = true,
["cu"] = true,
["cy"] = true,
["da"] = true,
["dcc"] = true,
["de"] = true,
["dlm"] = true,
["dng"] = true,
["dnj"] = true,
["dum"] = true,
["dv"] = true,
["egl"] = true,
["egy"] = true,
["el"] = true,
["en"] = true,
["enm"] = true,
["es"] = true,
["et"] = true,
["eu"] = true,
["evn"] = true,
["fa"] = true,
["fax"] = true,
["ff"] = true,
["fi"] = true,
["fo"] = true,
["fr"] = true,
["fro"] = true,
["frp"] = true,
["frr"] = true,
["fur"] = true,
["fy"] = true,
["ga"] = true,
["gcf"] = true,
["gd"] = true,
["gem-pro"] = true,
["gl"] = true,
["gmq-oda"] = true,
["gmq-pro"] = true,
["gmw-bgh"] = true,
["gmw-cfr"] = true,
["gmw-ecg"] = true,
["gmw-pro"] = true,
["gmw-rfr"] = true,
["gmy"] = true,
["goh"] = true,
["grc"] = true,
["grk-ita"] = true,
["gsw"] = true,
["gu"] = true,
["gug"] = true,
["guw"] = true,
["ha"] = true,
["haa"] = true,
["haw"] = true,
["he"] = true,
["hi"] = true,
["hit"] = true,
["hrx"] = true,
["hsb"] = true,
["ht"] = true,
["hu"] = true,
["hy"] = true,
["id"] = true,
["idb"] = true,
["ilo"] = true,
["inc-apa"] = true,
["inc-ash"] = true,
["inc-ohi"] = true,
["is"] = true,
["it"] = true,
["iu"] = true,
["izh"] = true,
["ja"] = true,
["jbo"] = true,
["jje"] = true,
["jut"] = true,
["jv"] = true,
["ka"] = true,
["kbd"] = true,
["kca-eas"] = true,
["kca-nor"] = true,
["kca-sou"] = true,
["kea"] = true,
["kho"] = true,
["kix"] = true,
["klj"] = true,
["kls"] = true,
["kmr"] = true,
["kn"] = true,
["kne"] = true,
["ko"] = true,
["kok"] = true,
["kpt"] = true,
["kpv"] = true,
["krc"] = true,
["krl"] = true,
["kry"] = true,
["kw"] = true,
["kzw"] = true,
["la"] = true,
["lad"] = true,
["li"] = true,
["lij"] = true,
["lis"] = true,
["liv"] = true,
["lld"] = true,
["lmo"] = true,
["lrl"] = true,
["lt"] = true,
["lv"] = true,
["lzz"] = true,
["mak"] = true,
["mch"] = true,
["mco"] = true,
["mh"] = true,
["mhd"] = true,
["mic"] = true,
["mk"] = true,
["ml"] = true,
["mlm"] = true,
["mn"] = true,
["mns-cen"] = true,
["mns-nor"] = true,
["mns-sou"] = true,
["moh"] = true,
["mr"] = true,
["ms"] = true,
["mt"] = true,
["mui"] = true,
["mul"] = true,
["mus"] = true,
["mvi"] = true,
["mwl"] = true,
["my"] = true,
["nap"] = true,
["nb"] = true,
["nds"] = true,
["ne"] = true,
["new"] = true,
["nhn"] = true,
["nhx"] = true,
["niv"] = true,
["nl"] = true,
["nn"] = true,
["non"] = true,
["nrf"] = true,
["nrn"] = true,
["nv"] = true,
["oc"] = true,
["oj"] = true,
["oko"] = true,
["okz"] = true,
["onb"] = true,
["os"] = true,
["osc"] = true,
["ota"] = true,
["otk"] = true,
["pa"] = true,
["pam"] = true,
["paw"] = true,
["peh"] = true,
["phl"] = true,
["pl"] = true,
["pms"] = true,
["pnt"] = true,
["poz-pro"] = true,
["ppl"] = true,
["pra"] = true,
["ps"] = true,
["pt"] = true,
["qu"] = true,
["qwc"] = true,
["qwm"] = true,
["rcf"] = true,
["rgn"] = true,
["rm"] = true,
["rmc"] = true,
["rml"] = true,
["rmn"] = true,
["rmy"] = true,
["ro"] = true,
["roa-bbn"] = true,
["roa-leo"] = true,
["roa-ona"] = true,
["roa-opt"] = true,
["rom"] = true,
["rsk"] = true,
["ru"] = true,
["rue"] = true,
["rup"] = true,
["rw"] = true,
["rys"] = true,
["ryu"] = true,
["sa"] = true,
["saj"] = true,
["sc"] = true,
["scl"] = true,
["scn"] = true,
["sco"] = true,
["sd"] = true,
["se"] = true,
["sel-sou"] = true,
["sgh"] = true,
["sh"] = true,
["shi"] = true,
["sjd"] = true,
["sjs"] = true,
["sk"] = true,
["skr"] = true,
["sl"] = true,
["sla-pro"] = true,
["smi-pro"] = true,
["sn"] = true,
["sq"] = true,
["srn"] = true,
["su"] = true,
["sux"] = true,
["sv"] = true,
["sw"] = true,
["szl"] = true,
["ta"] = true,
["taa"] = true,
["tay"] = true,
["te"] = true,
["tet"] = true,
["tfn"] = true,
["tg"] = true,
["th"] = true,
["tkr"] = true,
["tl"] = true,
["tmh"] = true,
["tpw"] = true,
["tr"] = true,
["trk-pro"] = true,
["tsg"] = true,
["tt"] = true,
["tup-kaw"] = true,
["tuw-sol"] = true,
["tyv"] = true,
["udi"] = true,
["udm"] = true,
["uk"] = true,
["ur"] = true,
["urj-fin-pro"] = true,
["uz"] = true,
["vot"] = true,
["war"] = true,
["vec"] = true,
["vi"] = true,
["vo"] = true,
["xcl"] = true,
["xh"] = true,
["xme-ker"] = true,
["xmf"] = true,
["xpg"] = true,
["xqa"] = true,
["xum"] = true,
["ycr"] = true,
["yi"] = true,
["yo"] = true,
["yok-bvy"] = true,
["yok-dly"] = true,
["yok-kry"] = true,
["yok-nvy"] = true,
["yok-svy"] = true,
["yok-tky"] = true,
["yrk-tun"] = true,
["yrl"] = true,
["za"] = true,
["xbo"] = true,
["zh"] = true,
["zle-ono"] = true,
["zle-ort"] = true,
["zls-chs"] = true,
["zlw-ocs"] = true,
["zlw-opl"] = true,
["zlw-osk"] = true,
["zlw-slv"] = true,
["zu"] = true,
}
return {
langs_with_lang_specific_modules = langs_with_lang_specific_modules,
}
021olkkfh2bsnz6mw7ra0p6nehnwb1s
Module:labels/data
828
8096
54971
2026-09-30T17:38:52Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local labels = {} -- Grammatical labels labels["abbreviation"] = { aliases = {"abbreviations", "abbreviated"}, glossary = true, pos_categories = "abbreviations", } labels["abstract noun"] = { display = "abstract", glossary = true, pos_categories = "abstract nouns", } labels["abstract verb"] = { display = "abstract", glossary = true, pos_categories = "abstract verbs", } labels["acronym"] = { glossary = true, pos_categories = "acronyms", } labels["...'
54971
Scribunto
text/plain
local labels = {}
-- Grammatical labels
labels["abbreviation"] = {
aliases = {"abbreviations", "abbreviated"},
glossary = true,
pos_categories = "abbreviations",
}
labels["abstract noun"] = {
display = "abstract",
glossary = true,
pos_categories = "abstract nouns",
}
labels["abstract verb"] = {
display = "abstract",
glossary = true,
pos_categories = "abstract verbs",
}
labels["acronym"] = {
glossary = true,
pos_categories = "acronyms",
}
labels["active voice"] = {
aliases = {"active", "in active", "in the active", "in active voice", "in the active voice"},
glossary = true,
}
labels["ambitransitive"] = {
aliases = {"ambitransitively"},
glossary = true,
pos_categories = {"transitive verbs", "intransitive verbs"},
}
labels["angry register"] = {
aliases = {"angry", "anger", "said in anger"},
glossary = true,
pos_categories = "angry register terms",
}
labels["animate"] = {
glossary = true,
}
labels["indicative"] = {
aliases = {"in the indicative", "indicative mood"},
glossary = "indicative mood",
}
labels["subjunctive"] = {
aliases = {"in the subjunctive", "subjunctive mood"},
glossary = "subjunctive mood",
}
labels["imperative"] = {
aliases = {"in the imperative", "imperative mood"},
glossary = "imperative mood",
}
labels["jussive"] = {
aliases = {"in the jussive", "jussive mood"},
glossary = "jussive mood",
}
labels["atelic"] = {
glossary = true,
}
labels["attenuative"] = {
pos_categories = "attenuative verbs",
}
labels["attributive"] = {
glossary = true,
}
labels["attributively"] = {
glossary = "attributive",
}
labels["auxiliary"] = {
glossary = true,
pos_categories = "auxiliary verbs",
}
labels["cardinal"] = {
aliases = {"cardinal number", "cardinal numeral"},
display = "[[cardinal number]]",
pos_categories = "cardinal numbers",
}
labels["catenative"] = {
glossary = "catenative verb",
}
labels["causative"] = {
glossary = true,
}
labels["causative verb"] = {
display = "causative",
glossary = true,
pos_categories = "causative verbs",
}
labels["cognate object"] = {
aliases = {"with cognate object"},
display = "with [[w:Cognate object|cognate object]]",
pos_categories = "verbs used with cognate objects",
}
labels["collective"] = {
glossary = true,
display = "collective",
pos_categories = "collective nouns",
}
labels["collectively"] = {
glossary = "collective",
display = "collectively",
pos_categories = "collective nouns",
}
labels["collective number"] = {
aliases = {"collective numeral"},
display = "[[collective number]]",
pos_categories = "collective numbers",
}
labels["common"] = {
glossary = true,
}
labels["comparable"] = {
glossary = true,
}
labels["completive"] = {
pos_categories = "completive verbs",
}
labels["concrete verb"] = {
display = "concrete",
Wiktionary = true,
pos_categories = "concrete verbs",
}
labels["contraction"] = {
aliases = {"contractions", "contracted"},
glossary = true,
pos_categories = "contractions",
}
labels["control verb"] = {
aliases = {"control"},
Wikipedia = true,
pos_categories = "control verbs",
}
labels["copulative"] = {
aliases = {"copular"},
glossary = true,
pos_categories = "copulative verbs",
}
labels["countable"] = {
glossary = true,
pos_categories = "countable nouns",
}
labels["cumulative"] = {
pos_categories = "cumulative verbs",
}
labels["defective adjective"] = {
aliases = {"defective a", "defective adj", "defective adjectives"},
display = "defective",
glossary = true,
pos_categories = "defective adjectives",
}
labels["defective noun"] = {
aliases = {"defective n", "defective nouns"},
display = "defective",
glossary = true,
pos_categories = "defective nouns",
}
labels["defective verb"] = {
aliases = {"defective v", "defective vb", "defective verbs"},
display = "defective",
glossary = true,
pos_categories = "defective verbs",
}
labels["definite"] = {
aliases = {"def"},
glossary = true,
}
labels["deliberate misspelling"] = {
deprecated_aliases = {"deliberate mispelling"},
display = "deliberate [[misspelling]]",
}
labels["delimitative"] = {
pos_categories = "delimitative verbs",
}
labels["deponent"] = {
glossary = true,
pos_categories = "deponent verbs",
}
labels["distributive"] = {
pos_categories = "distributive verbs",
}
labels["distributive number"] = {
aliases = {"distributive numeral"},
display = "[[distributive number]]",
pos_categories = "distributive numbers",
}
labels["ditransitive"] = {
aliases = {"ditransitively"},
glossary = true,
pos_categories = "ditransitive verbs",
}
labels["dual only"] = {
aliases = {"dual-only", "dualonly", "duale tantum"},
display = "[[dual]] only",
pos_categories = "dualia tantum",
}
labels["dysphemistic"] = {
aliases = {"dysphemism"},
glossary = "dysphemism",
pos_categories = "dysphemisms",
}
labels["by ellipsis"] = {
aliases = {"ellipsis"},
glossary = "ellipsis",
pos_categories = "ellipses",
}
labels["elongated"] = {
aliases = {"elongation"},
glossary = true,
pos_categories = "elongated forms",
}
labels["emphatic"] = {
glossary = true,
}
labels["ergative"] = {
glossary = true,
pos_categories = "ergative verbs",
}
labels["expressive"] = {
glossary = true,
pos_categories = "expressive terms",
}
labels["by extension"] = {
aliases = {"hence"},
}
labels["feminine"] = {
glossary = true,
}
labels["focus"] = {
glossary = true,
pos_categories = "focus adverbs",
}
labels["fractional"] = {
aliases = {"fractional number", "fractional numeral"},
display = "[[fractional number]]",
pos_categories = "fractional numbers",
}
labels["frequentative"] = {
glossary = true,
pos_categories = "frequentative verbs",
}
labels["hedge"] = {
aliases = {"hedges"},
glossary = true,
pos_categories = "hedges",
}
labels["ideophonic"] = {
aliases = {"ideophone"},
glossary = true,
}
labels["idiomatic"] = {
aliases = {"idiom", "idiomatically"},
glossary = true,
pos_categories = "idioms",
}
labels["imperfect"] = {
glossary = true,
}
labels["imperfective"] = {
glossary = true,
pos_categories = "imperfective verbs",
}
labels["impersonal"] = {
glossary = "impersonal verb",
pos_categories = "impersonal verbs",
}
labels["in the singular"] = {
aliases = {"in singular"},
display = "in the [[singular]]",
}
labels["in the dual"] = {
aliases = {"in dual"},
display = "in the [[dual]]",
}
labels["in the plural"] = {
aliases = {"in plural"},
display = "in the [[Appendix:Glossary#plural|plural]]",
}
labels["inanimate"] = {
aliases = {"not animate"},
glossary = true,
}
labels["inchoative"] = {
glossary = true,
pos_categories = "inchoative verbs",
}
labels["indefinite"] = {
aliases = {"indef", "not definite"},
glossary = true,
}
labels["initialism"] = {
glossary = true,
pos_categories = "initialisms",
}
labels["intensive verb"] = {
display = "intensive",
pos_categories = "intensive verbs",
}
labels["intransitive"] = {
aliases = {"not transitive", "intransitively", "not transitively"},
glossary = true,
pos_categories = "intransitive verbs",
}
labels["IPA"] = {
aliases = {"International Phonetic Alphabet"},
Wikipedia = "International Phonetic Alphabet",
plain_categories = "IPA symbols",
}
labels["iterative"] = {
glossary = true,
pos_categories = "iterative verbs",
}
labels["litotes"] = {
aliases = {"litote", "litotic", "litotical"},
glossary = true,
pos_categories = true,
}
labels["masculine"] = {
glossary = true,
}
labels["mediopassive voice"] = {
aliases = {"mediopassive", "middle passive", "middle-passive", "in mediopassive", "in middle passive", "in middle-passive", "in the mediopassive", "in the middle passive", "in the middle-passive", "in mediopassive voice", "in middle passive voice", "in middle-passive voice", "in the mediopassive voice", "in the middle passive voice", "in the middle-passive voice"},
glossary = true,
}
labels["meiosis"] = {
aliases = {"meioses", "meiotic"},
glossary = true,
pos_categories = "meioses",
}
labels["middle voice"] = {
aliases = {"middle", "in middle", "in the middle", "in middle voice", "in the middle voice"},
glossary = true,
}
labels["misspelling"] = {
deprecated_aliases = {"mispelling"},
display = "[[misspelling]]",
}
labels["mnemonic"] = {
display = "[[mnemonic]]",
pos_categories = "mnemonics",
}
labels["modal"] = {
Wikipedia = "Modality (linguistics)",
}
labels["modal adverb"] = {
aliases = {"modal adverbs"},
display = "modal",
Wikipedia = "Modality (linguistics)",
pos_categories = "modal adverbs",
}
labels["modal verb"] = {
aliases = {"modal verbs"},
display = "modal",
Wikipedia = "Modality (linguistics)",
pos_categories = "modal verbs",
}
labels["NAPA"] = {
aliases = {"Americanist_phonetic_notation"},
Wikipedia = "Americanist_phonetic_notation",
plain_categories = "NAPA symbols",
}
labels["always in the negative"] = {
display = "always in the [[Appendix:Glossary#negative polarity item|negative]]",
pos_categories = "negative polarity items",
}
labels["chiefly in the negative"] = {
aliases = {"chiefly used in the negative", "negative polarity", "negative polarity item", "usually in the negative", "usually used in the negative"},
display = "chiefly in the [[Appendix:Glossary#negative polarity item|negative]]",
pos_categories = "negative polarity items",
}
labels["chiefly in the negative plural"] = {
aliases = {"chiefly used in the negative plural", "negative polarity plural", "negative polarity plural item", "usually in the negative plural", "usually used in the negative plural"},
display = "chiefly in the [[Appendix:Glossary#negative polarity item|negative]] [[Appendix:Glossary#plural|plural]]",
pos_categories = "negative polarity items",
}
labels["always in the positive"] = {
display = "always in the [[Appendix:Glossary#positive polarity item|positive]]",
-- pos_categories = {"positive polarity items"},
}
labels["chiefly in the positive"] = {
aliases = {"chiefly used in the positive", "positive polarity", "positive polarity item", "usually in the positive", "usually used in the positive"},
display = "chiefly in the [[Appendix:Glossary#positive polarity item|positive]]",
-- pos_categories = {"positive polarity items"},
}
labels["chiefly in the positive plural"] = {
aliases = {"chiefly used in the positive plural", "positive polarity plural", "positive polarity plural item", "usually in the positive plural", "usually used in the positive plural"},
display = "chiefly in the [[Appendix:Glossary#positive polarity item|positive]] [[Appendix:Glossary#plural|plural]]",
-- pos_categories = "positive polarity items",
}
labels["neuter"] = {
glossary = true,
}
-- British English ("ise")
labels["nominalised"] = {
aliases = {"nominalisation", "substantivised", "substantivisation"},
glossary = "nominalization",
pos_categories = "nominalized adjectives",
}
-- American English ("ize")
labels["nominalized"] = {
aliases = {"nominalization", "substantivized", "substantivization"},
glossary = "nominalization",
pos_categories = "nominalized adjectives",
}
labels["not comparable"] = {
aliases = {"notcomp", "incomparable", "uncomparable"},
glossary = "uncomparable",
}
labels["onomatopoeia"] = {
glossary = true,
pos_categories = "onomatopoeias",
}
labels["ordinal"] = {
aliases = {"ordinal number", "ordinal numeral"},
display = "[[ordinal number]]",
pos_categories = "ordinal numbers",
}
labels["partitive verb"] = {
display = "[[Appendix:Glossary#transitive|transitive]], usually [[Appendix:Finnic telic and atelic verbs|atelic]]",
pos_categories = "transitive verbs", -- = "partitive verbs",
}
labels["perfect"] = {
glossary = true,
}
labels["participle"] = {
glossary = true,
}
labels["passive voice"] = {
aliases = {"passive", "in passive", "in the passive", "in passive voice", "in the passive voice"},
glossary = true,
}
labels["perfect"] = {
glossary = true,
}
labels["perfective"] = {
glossary = true,
pos_categories = "perfective verbs",
}
labels["plural only"] = {
aliases = {"plural-only", "pluralonly", "plurale tantum"},
display = "[[Appendix:Glossary#plural|plural]] only",
pos_categories = "pluralia tantum",
}
labels["possessional adjective"] = {
aliases = {"possessional", "possessional adjectives"},
display = "possessional",
glossary = true,
pos_categories = "possessional adjectives",
}
labels["possessive pronoun"] = {
aliases = {"possessive determiner"},
display = "possessive",
glossary = "possessive determiner",
pos_categories = "possessive pronouns",
}
labels["postpositive"] = {
glossary = true,
}
labels["predicative"] = {
glossary = true,
}
labels["predicatively"] = {
glossary = "predicative",
}
labels["prepositive"] = {
glossary = true,
}
labels["prescriptive"] = {
aliases = {"normative", "prescribed"},
glossary = true,
}
labels["privative"] = {
pos_categories = "privative verbs",
}
labels["procedure word"] = {
display = "[[procedure word]]",
}
labels["productive"] = {
glossary = true,
}
-- TODO: This label is probably inappropriate for many languages
labels["pronominal"] = {
glossary = "pronominal verb",
}
labels["pronunciation spelling"] = {
glossary = true,
}
labels["pro-verb"] = {
Wikipedia = true,
}
labels["reciprocal"] = {
glossary = true,
pos_categories = "reciprocal verbs",
}
labels["reflexive"] = {
glossary = true,
pos_categories = "reflexive verbs",
}
labels["reflexive pronoun"] = {
glossary = "reflexive",
pos_categories = "reflexive pronouns",
}
labels["relational"] = {
glossary = true,
pos_categories = "relational adjectives",
}
labels["repetitive"] = {
pos_categories = "repetitive verbs",
}
labels["respelling"] = {
glossary = true,
}
labels["reversative"] = {
pos_categories = "reversative verbs",
}
labels["rhetorical question"] = {
glossary = true,
pos_categories = "rhetorical questions",
}
labels["rhotic"] = {
glossary = true,
}
labels["saturative"] = {
aliases = {"sative"},
pos_categories = "saturative verbs",
}
labels["semelfactive"] = {
glossary = true,
pos_categories = "semelfactive verbs",
}
labels["sentence adverb"] = {
display = "[[sentence adverb]]",
pos_categories = "sentence adverbs",
}
labels["set phrase"] = {
display = "[[set phrase]]",
}
labels["simile"] = {
glossary = true,
pos_categories = "similes",
}
labels["singular only"] = {
aliases = {"singular-only", "singulare tantum", "no plural"},
display = "singular only",
pos_categories = "singularia tantum",
}
labels["snowclone"] = {
glossary = true,
pos_categories = "snowclones",
}
labels["stative"] = {
aliases = {"stative verb"},
glossary = true,
pos_categories = "stative verbs",
}
labels["strictly"] = {
aliases = {"strict", "narrowly", "narrow"},
glossary = true,
}
labels["substantive"] = {
glossary = true,
track = true,
}
labels["terminative"] = {
pos_categories = "terminative verbs",
}
labels["transitive"] = {
aliases = {"transitively"},
glossary = true,
pos_categories = "transitive verbs",
}
labels["transmission error"] = {
Wiktionary = true,
}
labels["tritransitive"] = {
aliases = {"tritransitively"},
glossary = true,
pos_categories = "tritransitive verbs",
}
labels["unaccusative"] = {
aliases = {"not accusative"},
Wikipedia = "Unaccusative verb",
}
labels["uncountable"] = {
aliases = {"not countable"},
glossary = true,
pos_categories = "uncountable nouns",
}
labels["unergative"] = {
aliases = {"not ergative"},
Wikipedia = "Unergative verb",
}
labels["UPA"] = {
aliases = {"Uralic Phonetic Alphabet"},
Wikipedia = "Uralic Phonetic Alphabet",
plain_categories = "UPA symbols",
}
labels["usually plural"] = {
aliases = {"usually in the plural", "usually in plural"},
display = "usually in the [[Appendix:Glossary#plural|plural]]",
deprecated = true,
}
-- Usage labels
labels["4chan"] = {
aliases = {"4chan slang"},
display = "[[w:4chan|4chan]] {{glossary|slang}}",
pos_categories = "4chan slang",
}
labels["4chan lgbt"] = {
aliases = {"tttt"},
display = "[[w:4chan|4chan]] /lgbt/ {{glossary|slang}}",
pos_categories = "4chan /lgbt/ slang",
}
labels["ACG"] = {
display = "[[ACG]]",
-- see also "fandom slang"
pos_categories = "fandom slang",
}
labels["endearing"] = {
aliases = {"affectionate"},
display = "[[endearing]]",
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "endearing terms",
}
labels["endearing form"] = {
aliases = {"affectionate form"},
display = "[[endearing]]",
pos_categories = "endearing forms",
}
labels["pre-classical"] = {
aliases = {"Pre-classical", "pre-Classical", "Pre-Classical", "Preclassical", "preclassical", "ante-classical", "Ante-classical", "ante-Classical", "Ante-Classical", "Anteclassical", "anteclassical"},
display = "pre-Classical",
regional_categories = true,
}
labels["anti-LGBTQ slur"] = {
-- don't add aliases "homophobia" or "transphobia" because these could be topical categories
aliases = {"homophobic", "transphobic"},
display = "anti-[[LGBTQ]] [[slur]]",
pos_categories = "anti-LGBTQ slurs",
}
labels["archaic"] = {
aliases = {"antiquated"},
glossary = true,
sense_categories = true,
}
labels["archaic form"] = {
glossary = "archaic",
display = "archaic",
pos_categories = "archaic forms",
}
labels["Australian slang"] = {
display = "[[Australian]] {{glossary|slang}}",
regional_categories = "Australian",
plain_categories = true,
}
labels["avoidance"] = {
glossary = true,
}
labels["back slang"] = {
aliases = {"backslang", "back-slang"},
glossary = "backslang",
pos_categories = true,
}
labels["Bargoens"] = {
Wikipedia = true,
plain_categories = true,
}
labels["Braille"] = {
Wikipedia = true,
}
labels["British slang"] = {
aliases = {"UK slang"},
display = "[[British]] {{glossary|slang}}",
plain_categories = true,
}
labels["Cambridge University slang"] = {
aliases = {"University of Cambridge slang", "Cantab slang"},
display = "[[w:University of Cambridge|Cambridge University]] {{glossary|slang}}",
topical_categories = "Universities",
plain_categories = true,
}
labels["cant"] = {
aliases = {"argot", "cryptolect"},
display = "[[cant]]",
pos_categories = true,
}
labels["capitalized"] = {
aliases = {"capitalised"},
display = "[[capitalisation|capitalized]]",
}
labels["Castilianism"] = {
aliases = {"Hispanicism"},
display = "[[Castilianism]]",
}
labels["childish"] = {
aliases = {"baby talk", "child language", "infantile", "puerile"},
display = "[[childish]]",
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
pos_categories = "childish terms",
}
labels["chu Nom"] = {
display = "[[Vietnamese]] [[chữ Nôm]]",
plain_categories = "Vietnamese Han tu",
}
labels["Cockney rhyming slang"] = {
display = "[[Cockney rhyming slang]]",
plain_categories = true,
}
labels["colloquial"] = {
aliases = {"colloquially"},
glossary = true,
pos_categories = "colloquialisms",
}
-- FIXME! The following two are apparently for Persian but probably don't belong in this file.
labels["colloquial-um"] = {
glossary = "colloquial",
pos_categories = "colloquialisms containing sequence um",
}
labels["colloquial-un"] = {
glossary = "colloquial",
pos_categories = "colloquialisms containing sequence un",
}
labels["corporate jargon"] = {
aliases = {"business jargon", "corporatese", "businessese", "corporate speak", "business speak"},
display = "[[corporate]] [[jargon]]",
pos_categories = true,
}
labels["costermongers"] = {
aliases = {"coster", "costers", "costermonger", "costermongers back slang", "costermongers' back slang"},
display = "[[Appendix:Costermongers' back slang|costermongers]]",
plain_categories = "Costermongers' back slang",
}
labels["criminal slang"] = {
aliases = {"thieves' cant", "Thieves' Cant", "thieves cant", "thieves'", "thieves", "thieves' cant"}, -- Thieves' Cant is English-only, so defined in the English submodule; if other languages try to use it, it's just criminal slang
display = "[[criminal]] {{glossary|slang}}",
topical_categories = "Crime",
pos_categories = true,
}
labels["dated"] = {
aliases = {"old-fashioned"},
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "dated terms",
}
labels["dated form"] = {
aliases = {"old-fashioned form"},
glossary = "dated",
display = "dated",
pos_categories = "dated forms",
}
-- combine with previous?
labels["dated sense"] = {
glossary = "dated",
sense_categories = "dated",
}
labels["derogatory"] = {
aliases = {"pejorative", "derogative", "disparaging"},
display = "[[derogatory]]",
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "derogatory terms",
}
labels["derogatory form"] = {
aliases = {"pejorative form", "derogative form", "disparaging form"},
display = "[[derogatory]]",
pos_categories = "derogatory forms",
}
labels["dialect"] = {-- separated from "dialectal" so e.g. "obsolete|outside|the|_|dialect|of..." displays right
glossary = "dialectal",
pos_categories = "dialectal terms",
}
labels["dialectal"] = {
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "dialectal terms",
}
labels["dialectal form"] = {
glossary = "dialectal",
display = "dialectal",
pos_categories = "dialectal forms",
}
labels["dialects"] = {-- separated from "dialectal" so e.g. "obsolete|outside|dialects" displays right
glossary = "dialectal",
pos_categories = "dialectal terms",
}
labels["dis legomenon"] = {
display = "[[dis legomenon]]",
pos_categories = "dis legomena",
}
labels["dismissal"] = {
display = "[[dismissal]]",
pos_categories = "dismissals",
}
labels["drag slang"] = {
aliases = {"Drag Race slang"},
display = "[[drag]] {{glossary|slang}}",
pos_categories = "drag slang",
}
labels["ecclesiastical"] = {
display = "[[ecclesiastical#Adjective|ecclesiastical]]",
pos_categories = "ecclesiastical terms",
}
labels["ethnic slur"] = {
aliases = {"racial slur"},
display = "[[ethnic]] [[slur]]",
pos_categories = "ethnic slurs",
}
labels["euphemistic"] = {
aliases = {"euphemism"},
glossary = "euphemism",
pos_categories = "euphemisms",
}
labels["eye dialect"] = {
display = "[[eye dialect]]",
pos_categories = true,
}
labels["familiar"] = {
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
pos_categories = "familiar terms",
}
labels["fandom slang"] = {
aliases = {"fandom"},
display = "[[fandom]] {{glossary|slang}}",
pos_categories = true,
}
labels["figurative"] = {
aliases = {"metaphorical", "metaphoric", "metaphor"},
glossary = "figurative",
}
labels["figuratively"] = {
aliases = {"metaphorically"},
glossary = "figurative",
}
labels["folk songs"] = {
aliases = {"folksongs", "used in folk songs", "used in folksongs"},
pos_categories = "folk-poetic terms",
display = "used in [[folk song]]s",
}
labels["folk tales"] = {
aliases = {"folktales", "used in folk tales", "used in folktales"},
pos_categories = "folk-poetic terms",
display = "used in [[folk tale]]s",
}
labels["folk-poetic"] = {
-- should be "terms with X senses", leaving "X terms" to the term-context temp
aliases = {"folk poetic"},
Wikipedia = "Folk poetry",
pos_categories = "folk-poetic terms",
}
labels["formal"] = {
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
pos_categories = "formal terms",
}
labels["formal form"] = {
glossary = "formal",
display = "formal",
pos_categories = "formal forms",
}
labels["gay slang"] = {
display = "[[gay]] {{glossary|slang}}",
pos_categories = true,
}
labels["gender critical slang"] = {
aliases = {"gender-critical slang", "GC slang", "TERF slang"},
display = "[[gender-critical]] {{glossary|slang}}",
pos_categories = "gender-critical slang",
}
labels["gender-neutral"] = {
glossary = "gender-neutral",
pos_categories = "gender-neutral terms",
}
labels["graffiti slang"] = {
display = "[[graffiti#Noun|graffiti]] {{glossary|slang}}",
pos_categories = true,
}
labels["genericized trademark"] = {
aliases = {"genericised trademark", "generic trademark", "proprietary eponym", "gentrade"},
display = "[[genericized trademark]]",
pos_categories = "genericized trademarks",
}
labels["ghost word"] = {
aliases = {"ghost"},
display = "ghost word",
glossary = true,
pos_categories = "ghost words",
}
labels["hapax legomenon"] = {
aliases = {"hapax"},
display = "hapax legomenon",
glossary = true,
pos_categories = "hapax legomena",
}
labels["higher register"] = {
aliases = {"high register", "elevated register", "elevated"},
glossary = "higher register",
pos_categories = "higher register terms",
}
labels["historical"] = {
aliases = {"historic"},
glossary = true,
sense_categories = true,
}
labels["non-native speakers"] = {-- language-agnostic version
aliases = {"NNS"},
display = "[[non-native speaker]]s", -- so preceded by "used by", "error by children and", etc? or reword?
regional_categories = {"Non-native speakers'"},
}
-- used exclusively by languages that use the "Jpan" script code
labels["historical hiragana"] = {
pos_categories = true,
}
-- used exclusively by languages that use the "Jpan" script code
labels["historical katakana"] = {
pos_categories = true,
}
-- applies to Japanese and Korean, etc., please do not confuse with "polite"
labels["honorific"] = {
Wikipedia = "Honorifics (linguistics)",
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
pos_categories = "honorific terms",
}
-- for Ancient Greek
labels["Homeric epithet"] = {
display = "[[Homeric Greek|Homeric]] [[w:Epithets in Homer|epithet]]",
omit_postComma = true,
plain_categories = "Epic Greek",
}
-- applies to Japanese and Korean, etc.
labels["humble"] = {
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
display = "[[humble]]",
pos_categories = "humble terms",
}
-- for Akkadian
labels["in hendiadys"] = {
aliases = {"hendiadys"},
display = "in {{w|hendiadys}}",
pos_categories = "terms used in hendiadys",
}
labels["humorous"] = {
-- should be "terms with X senses", leaving "X terms" to the term-context temp; NB and cf a similar "jocular" label further up on this page
aliases = {"humorously", "jocular"},
glossary = true,
pos_categories = "humorous terms",
}
labels["hyperbolic"] = {
aliases = {"hyperbole"},
glossary = true,
pos_categories = "hyperboles",
}
labels["hypercorrect"] = {
glossary = true,
pos_categories = "hypercorrections",
}
labels["hyperforeign"] = {
glossary = true,
pos_categories = "hyperforeign terms",
}
labels["imperial"] = {
aliases = {"emperor", "empress"},
pos_categories = "royal terms",
}
labels["incel slang"] = {
display = "[[incel]] {{glossary|slang}}",
pos_categories = true,
}
labels["informal"] = {
aliases = {"informally", "not formal"},
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "informal terms",
}
labels["informal form"] = {
glossary = "informal",
display = "informal",
pos_categories = "informal forms",
}
labels["Internet slang"] = {
aliases = {"internet slang"},
display = "[[Internet]] {{glossary|slang}}",
pos_categories = "internet slang",
}
labels["IRC"] = {
display = "[[IRC]]",
pos_categories = "internet slang",
}
labels["ironic"] = {
display = "[[irony|ironic]]",
}
-- Not the same as "journalism", which maps to a topical category (e.g. [[:Category:en:Journalism]], instead of [[:Category:English journalistic terms]]).
labels["journalistic"] = {
aliases = {"journalese"},
display = "[[journalistic]]",
pos_categories = "journalistic terms",
}
labels["leet"] = {
aliases = {"leetspeak"},
display = "[[leetspeak]]",
pos_categories = "leetspeak",
}
labels["LGBTQ slang"] = {
aliases = {"LGBT slang"},
display = "[[LGBTQ]] {{glossary|slang}}",
pos_categories = true,
}
labels["literal"] = {
glossary = "literally",
}
labels["literally"] = {
glossary = "literally",
}
labels["literary"] = {
-- should be "terms with X senses", leaving "X terms" to the term-context temp
aliases = {"bookish"},
glossary = true,
pos_categories = "literary terms",
}
labels["literary form"] = {
aliases = {"bookish form"},
glossary = "literary",
display = "literary",
pos_categories = "literary forms",
}
--see also "short scale"
labels["long scale"] = {
display = "[[w:Long and short scales|long scale]]"
}
labels["loosely"] = {
aliases = {"loose", "broadly", "broad"},
glossary = true,
}
labels["Lubunyaca"] = {
display = "[[Lubunyaca]]",
pos_categories = true,
}
labels["medical slang"] = {
display = "[[medical]] {{glossary|slang}}",
pos_categories = true,
}
-- for Awetí, Karajá, etc., where men and women use different words
labels["men's speech"] = {
aliases = {"male speech"},
glossary = "men's speech",
pos_categories = "men's speech terms",
}
labels["metonymic"] = {
aliases = {"metonymically", "metonymy", "metonym"},
glossary = true,
pos_categories = "metonyms",
}
labels["military slang"] = {
display = "[[military]] {{glossary|slang}}",
pos_categories = true,
}
labels["minced oath"] = {
display = "[[minced oath]]",
pos_categories = "minced oaths",
}
labels["multiplicative"] = {
aliases = {"multiplicative number", "multiplicative numeral"},
display = "[[multiplicative number]]",
pos_categories = "multiplicative numbers",
}
labels["multiplicity slang"] = {
display = "{{l|en|multiplicity|id=multiple personalities}} {{glossary|slang}}",
pos_categories = true,
}
labels["naval slang"] = {
aliases = {"navy slang"},
display = "[[naval]] {{glossary|slang}}",
pos_categories = true,
}
labels["neologism"] = {
aliases = {"neologistic"},
glossary = true,
pos_categories = "neologisms",
}
labels["neopronoun"] = {
display = "[[neopronoun]]",
-- pos_categories = {"neopronouns"},
}
labels["no longer productive"] = {
aliases = {"non-productive"},
display = "no longer [[Appendix:Glossary#productive|productive]]",
}
labels["nonce word"] = {
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
aliases = {"nonce"},
glossary = true,
pos_categories = "nonce terms",
}
labels["nonstandard"] = {
aliases = {"non-standard", "substandard", "sub-standard"},
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "nonstandard terms",
}
labels["nonstandard form"] = {
aliases = {"non-standard form", "substandard form", "sub-standard form"},
glossary = "nonstandard",
display = "nonstandard",
pos_categories = "nonstandard forms",
}
labels["numismatic slang"] = {
display = "[[numismatic]] {{glossary|slang}}",
pos_categories = true,
}
labels["obsolete"] = {
glossary = true,
sense_categories = true,
}
labels["obsolete form"] = {
glossary = "obsolete",
display = "obsolete",
pos_categories = "obsolete forms",
}
labels["obsolete term"] = {
glossary = "obsolete",
-- combine with previous two, q.v.
pos_categories = "obsolete terms",
}
labels["offensive"] = {
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp
pos_categories = "offensive terms",
}
labels["officialese"] = {
aliases = {"bureaucratic"},
display = "[[officialese]]",
pos_categories = "officialese terms",
}
labels["Oxbridge slang"] = {
display = "[[w:Oxbridge|Oxbridge]] {{glossary|slang}}",
topical_categories = "Universities",
plain_categories = {"Cambridge University slang", "Oxford University slang"},
}
labels["Oxford University slang"] = {
aliases = {"University of Oxford slang", "Oxon slang"},
display = "[[w:University of Oxford|Oxford University]] {{glossary|slang}}",
topical_categories = "Universities",
plain_categories = true,
}
labels["poetic"] = {
aliases = {"poi"}, -- Only used in Ancient Greek as a holdover from [[Module:grc:Dialects]].
-- should be "terms with X senses", leaving "X terms" to the term-context temp
glossary = true,
pos_categories = "poetic terms",
}
labels["poetic form"] = {
glossary = "poetic",
display = "poetic",
pos_categories = "poetic forms",
}
labels["polite"] = {
glossary = true,
pos_categories = "polite terms",
}
labels["post-classical"] = {
aliases = {"Post-classical", "post-Classical", "Post-Classical", "Postclassical", "postclassical"},
display = "post-Classical",
regional_categories = true,
}
labels["prison slang"] = {
display = "[[prison]] {{glossary|slang}}",
pos_categories = true,
}
labels["proscribed"] = {
glossary = true,
pos_categories = "proscribed terms",
}
labels["puristic"] = {
aliases = {"purism"},
Wikipedia = "Linguistic purism",
pos_categories = "puristic terms",
}
labels["radio slang"] = {
display = "[[radio]] {{glossary|slang}}",
pos_categories = true,
}
labels["Reddit slang"] = {
display = "[[Reddit]] {{glossary|slang}}",
pos_categories = true,
}
labels["rare"] = {
aliases = {"rare sense"},
glossary = true,
sense_categories = true,
}
labels["rare form"] = {
glossary = "rare",
display = "rare",
pos_categories = "rare forms",
}
labels["rare term"] = {
display = "rare",
-- see comments about "obsolete"
pos_categories = "rare terms",
}
-- cf Cockney rhyming slang
labels["rhyming slang"] = {
display = "[[rhyming slang]]",
pos_categories = true,
}
labels["religious slur"] = {
aliases = {"sectarian slur"},
display = "[[religious]] [[slur]]",
pos_categories = "religious slurs",
}
labels["retronym"] = {
glossary = true,
pos_categories = "retronyms",
}
labels["reverential"] = {
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
display = "[[reverential]]",
pos_categories = "reverential terms",
}
labels["royal"] = {
aliases = {"regal"},
pos_categories = "royal terms",
}
labels["rustic"] = {
glossary = true,
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
aliases = {"rural"},
pos_categories = "rustic terms",
}
labels["sarcastic"] = {
display = "[[sarcastic]]",
pos_categories = "sarcastic terms",
}
labels["school slang"] = {
aliases = {"public school slang"},
display = "[[school]] {{glossary|slang}}",
pos_categories = true,
}
labels["self-deprecatory"] = {
aliases = {"self-deprecating"},
display = "[[self-deprecatory]]",
-- should be "terms with X senses", leaving "X terms" to the term-context temp?
pos_categories = "self-deprecatory terms",
}
-- Swahili Sheng cant / argot
-- should this be in a language-specific module?
labels["Sheng"] = {
Wikipedia = "Sheng slang",
plain_categories = true,
}
labels["siglum"] = {
aliases = {"sigla"},
glossary = true,
pos_categories = "sigla",
}
--see also "long scale"
labels["short scale"] = {
display = "[[w:Long and short scales|short scale]]"
}
labels["slang"] = {
glossary = true,
pos_categories = true,
}
labels["solemn"] = {
glossary = true,
pos_categories = "solemn terms",
}
labels["Stenoscript"] = {
aliases = {"stenoscript"},
display = "[[Stenoscript]]",
pos_categories = "Stenoscript abbreviations",
}
labels["superseded"] = {
glossary = true
}
labels["swear word"] = {
aliases = {"profanity", "expletive"},
pos_categories = "swear words",
}
labels["syncopated"] = {
aliases = {"syncope", "syncopic", "syncopation"},
glossary = true,
pos_categories = "syncopic forms",
}
labels["synecdochic"] = {
aliases = {"synecdochically", "synecdochical", "synecdoche"},
glossary = true,
pos_categories = "synecdoches",
}
labels["technical"] = {
display = "[[technical]]",
pos_categories = "technical terms",
}
labels["telic"] = {
glossary = true,
}
labels["text messaging"] = {
aliases = {"texting"},
display = "[[text messaging]]",
pos_categories = "text messaging slang",
}
labels["tone indicator"] = {
display = "[[tone indicator]]",
pos_categories = "tone indicators",
}
labels["trademark"] = {
display = "[[trademark]]",
pos_categories = "trademarks",
}
labels["transferred sense"] = {
glossary = true,
pos_categories = "terms with transferred senses",
}
labels["transferred senses"] = {
display = "[[transferred sense#English|transferred senses]]",
pos_categories = "terms with transferred senses",
}
labels["transgender slang"] = {
aliases = {"trans slang"},
display = "[[transgender]] {{glossary|slang}}",
pos_categories = true,
}
labels["Twitch-speak"] = {
display = "[[Twitch-speak]]",
pos_categories = true,
}
labels["uds."] = {
display = "[[Appendix:Spanish pronouns#Ustedes and vosotros|used formally in Spain]]",
}
labels["uncommon"] = {
glossary = true,
sense_categories = true,
}
labels["uncommon form"] = {
glossary = "uncommon",
display = "uncommon",
pos_categories = "uncommon forms",
}
labels["university slang"] = {
aliases = {"college slang", "student slang"},
display = "[[university]] {{glossary|slang}}",
topical_categories = "Universities",
pos_categories = "student slang",
}
labels["verlan"] = {
glossary = true,
plain_categories = true,
}
labels["very rare"] = {
display = "very [[Appendix:Glossary#rare|rare]]",
sense_categories = "rare",
}
labels["vulgar"] = {
aliases = {"coarse", "obscene", "profane"},
glossary = true,
pos_categories = "vulgarities",
}
labels["vesre"] = {
Wikipedia = true,
plain_categories = true,
}
labels["youth slang"] = {
display = "[[youth]] {{glossary|slang}}",
pos_categories = "slang",
}
labels["2channel slang"] = {
aliases = {"2channel", "2ch slang"},
display ="[[w:2channel|2channel]] {{glossary|slang}}",
pos_categories = {"internet slang" , "2channel slang"},
}
-- for Awetí, Karajá, etc., where men & women use different words
labels["women's speech"] = {
aliases = {"female speech"},
glossary = "women's speech",
pos_categories = "women's speech terms",
}
-- terms applying to Old Norse skaldic poetry
labels["kenning"] = {
aliases = {"Kenning"},
Wikipedia = "Kenning",
pos_categories = "kennings",
}
labels["heiti"] = {
aliases = {"Heiti"},
Wikipedia = "Heiti",
pos_categories = true,
}
return require("Module:labels").finalize_data(labels)
n8m44ayqmg3vl4yjuowxa0q1qq4mflm
Module:inflection utilities
828
8097
54972
2026-09-30T17:39:33Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local m_links = require("Module:links") local m_str_utils = require("Module:string utilities") local m_table = require("Module:table") local put = require("Module:parse utilities") local headword_data_module = "Module:headword/data" local script_utilities_module = "Module:script utilities" local pages_module = "Module:pages" local template_parser_module = "Module:template parser" local is_callable = require("Module:fun").is_callable local spli...'
54972
Scribunto
text/plain
local export = {}
local m_links = require("Module:links")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local put = require("Module:parse utilities")
local headword_data_module = "Module:headword/data"
local script_utilities_module = "Module:script utilities"
local pages_module = "Module:pages"
local template_parser_module = "Module:template parser"
local is_callable = require("Module:fun").is_callable
local split = m_str_utils.split
local ufind = m_str_utils.find
local usub = m_str_utils.sub
local ucfirst = m_str_utils.ucfirst
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local dump = mw.dumpObject
local new_title = mw.title.new
local insert = table.insert
local concat = table.concat
local insert_if_not = m_table.insertIfNot
local shallow_copy = m_table.shallowCopy
local function track(page)
require("Module:debug/track")("inflection utilities/" .. page)
return true
end
local footnote_abbrevs = {
["a"] = "archaic",
["c"] = "colloquial",
["d"] = "dialectal",
["fp"] = "folk-poetic",
["l"] = "literary",
["lc"] = "low colloquial",
["p"] = "poetic",
["pej"] = "pejorative",
["r"] = "rare",
}
--[==[ intro:
The following code is used in building up the inflection of terms in inflected languages, where a term can potentially
consist of several inflected words, each surrounded by fixed text, and a given slot (e.g. accusative singular) of a
given word can potentially consist of multiple possible inflected forms. In addition, each form may be associated with
a manual transliteration and/or a list of footnotes (or qualifiers, in the case of headword lines). The following
terminology is helpful to understand:
* A '''term''' is a word or multiword expression that can be inflected. A multiword term may in turn consist of several
single-word inflected terms with surrounding fixed text. A term belongs to a particular '''part of speech''' (e.g.
noun, verb, adjective, etc.).
* An '''inflection dimension''' is a particular dimension over which a term may be inflected, such as case, number,
gender, person, tense, mood, voice, aspect, etc.
* The '''lemma''' is the particular form of a term under which the term is entered into a dictionary. For example, for
verbs, it is most commonly the infinitive, but this differs for some languages: e.g. Latin, Greek and Bulgarian use
the first-person singular present indicative (active voice in the case of Latin and Greek); Sanskrit and Macedonian
use the third-person singular present indicative (active voice in the case of Sanskrit); Hebrew and Arabic use the
third-person singular masculine past (aka "perfect"); etc. For nouns, the lemma form is most commonly the nominative
singular, but e.g. for Old French it is the objective singular and for Sanskrit it is the root.
* A '''slot''' is a particular combination of inflection dimensions. An example might be "accusative plural" for a noun,
or "first-person singular present indicative" for a verb. Slots are named in a language-specific fashion. For
example, the slot "accusative plural" might have a name `accpl`, while "first-person singular present indicative"
might be variously named `pres1s`, `pres_ind_1_sg`, etc. Each slot is filled with zero or more '''forms'''.
* A '''form''' is a particular inflection of a slot for a particular term. Note that a given slot may (and often does)
have more than one associated form; these different forms are termed '''variants'''. An example is
{{m+|de|Bug||bow (of a ship)}}, which has two genitive singular forms ''Buges'' and ''Bugs''; two plural forms in all
cases, e.g. nominative plural ''Buge'' and ''Büge''; and two dative singular forms ''Bug'' and rare/archaic ''Buge''.
The form variants for a given slot are ordered, and generally should have the more common and/or preferred variants
first, along with rare, archaic or obsolete variants last (if they are included at all).
* Forms are described using '''form objects''', which are Lua objects taking the form
`{form="``form_value``", translit="``manual_translit``", footnotes={"``footnote``", "``footnote``", ...}}`.
(Additional '''metadata''' may be present in a form object, although the support for preserving such metadata when
transformations are applied to form objects isn't yet complete.) ``form_value`` is a '''form value''' specifying the
value of the form itself in the term's script. ``manual_translit`` specifies optional manual transliteration for the
form, in case (a) the form value is in a different script; and (b) either the form's automatic transliteration is
incorrect and needs to be overridden, or the language of the term has no automatic transliteration (e.g. in the case
of Persian and Hebrew). ``footnote`` is a footnote to be attached to the form in question, and should be e.g.
{"[archaic]"} or {"[only in the meaning 'to succeed (an officeholder)']"}, i.e. the string must be surrounded by
brackets and should begin with a lowercase letter and not end in a period/full stop. When such footnotes are converted
to actual footnotes in a table of inflected forms, the brackets will be removed, the first letter will be capitalized
and a period/full stop will be added to the end. (However, when such footnotes are used as qualifiers in headword
lines, only the brackets will be removed, with no capitalization or final period.) Note that only ``form_value`` is
mandatory.
* A list of zero or more form objects is termed a '''form object list''', or usually just a '''form list'''. Such lists
are ordered and go into form tables (see below).
* A '''form table''' is a Lua table (i.e. a dictionary) describing all the possible inflections of a given term. The
keys in such a table are slots (strings) and the values are form lists. '''NOTE:''' All inflection code assumes and
maintains the invariant that no two slots, and no two forms in a single slot, share the same form object (by
reference, i.e. the Lua object describing a form object should never be shared in two places). This allows for safely
side-effecting form objects in certain sorts of operations. This same invariant necessarily applies to the Lua list
objects containing the form objects, but does '''NOT''' apply to metadata inside of form objects. In particular, a
list of footnotes may well be shared among different form objects. This means it is '''NOT''' safe to side-effect
such lists, and in fact no code in this module that manipulates footnote lists will ever side-effect such lists; they
are treated as immutable.
* Some functions, to save memory, accept and work with abbreviated forms of form objects and/or form lists.
Specifically, an '''abbreviated form object''' is either a form object or a string, the latter corresponding to a form
object whose form value is the string and all other properties are nil. Similarly, an '''abbreviated form list''' is
either a single abbreviated form object or a list of such objects, i.e. any of a string, form object or list of
strings and/or form objects. Functions that do not accept such abbreviated structures may be said to insist on being
passed form objects in '''full form''', or form lists in '''full list form'''.
* Each slot is associated with an '''accelerator tag set''', which is a list of inflection tags that are used when
generating an accelerator entry for the forms in the slot (see [[WT:ACCEL]]). For example, the first singular present
indicative of a verb might have slot name `pres_1sg` and corresponding accelerator tag set `1|s|pres|ind`. As shown,
the accelerator tag set is a string consisting of inflection tags (as used in {{tl|inflection of}}) separated by `|`.
Despite the terminology ''tag set'', the tags in a tag set are ordered, although the same tag should never occur
twice.
* Some inflected terms are '''multiword''', i.e. they consist of multiple '''words''', where each word is generally
separated by spaces or sometimes hyphens. In such a term, some of the words inflect, while others remain fixed. Words
that inflect are termed '''inflecting words''' (or more correctly '''inflecting parts''', since in some circumstances,
parts of a word can inflect). The '''fixed text''' is all the parts of a multiword term that do not inflect.
* The descriptor that describes how a given term inflects is called an '''inflection spec''', and consists of the lemma
form of the term itself, annotated with an '''angle bracket spec''' after each inflecting word. As the name implies,
an angle bracket spec is surrounded by angle brackets (`<...>`). A simple example is {{m+|de|Feder||feather}}, whose
inflection spec looks like `Feder<f>`, where `f` specifies the feminine gender. In this case, although there are
several properties that could be specified between angle brackets, all except the gender are optional and have been
left out, indicating that defaults should be used. Another example is {{m+|de|Baske|Basque person}}, whose inflection
spec looks like `Baske<m.weak>`, where `m` specifies the masculine gender and `weak` specifies the weak inflection.
Note that individual components of an angle bracket spec like `m` and `weak` are termed '''indicators''' and are
separated by periods/full stops. A slightly more complex example is {{m+|de|Zeitgeist||zeitgeist}}, whose inflection
spec looks like `Zeitgeist<m,es:s,er>` and which specifies three things in a single '''compound indicator''': `m` (the
masculine gender); `es:s` (the genitive singular, which can end in either ''-es'' or ''-s''); and `er` (the
nomininative plural, which ends in ''-er'').
* If there are several inflecting words in a term, each one will be followed by its own angle bracket spec. An example
is {{m+|de|schwarzes Loch||black hole}}, whose inflection spec looks like `schwarzes<+> Loch<n,es:s,^er>`. Here,
the adjective ''schwarzes'' (the nominative neuter singular of {{m|de|schwarz||black}}) is followed by the angle
bracket spec `<+>` specifying that it inflects as an adjective, and the noun ''Loch'' has the angle bracket spec
`<n,es:s,^er>`, indicating (similarly to the above example) that it is neuter, has a genitive singular in either
''-es'' or ''-s'', and has a nominative plural in ''-er'' with umlaut, hence ''Löcher'' (the `^` specifies that the
form requires umlaut).
* Sometimes a given term has multiple ways of inflecting that differ in ways that can't be specified using a single
angle bracket spec. This is supported using '''alternants''', which are specified using double parentheses. (This is
so that terms that themselves contain parentheses can be specified without interference.) An example is
{{m+|uk|русин||Rusyn}}, which can be stressed either as ''ру́син'' (stress on the first syllable and following accent
paradigm ''a'', hence genitive singular ''ру́сина'') or ''руси́н'' (stress on the second syllable and following accent
paradigm ''b'', hence genitive singular ''русина́''; note how the stress moves onto the ending, in accordance with the
accent paradigm). This is specified using `((ру́син<pr>,руси́н<b.pr>))`, i.e. each separate the alternants with a comma
and surround them with double parentheses. (Here, `pr` means that the terms belong to the personal animacy class, and
`b` specifies the accent paradigm; paradigm ''a'' is the default and hence is omitted.)
* Note that occasionally, parts of a single space-delimited word can inflect separately. An example is
{{m+|la|rōsmarīnus||rosemary}}, which is a compound of {{m+|la|rōs||dew}} and {{m|la|marīnus||marine, of the sea}}.
In this compound, both parts of the compound can inflect separately; hence genitive singular ''rōrismarīnī'',
accusative singular ''rōremmarīnum'', etc. Alternatively, only the second part inflects; hence genitive singular
''rōsmarīnī'', accusative singular ''rōsmarīnum'', etc. This is specified as
`((rōs/rōr<3.M>marīnus<2>,rōsmarīnus<2>))`. Here, the term {{m|la|rōs}} by itself would have inflection spec
`rōs/rōr<3.M>` (indicating that it is third declension masculine with a non-nominative-singular stem ''rōr-'') and
the term {{m|la|marīnus}} would have inflection spec `<2>` (indicating that it is second declension; the masculine
gender is inferred from the ''-us'' ending). When combined in a single inflection spec, the doubly-inflecting
alternant is written `rōs/rōr<3.M>marīnus<2>`, with each inflecting part followed by its corresponding angle bracket
spec, and the singly-inflecting alternant is written `rōsmarīnus<2>`. As this example shows, the two alternants
need not correspond in how many inflecting parts there are. It should also be noted that fixed text can surround
an alternant and it is even possible to supply multiple alternants in a single inflection spec (e.g. if the term
has two words in it and each word requires an alternant to inflect).
* The result of parsing a single angle bracket spec is stored into a '''word spec'''. The structure of a word spec is
fairly arbitrary and is determined by the user-written `parse_indicator_spec` function, but always contains a form
table under the `forms` key that is populated during inflection (see below). A parameter or local variable that holds
a word spec is conventionally named `base` for historical reasons. Word specs are grouped together into a structure
termed a '''multiword spec''', which describes one or more word specs along with the fixed text in between and around
the inflected words. Multiword specs are in turn grouped into structures termed '''alternant specs''', indicating
the distinct alternants and the words in each alternant. Finally, multiword specs and alternant specs are grouped into
an '''alternant multiword spec''', which is the top-level object describing an inflection spec. Each of these
different specs has a form table in it stored in the `forms` key that is populated during the inflection process and
contains the form objects that specify the inflections of this part of the full multiword term. (It should be noted
that the term '''spec''' is overloaded to mean two different things: the user-specified descriptor that specifies the
lemma form of the term and associated inflection, and the associated internal Lua object that encapsulates all
information derived from the descriptor, along with later-generated information on how to inflect the term(s) being
described.)
* Among these various "spec" structures, the two most important are the top-level alternant multiword spec and the
bottom-level word spec or "base". You will rarely find it necessary to manipulate the intermediate structures or
concern yourself with the details of their formation.
* The term ''form'' is unfortunately overloaded in various modules to mean several things. In particular, for
historical reasons, the form value inside of a form object is stored using the key `form`; the form table inside of an
alternant multiword spec, a word spec (or "base") and the intermediate structures is stored using the key `forms`; and
the accelerator tag set is internally referred to in [[WT:ACCEL]] as a "form". To avoid confusion, the following
conventions are followed in code in this module, and should be followed for code in invoking modules as well:
*# Functions that accept form objects often name the relevant parameter `form` (if a single form object is required) or
`forms` (if a list of form objects, aka form list, is required).
*# Functions that accept abbreviated form objects should (but don't always) indicate this by naming the parameter
`abform` (for a single abbreviated form object) or `abforms` (for an abbreviated form list).
*# Functions that accept a form value (the native-script string portion of a form object, stored for historical reasons
in the `.form` property) should '''not''' call such a parameter `form`, but instead use something that makes clear
that a form value is required, such as `formval` or sometimes just `val`.
*# Similarly, functions that accept a form table should '''not''' call such a parameter `forms` (although for historical
reasons the form table in an alternant multiword spec is stored in the field `forms`). Instead, use `formtable` or
`formtab`, or similar name that makes clear that the value is a form table (i.e. a map from slot to form list).
====Footnote handling====
Each form can have one or more attached footnotes. The form of a footnote as specified by the user and stored in form
values is e.g. {"[archaic]"} or {"[only in the meaning 'to succeed (an officeholder)']"}, i.e. the string must be
surrounded by brackets and should begin with a lowercase letter and not end in a period/full stop. When such footnotes
are converted to actual footnotes in a table of inflected forms, the brackets will be removed, the first letter will be
capitalized and a period/full stop will be added to the end. (However, when such footnotes are used as qualifiers in
headword lines, only the brackets will be removed, with no capitalization or final period.)
When merging two forms into one, such as when concatenating the form objects of two inflected words in a multiword term
or deduplicating form objects sharing the same form value during `show_forms()`, the footnotes are generally combined as
well. This means that if one form object has footnotes and the other doesn't, the resulting form object inherits the
footnotes of the object that has them, and if both form objects have footnotes, the resulting form object gets all
footnotes from both source form objects, with duplicates removed. However, when inserting a form into a form table slot
that already has a form whose form value and translit are identical to the new form, the behavior is different. In
under normal circumstances the footnotes of the new form are ''not'' incorporated into those of the existing form (if
any), but are simply dropped. To understand why this makes sense, consider a term that has two possible forms of its
lemma (e.g. two forms differing in stress or in vowel length), where the second form is archaic, rare, colloquial or the
like, and has an attached footnote indicating this. An example of this is {{m+|ru|кожух||sheepskin coat; bullet shell}},
where the form ''кожу́х'' with accent pattern ''b'' is more common overall but the form ''ко́жух'' with accent pattern
''c(1)'' is more common among professionals. On first glance, this could be indicated using
`((кожу́х<b>,ко́жух<c(1).[professional usage only]>))`. But some forms of these two declensions are the same (in
particular, the genitive, dative, instrumental and prepositional plural). If for these slots, the footnotes of the
duplicate forms were combined (i.e. the footnotes of the second declension pattern were added to the already-existing
form taken from the first declension pattern), these forms would wrongly be labeled as ''professional usage only''.
For this reason, it makes more sense to drop the footnotes of the second form when deduplicating.
The same sort of behavior makes sense when a single lemma can have two different declensions, the second of which
requires a footnote and where some forms in the two declensions are shared. An example of this is
{{m+|uk|окови́та||strong, high-quality liquor}}, which can be inflected adjectivally or (rarely) nominally. This would be
indicated as `((окови́та<sg.+>,окови́та<sg.[rare]>))` where the `+` indicates adjectival declension and the `sg` indicates
that this term only exists in the singular. Here, the two declensions differ in the genitive, dative/locative and
vocative (respectively, adjectival ''окови́тої'', ''окови́тій'', ''окови́та'' vs. nominal ''окови́ти'', ''окови́ті'',
''окови́то'') but are the same in the accusative (''окови́ту'') and instrumental (''окови́тою''). Again, dropping the
footnotes of the second form when deduplicating is correct and including them would be wrong.
This behavior can be changed by attaching a '''footnote modifier''' to the footnote associated the second form. A
footnote modifier is a symbol attached to the beginning of a footnote, directly following the opening bracket. The
following modifiers are currently recognized:
* `!` or `+`: If placed on a footnote of the second form, combine that footnote with those of the first form (if any)
rather than dropping it.
* `*`: If placed on a footnote of the first form, drop that footnote when merging a second form with any footnotes.
An example where the `*` modifier makes sense is a modification of the above example with {{m+|ru|кожух}}. If we
notated it as `((кожу́х<b.[more common among laymen]>,ко́жух<c(1).[more common among professionals]>))`, the shared forms
would wrongly have the footnote ''more common among laymen'' when in fact they are the only possible forms. If instead
we used `((кожу́х<b.[*more common among laymen]>,ко́жух<c(1).[more common among professionals]>))`, the shared forms
would correctly have no footnote.
]==]
local function extract_footnote_modifiers(footnote)
local footnote_mods, footnote_without_mods = footnote:match("^%[([!*+]?)(.*)%]$")
if not footnote_mods then
error("Saw footnote '" .. footnote .. "' not surrounded by brackets")
end
return footnote_mods, footnote_without_mods
end
function export.deduplicate_footnotes(existing, new)
if not new then
return existing
end
-- Check to see if there are existing footnotes with *; if so, remove them.
if existing then
local any_footnotes_with_asterisk = false
for _, footnote in ipairs(existing) do
local footnote_mods, _ = extract_footnote_modifiers(footnote)
if footnote_mods:find("%*") then
any_footnotes_with_asterisk = true
break
end
end
if any_footnotes_with_asterisk then
local filtered_footnotes = {}
for _, footnote in ipairs(existing) do
local footnote_mods, _ = extract_footnote_modifiers(footnote)
if not footnote_mods:find("%*") then
insert(filtered_footnotes, footnote)
end
end
if #filtered_footnotes > 0 then
existing = filtered_footnotes
else
existing = nil
end
end
end
-- The behavior here has changed; track cases where the old behavior might
-- be needed by adding ! to the footnote.
track("combining-footnotes")
local any_footnotes_with_bang = false
for _, footnote in ipairs(new) do
local footnote_mods, _ = extract_footnote_modifiers(footnote)
if footnote_mods:find("[!+]") then
any_footnotes_with_bang = true
break
end
end
if any_footnotes_with_bang then
if not existing then
existing = {}
else
existing = shallow_copy(existing)
end
for _, footnote in ipairs(new) do
local already_seen = false
local footnote_mods, footnote_without_mods = extract_footnote_modifiers(footnote)
if footnote_mods:find("[!+]") then
for _, existing_footnote in ipairs(existing) do
local _, existing_footnote_without_mods = extract_footnote_modifiers(existing_footnote)
if existing_footnote_without_mods == footnote_without_mods then
already_seen = true
break
end
end
if not already_seen then
insert(existing, footnote)
end
end
end
end
return existing
end
--[==[
Combine two sets of footnotes. If either is {nil}, just return the other, and if both are {nil}, return {nil}.
]==]
function export.combine_footnotes(notes1, notes2)
if not notes1 and not notes2 then
return nil
end
if not notes1 then
return notes2
end
if not notes2 then
return notes1
end
local combined = shallow_copy(notes1)
for _, note in ipairs(notes2) do
insert_if_not(combined, note)
end
return combined
end
function export.combine_formobj_lists(list1, list2, metadata)
if not list1 and not list2 then
return nil
end
if not list1 then
return list2
end
if not list2 then
return list1
end
return export.insert_formobjs_into_list {
existing_list = list1,
formobjs = list2,
metadata = metadata,
}
end
function export.looks_like_formobj_list(list)
if list == nil then
return true
end
if type(list) ~= "table" then
return false
end
for k, v in pairs(list) do
if type(k) ~= "number" then
return false
end
if type(v) ~= "table" then
return false
end
if type(v.form) ~= "string" then
return false
end
end
return true
end
function export.looks_like_boolean(object)
return object == nil or type(object) == "boolean"
end
function export.default_fallback_deduplicate_handler(_prop, existing, _new)
return existing, true
end
function export.default_fallback_combine_handler(_prop, existing, new)
if export.looks_like_formobj_list(existing) and export.looks_like_formobj_list(new) then
return export.combine_formobj_lists(existing, new), true
end
if export.looks_like_boolean(existing) and export.looks_like_boolean(new) then
return existing or new, true
end
if existing ~= nil then
return existing, true
end
return new, true
end
--[=[
Default handler functions for working with form object metadata. The user-specific metadata structure has the same
format. The key is a specific metadata field and the value is an object with fields specifying handlers and other
properties of the metadata. The recognized per-metadata fields are:
* `deduplicate`: Handler to deduplicate form objects when inserting a form object into an existing list (e.g. in a
form table) and two form objects have the same form value and translit. It is passed two values, respectively the
metadata property from the existing list object and the new object being inserted into the list. The default and
most common behavior here is to preserve the property from the existing object, even if {nil}, and discard the value
from the new object. This is what is done in footnotes, for example, unless the existing footnote is preceded by a *
(meaning to discard both values) or the new one is preceded by a + (meaning to preserve and combine both values,
which is not currrently used anywhere and may not make sense). This ensures, for example, that if a noun can be
declined according to two paradigms and the second one is rare, the {"rare"} footnote on the second one gets
discarded whenever the two paradigms coincide.
* `combine`: Handler to combine form objects when deriving a new object from an existing one. This is conceptually very
different from deduplication. In this situation, the existing and new objects may or may not match in form value and
translit, and since the new form object is a derivative of the existing one, it should combine the metadata
properties of both, preserving as much information as possible and not discarding anything. Combining footnotes, for
example, means taking the footnotes from either one, and if both exising and new objects have footnotes, merging
the two lists and removing duplicates. (The only exception is if a new footnote is preceded by !, which causes
existing footnotes to be discarded. This is termed an ''override'', and allows e.g. for one form of a paradigm to
override the footnote associated with the paradigm as a whole, as for example if a paradigm is archaic except for
one of the forms.) The default `combine` strategy is as follows:
*# If both the existing and new objects are {nil} or "look like" form object lists (i.e. are tables where all keys are
numeric and all values are tables with a `form` key whose value is a string), combine them using
`insert_formobjs_into_list`.
*# If both the existing and new objects are {nil} or boolean, combine them using `A or B`.
*# Otherwise, take the existing value if it is non-{nil}, and if not, take the new value. Note that this means that the
existing value will be kept even if it is {false} (which would not happen if the new value were {true}); this allows
for metadata where {false} is a special signal and keys are otherwise strings or objects. However, in general if the
metadata objects are not boolean and not form object lists, they will need a custom combine handler.
Some keys in the metadata handler object are special:
* `*`: This provides "fallback" handlers. These handlers are called for all metadata fields without dedicated handlers.
These handlers work similar to normal `deduplicate` and `combine` handlers except (a) they are passed three values,
first the metadata property name and then the existing and new values; (b) they should return two values, first the
appropriate merged value and second a boolean indicating whether the property was handled. (If {false} is returned,
the first value is ignored and should be {nil}.) Dedicated and fallback handlers are called in the following order:
(1) dedicated user-specified handlers; (2) dedicated default handlers; (3) fallback user-specified handlers;
(4) fallback default handlers. This ensures that standard dedicated default handlers (particularly for footnotes)
get run prior to the user-specified fallback handlers; otherwise, practically all such fallback handlers will need
to check for and ignore footnotes.
* `_side_effect_formobjs`: If true, existing form objects can be side-effected during deduplication or combining. This
requires that two logically distinct form objects in a given form table never have the same reference. (Generally
two such objects should be in different slots. If two references to the same object occur in different positions
in the list of form objects under a single slot, it indicates an internal error in the deduplication mechanism.)
This means that user code that generates form objects for insertion into a table must take care never to reuse
existing form objects (e.g. when setting one slot equal to another slot), but must always shallow-copy the form
objects. This is not the default for this reason, and in fact it's not clear that implementing this results in any
efficiency gain (it may actually be the reverse, since form objects have to be copied that could otherwise be
shared).
]=]
local default_metadata_handlers = {
footnotes = {
deduplicate = export.deduplicate_footnotes,
combine = export.combine_footnotes,
},
["*"] = {
deduplicate = export.default_fallback_deduplicate_handler,
combine = export.default_fallback_combine_handler,
}
}
local function is_non_metadata_property(k)
return k == "form" or k == "translit"
end
-- Call the appropriate handler for merging the metadata for property `prop` with values `existing_val` and `new_val`,
-- using method `method` (either "deduplicate" or "combine"). `metadata` is the structure describing all properties of
-- all metadata in a given form object. This looks for, in order:
-- 1. A handler for the specific property in `metadata`.
-- 2. A default handler for the specific property.
-- 3. A fallback handler in `metadata`.
-- 4. A default fallback handler.
-- Return the new value.
local function call_merge_metadata_handler(metadata, prop, method, existing_val, new_val)
local function try_specific(handler_struct, prop)
if handler_struct and handler_struct[prop] and handler_struct[prop][method] then
return (handler_struct[prop][method](existing_val, new_val)), true
end
return nil, false
end
local function try_fallback(handler_struct, prop)
if handler_struct and handler_struct["*"] and handler_struct["*"][method] then
-- Two values are returned from the handler, the new value and whether the property was handled.
return handler_struct["*"][method](prop, existing_val, new_val)
end
return nil, false
end
local retval, matched = try_specific(metadata, prop)
if matched then
return retval
end
retval, matched = try_specific(default_metadata_handlers, prop)
if matched then
return retval
end
retval, matched = try_fallback(metadata, prop)
if matched then
return retval
end
retval, matched = try_fallback(default_metadata_handlers, prop)
return retval
end
-- Merge the metadata of two form objects using `method` (either "deduplicate" or "combine"). `metadata` is the
-- structure describing all properties of all metadata in a given form object. Metadata is merged between
-- `existing_formobj` (on the left) and `new_formobj` (on the right), and is normally stored into `existing_formobj`,
-- but will be stored into `new_formobj` if `merge_into_new` is given. If `metadata` allows for side-effecting, the
-- changes will be made by side-effecting the object to be changed; otherwise, a shallow copy of the object will be
-- made if necessary (only if the new value is not equal to the existing value). Returns two values, the changed form
-- object (which may be the same as one of the passed-in objects) and a flag which is true if a copy was made of a form
-- object.
local function merge_metadata(metadata, method, existing_formobj, new_formobj, merge_into_new)
local props_of_either = nil
for k, _ in pairs(existing_formobj) do
if not is_non_metadata_property(k) then
props_of_either = props_of_either or {}
props_of_either[k] = true
end
end
for k, _ in pairs(new_formobj) do
if not is_non_metadata_property(k) then
props_of_either = props_of_either or {}
props_of_either[k] = true
end
end
local store_object = merge_into_new and new_formobj or existing_formobj
if props_of_either then
if metadata._side_effect_formobjs then
for k, _ in pairs(props_of_either) do
store_object[k] = call_merge_metadata_handler(metadata, k, method, existing_formobj[k], new_formobj[k])
end
else
local need_to_copy = false
local new_values = {}
for k, _ in pairs(props_of_either) do
local newval = call_merge_metadata_handler(metadata, k, method, existing_formobj[k], new_formobj[k])
if newval ~= store_object[k] and not m_table.deepEquals(newval, store_object[k]) then
need_to_copy = true
end
new_values[k] = newval
end
if need_to_copy then
store_object = shallow_copy(store_object)
for k, _ in pairs(props_of_either) do
store_object[k] = new_values[k]
end
end
return store_object, need_to_copy
end
else
return store_object, false
end
end
--[==[
Insert a form object (see above) into a list of such objects. If the form is already present (i.e. both the form
value and translit, if any, match), the footnotes of the existing and new form might be combined (specifically,
footnotes in the new form beginning with `+` will be combined).
]==]
function export.insert_formobj_into_list(data)
local existing_list = data.existing_list
local formobj = data.formobj
-- Don't do anything if the form object is nil.
if not formobj then
return existing_list
end
for i, existing_formobj in ipairs(existing_list) do
if existing_formobj.form == formobj.form and existing_formobj.translit == formobj.translit then
local new_existing, copied = merge_metadata(data.metadata, "deduplicate", existing_formobj, formobj)
if copied then
local list_copy = shallow_copy(existing_list)
list_copy[i] = new_existing
return list_copy
end
return existing_list
end
end
-- Form not found.
insert(existing_list, formobj)
return existing_list
end
--[==[
Insert a list of form objects (see above) into an existing list of such objects. ``forms`` can be {nil},
in which case nothing happens. This is equivalent to calling `insert_form_into_list` for each form object.
]==]
function export.insert_formobjs_into_list(data)
local existing_list = data.existing_list
local formobjs = data.formobjs
if not formobjs then
return existing_list
end
for _, formobj in ipairs(formobjs) do
existing_list = export.insert_formobj_into_list {
existing_list = existing_list,
formobj = formobj,
metadata = data.metadata,
}
end
return existing_list
end
--[==[
Insert a form object (see above) into the given slot in the given form table. On input, `data` is an object with the
following fields:
* `formtable`: A form table (see above).
* `slot`: A slot in the form table, i.e. a key whose value is a form object list.
* `formobj`: A form object to be inserted into the slot. Can be {nil}, in which case nothing happens.
* `metadata`: The metadata structure describing how to combine user-specified metadata. Can be {nil} if there is no
user-specified metadata or the default fallback metadata handlers are sufficient (which is usually the case if the
all user-specified metadata is boolean).
]==]
function export.insert_formobj_into_form_table(data)
local formtable, slot, formobj, metadata = data.formtable, data.slot, data.formobj, data.metadata
-- Don't do anything if the form object is nil.
if not formobj then
return
end
if not formtable[slot] then
formtable[slot] = {}
end
formtable[slot] = export.insert_formobj_into_list {
existing_list = formtable[slot],
formobj = formobj,
metadata = metadata,
}
end
--[==[
Insert a list of form objects (see above) into the given slot in the given form table. On input, `data` is an object
with the following fields:
* `formtable`: A form table (see above).
* `slot`: A slot in the form table, i.e. a key whose value is a form object list.
* `formobjs`: A list of form objects (in "full list" form) to be inserted into the slot. Can be {nil} or an empty list,
in which case nothing happens.
* `metadata`: The metadata structure describing how to combine user-specified metadata. Can be {nil} if there is no
user-specified metadata or the default fallback metadata handlers are sufficient (which is usually the case if the
all user-specified metadata is boolean).
]==]
function export.insert_formobjs_into_form_table(data)
local formtable, slot, formobjs, metadata = data.formtable, data.slot, data.formobjs, data.metadata
if not formobjs or not formobjs[1] then
return
end
if not formtable[slot] then
formtable[slot] = {}
end
formtable[slot] = export.insert_formobjs_into_list {
existing_list = formtable[slot],
formobjs = formobjs,
metadata = metadata,
}
end
--[==[
Flatmap a function over a list of form objects and return the (potentially flattened) results. Flatmapping is like
a normal mapping operation except that the function being called may return a list, and all values returned are
concatenated into a single flattened list. On input, `data` is an object with the following fields:
* `formobjs`: A list of form objects to map over, in "full list form" (see above).
* `fn`: The function to map over each form object. If a form object's value is {"?"}, the form object is preserved on
output and the function is not called. Otherwise, the function is called with one argument, the form object, and
should {nil} (equivalent to returning an empty list) or an abbreviated form list (i.e. anything that is convertible
into a full list form, such as a single form value, a list of form values, a form object or a list of form objects).
For each form object in the return value, the metadata of the object mapped over is combined with the metadata of
the returned object, unless `no_combine_metadata` is given. The resulting form objects from all function calls are
flattened into a list and deduplicated in the same fashion as `insert_formobj_into_list()` (in case two different
form objects map to the same form value).
* `metadata`: The metadata structure describing how to combine user-specified metadata. Can be {nil} if there is no
user-specified metadata or the default fallback metadata handlers are sufficient (which is usually the case if the
all user-specified metadata is boolean).
* `no_combine_metadata`: See `fn` above. If given, metadata from objects mapped over will not be combined into the
returned objects.
NOTE: There is no `map_formobjs` function provided because `flatmap_formobjs` will correctly handle functions that
return single form objects.
]==]
function export.flatmap_formobjs(data)
local formobjs, fn, metadata, no_combine_metadata =
data.formobjs, data.fn, data.metadata, data.no_combine_metadata
if not formobjs then
return nil
end
local retval = {}
for _, formobj in ipairs(formobjs) do
local funret
if formobj.form == "?" then
if metadata and metadata._side_effect_formobjs then
funret = shallow_copy(formobj)
else
funret = formobj
end
else
funret = fn(formobj)
end
if funret then
funret = export.convert_to_full_list_form(funret)
for _, fr in ipairs(funret) do
if not no_combine_metadata then
fr = merge_metadata(metadata, "combine", formobj, fr, "merge_into_new")
end
retval = export.insert_formobj_into_list {
existing_list = retval,
formobj = fr,
metadata = metadata,
}
end
end
end
return retval
end
--[==[
Insert a form object (see above) into a list of such objects. If the form is already present (i.e. both the form
value and translit, if any, match), the footnotes of the existing and new form might be combined (specifically,
footnotes in the new form beginning with `!` will be combined).
]==]
function export.insert_form_into_list(list, form)
-- Don't do anything if the form object or the form inside it is nil. This simplifies
-- form insertion in the presence of inflection generating functions that may return nil,
-- such as generate_noun_vocative() and generate_noun_count_form().
if not form or not form.form then
return
end
for _, listform in ipairs(list) do
if listform.form == form.form and listform.translit == form.translit then
listform.footnotes = export.deduplicate_footnotes(listform.footnotes, form.footnotes)
return
end
end
-- Form not found.
insert(list, form)
end
--[==[
Insert a list of form objects (see above) into an existing list of such objects. ``forms`` can be {nil},
in which case nothing happens. This is equivalent to calling `insert_form_into_list` for each form object.
]==]
function export.insert_forms_into_list(list, forms)
if not forms then
return
end
for _, form in ipairs(forms) do
export.insert_form_into_list(list, form)
end
end
--[==[
Insert a form object (see above) into the given slot in the given form table. ``form`` can be {nil}, in which case
nothing happens.
]==]
function export.insert_form(formtable, slot, form)
-- Don't do anything if the form object or the form inside it is nil. This simplifies
-- form insertion in the presence of inflection generating functions that may return nil,
-- such as generate_noun_vocative() and generate_noun_count_form().
if not form or not form.form then
return
end
if not formtable[slot] then
formtable[slot] = {}
end
export.insert_form_into_list(formtable[slot], form)
end
--[==[
Insert a list of form objects (see above) into the given slot in the given form table. ``forms`` can be {nil},
in which case nothing happens.
]==]
function export.insert_forms(formtable, slot, forms)
if not forms then
return
end
for _, form in ipairs(forms) do
export.insert_form(formtable, slot, form)
end
end
--[==[
Identity mapping function.
]==]
function export.identity(formval, translit)
return formval, translit
end
local function form_value_transliterable(formval)
return formval ~= "?" and formval ~= "—"
end
local function call_map_function_str(str, fun)
if str == "?" then
return "?"
end
local newformval, newtranslit = fun(str)
if newtranslit then
return {form=newformval, translit=newtranslit}
else
return newformval
end
end
-- FIXME: This doesn't correctly handle metadata.
local function call_map_function_obj(form, fun)
if form.form == "?" then
return {form = "?", footnotes = form.footnotes}
end
local newformval, newtranslit = fun(form.form, form.translit)
return {form = newformval, translit = newtranslit, footnotes = form.footnotes}
end
--[==[
Map a function over the form values in ``forms`` (a list of form objects in "full list form"; see above). If an
input form value is {"?"}, it is preserved on output and the function is not called. Otherwise, the function is
called with two arguments, the original form and manual translit; if manual translit isn't relevant, it's fine to
declare the function with only one argument. The return value is either a single value (the new form) or two values
(the new form and new manual translit). The footnotes (if any) from the input form objects are preserved on output. Uses
`insert_form_into_list()` to insert the resulting form objects into the returned list in case two different forms map
to the same thing.
FIXME: Expand this to correctly handle metadata, or create a variant that correctly handles metadata.
]==]
function export.map_forms(forms, fun)
if not forms then
return nil
end
local retval = {}
for _, form in ipairs(forms) do
export.insert_form_into_list(retval, call_map_function_obj(form, fun))
end
return retval
end
--[==[
Map a list-returning function over the form values in ``forms`` (a list of form objects in "full list form"; see
above). If an input form value is {"?"}, it is preserved on output and the function is not called. Otherwise, the
function is called with two arguments, the original form and manual translit; if manual translit isn't relevant, it's
fine to declare the function with only one argument. The return value of the function can be {nil} or an abbreviated
form list (i.e. anything that is convertible into a full list form, such as a single form value, a list of form
values, a form object or a list of form objects). For each form object in the return value, the footnotes of that form
object (if any) are combined with any footnotes from the input form object, and the result inserted into the returned
list using `insert_form_into_list()` in case two different forms map to the same thing.
]==]
function export.flatmap_forms(forms, fun)
if not forms then
return nil
end
local retval = {}
for _, form in ipairs(forms) do
local funret = form.form == "?" and form or fun(form.form, form.translit)
if funret then
funret = export.convert_to_general_list_form(funret)
for _, fr in ipairs(funret) do
local newform = {
form = fr.form,
translit = fr.translit,
footnotes = export.combine_footnotes(form.footnotes, fr.footnotes)
}
export.insert_form_into_list(retval, newform)
end
end
end
return retval
end
--[==[
Map a function over the form values in ``abforms`` (an abbreviated form list). If the input form value is {"?"}, it is
preserved on output and the function is not called. If ``first_only`` is given and ``abforms`` is a list, only map over
the first element. Return value is of the same form as ``abforms``, unless ``abforms`` is a string and the function
returns both form value and manual translit (in which case the return value is a form object). The function is called
with two arguments, the original form value and manual translit; if manual translit isn't relevant, it's fine to declare
the function with only one argument. The return value is either a single value (the new form value) or two values (the
new form value and new manual translit). The footnotes (if any) from the input form objects are preserved on output.
FIXME: This function is used only in [[Module:bg-verb]] and should be moved into that module.
]==]
function export.map_form_or_forms(abforms, fun, first_only)
if not abforms then
return nil
elseif type(abforms) == "string" then
return call_map_function_str(abforms, fun)
elseif abforms.form then
return call_map_function_obj(abforms, fun)
else
local retval = {}
for i, abform in ipairs(abforms) do
if first_only then
return export.map_form_or_forms(abform, fun)
end
insert(retval, export.map_form_or_forms(abform, fun))
end
return retval
end
end
--[==[
Expand a given footnote (as specified by the user, including the surrounding brackets) into the form to be inserted
into the final generated table. If ``no_parse_refs`` is not given and the footnote is a reference (of the form
{"[ref:...]"}), parse and return the specified reference(s). Two values are returned, `footnote_string` (the expanded
footnote, or nil if the second value is present) and `references` (a list of objects of the form
`{text = ``text``, name = ``name``, group = ``group``}` if the footnote is a reference and ``no_parse_refs`` is not
given, otherwise {nil}). Unless ``return_raw`` is given, the returned footnote string is capitalized and has a final
period added.
]==]
function export.expand_footnote_or_references(note, return_raw, no_parse_refs)
local _, notetext = extract_footnote_modifiers(note)
if not no_parse_refs and notetext:find("^ref:") then
-- a reference
notetext = notetext:gsub("^ref:", "")
local parsed_refs = require("Module:references").parse_references(notetext)
for i, ref in ipairs(parsed_refs) do
if type(ref) == "string" then
parsed_refs[i] = {text = ref}
end
end
return nil, parsed_refs
end
if footnote_abbrevs[notetext] then
notetext = footnote_abbrevs[notetext]
track("footnote-whole-abbrev")
else
local split_notes = split(notetext, "<(.-)>")
for i, split_note in ipairs(split_notes) do
if i % 2 == 0 then
split_notes[i] = footnote_abbrevs[split_note]
track("footnote-angle-bracket-abbrev")
if not split_notes[i] then
-- Don't error for now, because HTML might be in the footnote.
-- Instead we should switch the syntax here to e.g. <<a>> to avoid
-- conflicting with HTML.
split_notes[i] = "<" .. split_note .. ">"
track("footnote-unrecognized-angle-bracket-abbrev")
--error("Unrecognized footnote abbrev: <" .. split_note .. ">")
else
track("footnote-recognized-angle-bracket-abbrev")
end
end
end
notetext = concat(split_notes)
end
return return_raw and notetext or ucfirst(notetext) .. "."
end
--[==[
Convert a list of foonotes to qualifiers and references for use in [[Module:headword]] or similar. Returns two values,
a list of qualifiers (possibly {nil}) and a list of reference structures (possibly {nil}), following the structure
defined in [[Module:references]]).
]==]
function export.convert_footnotes_to_qualifiers_and_references(footnotes)
if not footnotes then
return nil
end
local quals, refs
for _, qualifier in ipairs(footnotes) do
local this_footnote, this_refs = export.expand_footnote_or_references(qualifier, "return raw")
if this_refs then
if not refs then
refs = this_refs
else
for _, ref in ipairs(this_refs) do
insert(refs, ref)
end
end
else
if not quals then
quals = {this_footnote}
else
insert(quals, this_footnote)
end
end
end
return quals, refs
end
--[==[
Combine an abbreviated form object (either a string or a table) with additional footnotes, possibly replacing the form
value and/or translit in the process. Normally called in one of two ways:
(1) `combine_form_and_footnotes(``form_obj``, ``addl_footnotes``, ``new_form``, ``new_translit``)` where ``form_obj``
is an existing abbreviated form object; ``addl_footnotes`` is either {nil}, a single string (a footnote) or a list
of footnotes; ``new_formval`` is either {nil} or the new form value to substitute; and ``new_translit`` is either
{nil} or the new translit string to substitute.
(2) `combine_form_and_footnotes(``form_value``, ``footnotes``)`, where ``form_value`` is a form value (a string) and
``footnotes`` is either {nil}, a single string (a footnote) or a list of footnotes.
In either case, a form object is returned, preserving as many properties as possible from any existing form object in
``abform``. Do the minimal amount of work; e.g. if ``abform`` is a form object and ``addl_footnotes``, ``new_formval``
and ``new_translit`` are all {nil}, the same object as passed in is returned. Under no circumstances is the existing
form object side-effected.
'''FIXME:''' This does not correctly preserve metadata.
]==]
function export.combine_form_and_footnotes(abform, addl_footnotes, new_formval, new_translit)
if type(addl_footnotes) == "string" then
addl_footnotes = {addl_footnotes}
end
if not addl_footnotes and not new_formval and not new_translit then
return abform
end
if type(abform) == "string" then
new_formval = new_formval or abform
return {form = new_formval, translit = new_translit, footnotes = addl_footnotes}
end
abform = shallow_copy(abform)
if new_formval then
abform.form = new_formval
end
if new_translit then
abform.translit = new_translit
end
if addl_footnotes then
abform.footnotes = export.combine_footnotes(abform.footnotes, addl_footnotes)
end
return abform
end
--[==[
Convert an abbreviated form list (either a string, form object, or list of either) into full list form.
This function does not side-effect any of the objects passed into ``abforms``, but will return ``abforms``
unchanged if already in full list form.
]==]
function export.convert_to_full_list_form(abforms)
if type(abforms) == "string" then
return {{form = abforms}}
elseif abforms.form then
return {abforms}
else
-- Check if already in full list form and return directly if so.
local must_convert = false
for _, form in ipairs(abforms) do
if type(form) == "string" then
must_convert = true
break
end
end
if not must_convert then
return abforms
end
end
local retval = {}
for _, form in ipairs(abforms) do
if type(form) == "string" then
insert(retval, {form = form})
else
insert(retval, form)
end
end
return retval
end
--[==[
Convert an abbreviated form list (either a string, form object, or list of either) into full list form. If
``footnotes`` is supplied, then for each form in the form list, combine the form's footnotes with ``footnotes``.
This function does not side-effect any of the objects passed into ``abforms``, but will return ``abforms``
unchanged if already in full list form and ``footnotes`` is {nil}.
'''FIXME:''' This does not correctly preserve metadata.
]==]
function export.convert_to_general_list_form(abforms, footnotes)
if type(footnotes) == "string" then
footnotes = {footnotes}
end
if type(abforms) == "string" then
return {{form = abforms, footnotes = footnotes}}
elseif abforms.form then
return {export.combine_form_and_footnotes(abforms, footnotes)}
elseif not footnotes then
-- Check if already in full list form and return directly if so.
local must_convert = false
for _, form in ipairs(abforms) do
if type(form) == "string" then
must_convert = true
break
end
end
if not must_convert then
return abforms
end
end
local retval = {}
for _, form in ipairs(abforms) do
if type(form) == "string" then
insert(retval, {form = form, footnotes = footnotes})
else
insert(retval, export.combine_form_and_footnotes(form, footnotes))
end
end
return retval
end
local function is_table_of_strings(forms)
for k, v in pairs(forms) do
if type(k) ~= "number" or type(v) ~= "string" then
return false
end
end
return true
end
local function lang_or_func_transliterate(func, lang, text)
local retval
if func then
retval = func(text)
else
retval = (lang:transliterate(text))
end
-- FIXME! Hack to work around bug in ...:transliterate(). Remove me as soon as this bug is fixed.
if not retval and (text == " " or text == "-" or text == "-́" or text == "?" or text == "-∅") then
retval = text
end
if not retval then
error(("Unable to transliterate text '%s'"):format(text))
end
return retval
end
--[==[
Combine ``stems`` and ``endings`` and store into slot ``slot`` of form table ``formtable``. Either of ``stems`` and
``endings`` can be {nil} or an abbreviated form list. The combination of a given stem and ending happens using
``combine_stem_ending``, which takes two parameters (stem and ending, each a string) and returns one value (a string).
If manual transliteration is present in either ``stems`` or ``endings``, ``lang`` (a language object or a function of
one argument to transliterate a string) along with ``combine_stem_ending_tr`` (a function for combining manual
transliterations that works much like ``combine_stem_ending``) must be given. ``footnotes``, if specified, is a list of
additional footnotes to attach to the resulting inflections (stem+ending combinations). The resulting inflections are
inserted into the form table using `insert_form()`, in case of duplication.
]==]
function export.add_forms(formtable, slot, stems, endings, combine_stem_ending, lang, combine_stem_ending_tr, footnotes)
if stems == nil or endings == nil then
return
end
local function combine(stem, ending)
if stem == "?" or ending == "?" then
return "?"
end
return combine_stem_ending(stem, ending)
end
local function transliterate(text)
return lang_or_func_transliterate(is_callable(lang) and lang or nil, lang, text)
end
if type(stems) == "string" and type(endings) == "string" then
export.insert_form(formtable, slot, {form = combine(stems, endings), footnotes = footnotes})
elseif type(stems) == "string" and is_table_of_strings(endings) then
for _, ending in ipairs(endings) do
export.insert_form(formtable, slot, {form = combine(stems, ending), footnotes = footnotes})
end
else
stems = export.convert_to_general_list_form(stems)
endings = export.convert_to_general_list_form(endings, footnotes)
for _, stem in ipairs(stems) do
for _, ending in ipairs(endings) do
local new_footnotes = nil
if stem.footnotes and ending.footnotes then
new_footnotes = shallow_copy(stem.footnotes)
for _, footnote in ipairs(ending.footnotes) do
insert_if_not(new_footnotes, footnote)
end
elseif stem.footnotes then
new_footnotes = stem.footnotes
elseif ending.footnotes then
new_footnotes = ending.footnotes
end
local new_form = combine(stem.form, ending.form)
local new_translit
if new_form ~= "?" and (stem.translit or ending.translit) then
if not lang or not combine_stem_ending_tr then
error("Internal error: With manual translit, 'lang' and 'combine_stem_ending_tr' must be passed to 'add_forms'")
end
local stem_tr = stem.translit or transliterate(m_links.remove_links(stem.form))
local ending_tr = ending.translit or transliterate(m_links.remove_links(ending.form))
new_translit = combine_stem_ending_tr(stem_tr, ending_tr)
end
export.insert_form(formtable, slot, {form = new_form, translit = new_translit, footnotes = new_footnotes})
end
end
end
end
--[==[
Combine any number of form components and store into slot ``slot`` of form table ``formtable``. ``components`` is a list of abbreviated form
lists which should be concatenated similarly to how `add_forms()` does it, and stored in ``slot`` along with any footnotes in ``footnotes``.
More specifically:
# If there are no components, nothing happens.
# If there is one component, it is converted to full list form and `insert_forms()` called.
# If there are two components, they are treated as stems and endings respectively and `add_forms()` is called.
# If there are three or more components, they are concatenated left-to-right in the manner of a `reduce()` operation: the first two components
are combined using `add_forms()` and stored into a temporary table, then the next component is combined with the result of the previous
operation, etc. In the last combination, footnotes in `footnotes` are combined in, and the result stored into `formtable`.
This should generally be used when you are likely to have three or more components, as in [[Module:ar-verb]] (prefixes, stems and endings)
and [[Module:de-verb]] (which in some situations has five components combined together). ``combine_stem_ending``, ``lang``,
``combine_stem_ending_tr`` and ``footnotes`` are as in `add_forms()`.
]==]
function export.add_multiple_forms(formtable, slot, components, combine_stem_ending, lang, combine_stem_ending_tr, footnotes)
if #components == 0 then
return
elseif #components == 1 then
local forms = export.convert_to_general_list_form(components[1], footnotes)
export.insert_forms(formtable, slot, forms)
elseif #components == 2 then
local stems = components[1]
local endings = components[2]
export.add_forms(formtable, slot, stems, endings, combine_stem_ending, lang, combine_stem_ending_tr, footnotes)
else
local prev = components[1]
for i=2, #components do
local temptable = {}
export.add_forms(temptable, slot, prev, components[i], combine_stem_ending, lang, combine_stem_ending_tr,
i == #components and footnotes or nil)
prev = temptable[slot]
end
export.insert_forms(formtable, slot, prev)
end
end
local function iterate_slot_list_or_table(props, do_slot)
if props.slot_list then
for _, slot_and_accel_tag_set in ipairs(props.slot_list) do
local slot, accel_tag_set = unpack(slot_and_accel_tag_set)
do_slot(slot, accel_tag_set)
end
else
for slot, accel_tag_set in pairs(props.slot_table) do
do_slot(slot, accel_tag_set)
end
end
end
function export.default_split_bracketed_runs_into_words(bracketed_runs, data)
-- If the text begins with a hyphen, include the hyphen in the set of allowed characters
-- for an inflected segment. This way, e.g. conjugating "-ir" is treated as a regular
-- -ir verb rather than a hyphen + irregular [[ir]].
local is_suffix = (not data or data.text_index == 1) and bracketed_runs[1]:find("^%-")
local split_pattern = is_suffix and " " or "[ %-]"
return put.split_alternating_runs(bracketed_runs, split_pattern, "preserve splitchar")
end
local function props_transliterate(props, text)
return lang_or_func_transliterate(props.transliterate, props.lang, text)
end
local function parse_before_or_post_text(data)
-- NOTE: data.text_index is also used (in `default_split_bracketed_runs_into_words`).
local props, text, segments, lemma_is_last = data.props, data.text, data.segments, data.lemma_is_last
-- Call parse_balanced_segment_run() to keep multiword links together.
local bracketed_runs = put.parse_balanced_segment_run(text, "[", "]")
-- Split normally on space or hyphen (but customizable). Use preserve_splitchar so we know whether the separator was
-- a space or hyphen.
local space_separated_groups
if props.split_bracketed_runs_into_words then
space_separated_groups = props.split_bracketed_runs_into_words(bracketed_runs)
end
if not space_separated_groups then
space_separated_groups = export.default_split_bracketed_runs_into_words(bracketed_runs, data)
end
local parsed_components = {}
local parsed_components_translit = {}
local saw_manual_translit = false
local lemma
for j, space_separated_group in ipairs(space_separated_groups) do
local component = concat(space_separated_group)
if lemma_is_last and j == #space_separated_groups then
lemma = component
if lemma == "" and not props.allow_blank_lemma then
error("Word is blank: '" .. concat(segments) .. "'")
end
elseif component:find("//") then
-- Manual translit or respelling specified.
if not props.lang then
error("Manual translit not allowed for this language; if this is incorrect, 'props.lang' must be set internally")
end
saw_manual_translit = true
local split_components = split(component, "//", "plain")
if #split_components ~= 2 then
error("Term with translit or respelling should have only one // in it: " .. component)
end
local translit
component, translit = unpack(split_components)
if props.transliterate_respelling then
translit = props.transliterate_respelling(translit)
end
insert(parsed_components, component)
insert(parsed_components_translit, translit)
else
insert(parsed_components, component)
insert(parsed_components_translit, false) -- signal that it may need later transliteration
end
end
if saw_manual_translit then
for j, parsed_component in ipairs(parsed_components) do
if not parsed_components_translit[j] then
parsed_components_translit[j] = props_transliterate(props, m_links.remove_links(parsed_component))
end
end
end
text = concat(parsed_components)
local translit
if saw_manual_translit then
translit = concat(parsed_components_translit)
end
return text, translit, lemma
end
--[=[
Parse a segmented multiword spec such as "[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>" (in Ukrainian).
"Segmented" here means it is broken up on <...> segments using parse_balanced_segment_run(text, "<", ">"),
e.g. the above text would be passed in as {"[[медичний|меди́чна]]", "<+>", " [[сестра́]]", "<*,*#.pr>", ""}.
The return value is a table of the form
{
word_specs = {``word_spec``, ``word_spec``, ...},
post_text = "``text-at-end``",
post_text_no_links = "``text-at-end-no-links``",
post_text_translit = "``manual-translit-of-text-at-end``" or nil (if no manual translit or respelling was specified in the post-text)
}
where ``word_spec`` describes an individual inflected word and "``text-at-end``" is any raw text that may occur
after all inflected words. Individual words or linked text (including multiword text) may be given manual
transliteration or respelling in languages that support this using ``text``//``translit`` or ``text``//``respelling``.
Each ``word_spec`` is of the form returned by parse_indicator_spec():
{
lemma = "``lemma``",
before_text = "``text-before-word``",
before_text_no_links = "``text-before-word-no-links``",
before_text_translit = "``manual-translit-of-text-before-word``" or nil (if no manual translit or respelling was specified in the before-text)
-- Fields as described in parse_indicator_spec()
...
}
For example, the return value for "[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>" is
{
word_specs = {
{
lemma = "[[медичний|меди́чна]]",
overrides = {},
adj = true,
before_text = "",
before_text_no_links = "",
forms = {},
},
{
lemma = "[[сестра́]]",
overrides = {},
stresses = {
{
reducible = true,
genpl_reversed = false,
},
{
reducible = true,
genpl_reversed = true,
},
},
animacy = "pr",
before_text = " ",
before_text_no_links = " ",
forms = {},
},
},
post_text = "",
post_text_no_links = "",
}
]=]
local function parse_multiword_spec(segments, props, disable_allow_default_indicator)
local multiword_spec = {
word_specs = {}
}
if not disable_allow_default_indicator then
if #segments == 1 then
if props.allow_default_indicator then
insert(segments, "<>")
insert(segments, "")
elseif props.angle_brackets_omittable then
segments[1] = "<" .. segments[1] .. ">"
insert(segments, 1, "")
insert(segments, "")
end
end
end
-- Loop over every other segment. The even-numbered segments are angle-bracket specs while
-- the odd-numbered segments are the text between them.
for i = 2, #segments - 1, 2 do
local before_text, before_text_translit, lemma =
parse_before_or_post_text {
props = props,
text = segments[i - 1],
text_index = i - 1,
segments = segments,
lemma_is_last = true
}
local base = props.parse_indicator_spec(segments[i], lemma)
base.before_text = before_text
base.before_text_no_links = m_links.remove_links(base.before_text)
base.before_text_translit = before_text_translit
base.lemma = base.lemma or lemma
insert(multiword_spec.word_specs, base)
end
multiword_spec.post_text, multiword_spec.post_text_translit =
parse_before_or_post_text {
props = props,
text = segments[#segments],
text_index = #segments,
segments = segments,
lemma_is_last = false
}
multiword_spec.post_text_no_links = m_links.remove_links(multiword_spec.post_text)
return multiword_spec
end
--[=[
Parse an alternant, e.g. "((родо́вий,родови́й))" or "((ру́син<pr>,руси́н<b.pr>))" (both in Ukrainian).
The return value is a table of the form
{
alternants = {``multiword_spec``, ``multiword_spec``, ...}
}
where ``multiword_spec`` describes a given alternant and is as returned by parse_multiword_spec().
]=]
local function parse_alternant(alternant, props)
local alternant_text = alternant:match("^%(%((.*)%)%)$")
local segments = put.parse_balanced_segment_run(alternant_text, "<", ">")
local comma_separated_groups = put.split_alternating_runs(segments, "%s*,%s*")
local alternant_spec = {alternants = {}}
for _, comma_separated_group in ipairs(comma_separated_groups) do
insert(alternant_spec.alternants, parse_multiword_spec(comma_separated_group, props))
end
return alternant_spec
end
--[==[
Top-level parsing function. Parse text describing one or more inflected words. `text` is the inflected text to parse,
which generally has `<...>` specs following words to be inflected, and may have alternants indicated using double
parens. Examples:
* {"[[медичний|меди́чна]]<+> [[сестра́]]<*,*#.pr>"} (Ukrainian, for {{m|uk|меди́чна сестра́||nurse|lit=medical sister}});
* {"((ру́син<pr>,руси́н<b.pr>))"} (Ukrainian, for {{m|uk|русин||Rusyn}}, with two possible stress patterns);
* {"पंचायती//पंचाय*ती राज<M>"} (Hindi, for {{m|hi|पंचायती राज||village council}}, with phonetic respelling in the
before-text component);
* {"((<M>,<M.plstem:फ़तूह.dirpl:फ़तूह>))"} (Hindi, for {{m|hi|फ़तह||win, victory}} when used on that page, where the lemma
is omitted and taken from the pagename);
* {""} (for any number of Hindi adjectives, where the lemma is omitted and taken from the pagename, and the angle
bracket spec <> is assumed);
* {"काला<+>धन<M>"} (Hindi, for {{m|hi|कालाधन||black money}}, showing that closed compounds where each part is declined
can be correctly handled).
`props` is an object specifying properties used during parsing, as follows:
```{
parse_indicator_spec = __function__(``angle_bracket_spec``, ``lemma``) `''(required)''`,
lang = __lang object__,
transliterate_respelling = __function__(``respelling_or_translit``) `''(optional)''`,
split_bracketed_runs_into_words = __function__(``bracket_split_runs``) `''(optional)''`,
allow_default_indicator = __boolean__,
angle_brackets_omittable = __boolean__,
allow_blank_lemma = __boolean__,
}```
`parse_indicator_spec` is a required function that takes two arguments, a string surrounded by angle brackets and the
lemma, and should return an arbitrary object containing properties describing the indicators inside of the angle
brackets). This object is often called a '''base''' and given the argument name `base` in inflection code.
`lang` is the language object for the language in question; only needed if manual translit or respelling may be present
using `//`.
`transliterate_respelling` is a function that is only needed if respelling is allowed in place of manual translit after
`//`. It takes one argument, the respelling or translit, and should return the transliteration of any respelling but
return any translit unchanged.
`split_bracketed_runs_into_words` is an optional function to split the passed-in text into words. It is used, for
example, to determine what text constitutes a word when followed by an angle-bracket spec, i.e. what the lemma to be
inflected is vs. surrounding fixed text. It takes one argument, the result of splitting the original text on brackets,
and should return alternating runs of words and split characters, or nil to apply the default algorithm. Specifically,
the value passed in is the result of calling `parse_balanced_segment_run(``text``, "[", "]")` from
[[Module:parse utilities]] on the original text, and the default version of this function calls
`split_alternating_runs(``bracketed_runs``, ``pattern``, "preserve splitchar")`, where ``bracketed_runs`` is the value
passed in and ``pattern`` splits on either spaces or hyphens (unless the text begins with a hyphen, in which case
splitting is only on spaces, so that suffixes can be inflected).
`allow_default_indicator` should be {true} if an empty indicator in angle brackets `<>` can be omitted and should be
automatically added at the end of the multiword text (if no alternants) or at the end of each alternant (if alternants
present).
`angle_brackets_omittable` should be {true} if angle brackets can be omitted around a non-empty indicator in the
presence of a blank lemma. In this case, if the combined indicator spec has no angle brackets, they will be added around
the indicator (or around all indicators, if alternants are present). This only makes sense when `allow_blank_lemma` is
specified.
`allow_blank_lemma` should be {true} of if a blank lemma is allowed; in such a case, the calling function should
substitute a default lemma, typically taken from the pagename.
The return value is a table referred to as an '''alternant multiword spec''', and is of the form
```{
alternant_or_word_specs = {``alternant_or_word_spec``, ``alternant_or_word_spec``, ...},
post_text = "``text_at_end``",
post_text_no_links = "``text_at_end_no_links``",
post_text_translit = "``translit_of_text_at_end``" `(or nil)`,
}```
where `alternant_or_word_spec` is either an '''alternant spec''' as returned by `parse_alternant()` or a
'''multiword spec''' as described in the comment above `parse_multiword_spec()`. An alternant spec looks as follows:
```{
alternants = {``multiword_spec``, ``multiword_spec``, ...},
before_text = "``text_before_alternant``",
before_text_no_links = "``text_before_alternant``",
before_text_translit = "``translit_of_text_before_alternant``" `(or nil)`,
}```
i.e. it is like what is returned by `parse_alternant()` but has extra `before_text` and `before_text_no_links` fields.
]==]
function export.parse_inflected_text(text, props)
if props.angle_brackets_omittable and not props.allow_blank_lemma then
error("If 'angle_brackets_omittable' is specified, so should 'allow_blank_lemma'")
end
local alternant_multiword_spec = {alternant_or_word_specs = {}}
local alternant_segments = split(text, "(%(%(.-%)%))")
local last_post_text, last_post_text_no_links, last_post_text_translit
for i = 1, #alternant_segments do
if i % 2 == 1 then
local segments = put.parse_balanced_segment_run(alternant_segments[i], "<", ">")
-- Disable allow_default_indicator if alternants are present and we're processing
-- the non-alternant text. Otherwise we will try to treat the non-alternant text
-- surrounding the alternants as an inflected word rather than as raw text.
local multiword_spec = parse_multiword_spec(segments, props, #alternant_segments ~= 1)
for _, word_spec in ipairs(multiword_spec.word_specs) do
insert(alternant_multiword_spec.alternant_or_word_specs, word_spec)
end
last_post_text = multiword_spec.post_text
last_post_text_no_links = multiword_spec.post_text_no_links
last_post_text_translit = multiword_spec.post_text_translit
else
local alternant_spec = parse_alternant(alternant_segments[i], props)
alternant_spec.before_text = last_post_text
alternant_spec.before_text_no_links = last_post_text_no_links
alternant_spec.before_text_translit = last_post_text_translit
insert(alternant_multiword_spec.alternant_or_word_specs, alternant_spec)
end
end
alternant_multiword_spec.post_text = last_post_text
alternant_multiword_spec.post_text_no_links = last_post_text_no_links
alternant_multiword_spec.post_text_translit = last_post_text_translit
-- Save boolean properties from `props`. We need at least `allow_default_indicator` when implementing
-- `reconstruct_original_spec()`.
alternant_multiword_spec.allow_default_indicator = props.allow_default_indicator
alternant_multiword_spec.angle_brackets_omittable = props.angle_brackets_omittable
alternant_multiword_spec.allow_blank_lemma = props.allow_blank_lemma
return alternant_multiword_spec
end
-- Inflect alternants in ``alternant_spec`` (an object as returned by parse_alternant()).
-- This sets the form values in ```alternant_spec``.forms` for all slots.
-- (If a given slot has no values, it will not be present in ```alternant_spec``.forms`).
local function inflect_alternants(alternant_spec, props)
alternant_spec.forms = {}
for _, multiword_spec in ipairs(alternant_spec.alternants) do
export.inflect_multiword_or_alternant_multiword_spec(multiword_spec, props)
iterate_slot_list_or_table(props, function(slot)
if not props.skip_slot or not props.skip_slot(slot) then
export.insert_forms(alternant_spec.forms, slot, multiword_spec.forms[slot])
end
end)
end
end
--[=[
Subfunction of `inflect_multiword_or_alternant_multiword_spec()`. This is used in building up the inflections of
multiword expressions. The basic purpose of this function is to append a set of forms representing the inflections of
a given inflected term in a given slot onto the existing forms for that slot. Given a multiword expression potentially
consisting of several inflected terms along with fixed text in between, we work iteratively from left to right, adding
the new forms onto the existing ones. Normally, all combinations of new and existing forms are created, meaning if
there are M existing forms and N new ones, we will end up with M*N forms. However, some of these combinations can be
rejected using the variant mechanism (see the description of get_variants below).
Specifically, `formtable` is a table of per-slot forms, where the key is a slot and the value is a list of form objects
(objects of the form {form=``form``, translit=``manual_translit``, footnotes=``footnotes``}). `slot` is the slot in question.
`forms` specifies the forms to be appended onto the existing forms, and is likewise a list of form objects. `props`
is the same as in `inflect_multiword_or_alternant_multiword_spec()`. `before_text` is the fixed text that goes before
the forms to be added. `before_text_no_links` is the same as `before_text` but with any links (i.e. hyperlinks of the
form [[``term``]] or [[``term``|``display``]]) converted into raw terms using remove_links() in [[Module:links]], and
`before_text_translit` is optional manual translit of `before_text_no_links`.
Note that the value "?" in a form is "infectious" in that if either the existing or new form has the value "?", the
resulting combination will also be "?". This allows "?" to be used to mean "unknown".
]=]
local function append_forms(props, formtable, slot, forms, before_text, before_text_no_links, before_text_translit)
if not forms then
return
end
local old_forms = formtable[slot] or {{form = ""}}
local ret_forms = {}
for _, old_form in ipairs(old_forms) do
for _, form in ipairs(forms) do
local old_form_vars = props.get_variants and props.get_variants(old_form.form) or ""
local form_vars = props.get_variants and props.get_variants(form.form) or ""
if old_form_vars ~= "" and form_vars ~= "" and old_form_vars ~= form_vars then
-- Reject combination due to non-matching variant codes.
else
local new_formval
local new_translit
if old_form.form == "?" or form.from == "?" then
new_formval = "?"
else
new_formval = old_form.form .. before_text .. form.form
if old_form.translit or before_text_translit or form.translit then
if not props.lang then
error("Internal error: If manual translit is given, 'props.lang' must be set")
end
if not before_text_translit then
before_text_translit = props_transliterate(props, before_text_no_links) or ""
end
local old_translit =
old_form.translit or props_transliterate(props, m_links.remove_links(old_form.form)) or ""
local translit =
form.translit or props_transliterate(props, m_links.remove_links(form.form)) or ""
new_translit = old_translit .. before_text_translit .. translit
end
end
local new_formobj
local new_footnotes = export.combine_footnotes(old_form.footnotes, form.footnotes)
if new_formval == form.form and new_translit == form.translit then
-- Automatically preserve metadata when possible.
new_formobj = shallow_copy(form)
new_formobj.footnotes = new_footnotes
else
new_formobj = {form=new_formval, translit=new_translit, footnotes=new_footnotes}
if props.combine_metadata then
props.combine_metadata {
slot = slot,
dest_form = new_formobj,
form1 = old_form,
form2 = form,
between_text = before_text,
between_text_no_links = before_text_no_links,
between_text_translit = before_text_translit,
}
end
end
insert(ret_forms, new_formobj)
end
end
end
formtable[slot] = ret_forms
end
--[==[
Top-level inflection function. Create the inflections of a noun, verb, adjective or similar. `alternant_multiword_spec`
is as
returned by `parse_inflected_text` and describes the properties of the term to be inflected, including all the
user-provided inflection specifications (e.g. the number, gender, conjugation/declension/etc. of each word) and the
surrounding text. `props` indicates how to do the actual inflection (see below). The resulting inflected forms are
stored into the `.forms` property of `multiword_spec`. This property holds a table whose keys are slots (i.e. ID's
of individual inflected forms, such as "pres_1sg" for the first-person singular present indicative tense of a verb)
and whose values are lists of the form `{ form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``}`,
where ``form`` is a string specifying the value of the form (e.g. "ouço" for the first-person singular present indicative
of the Portuguese verb [[ouvir]]); ``manual_translit_or_nil`` is the corresponding manual transliteration if needed (i.e.
if the form is in a non-Latin script and the automatic transliteration is incorrect or unavailable), otherwise nil;
and ``footnote_list_or_nil`` is a list of footnotes to be attached to the form, or nil for no footnotes. Note that
currently footnotes must be surrounded by brackets, e.g "[archaic]", and should not begin with a capital letter or end
with a period. (Conversion from "[archaic]" to "Archaic." happens automatically.)
This function has no return value, but modifies `multiword_spec` in-place, adding the `forms` table as described above.
After calling this function, call show_forms() on the `forms` table to convert the forms and footnotes given in this
table to strings suitable for display.
`props` is an object specifying properties used during inflection, as follows:
```{
slot_list = {{"``slot``", "``accel``"}, {"``slot``", "``accel``"}, ...},
slot_table = {``slot`` = "``accel``", ``slot`` = "``accel``", ...},
skip_slot = nil `or` __function__(slot),
lang = nil `or` __lang_object__,
inflect_word_spec = __function__(base),
get_variants = nil 'or` __function__(formval),
combine_metadata = nil `or` __function__(data),
include_user_specified_links = __boolean__,
}```
`slot_list` is a list of two-element lists of slots and associated accelerator tags. ``slot`` is arbitrary but should
correspond with slot names as generated by `inflect_word_spec`. ``accel`` is the corresponding accelerator tags; e.g. if
``slot`` is "pres_1sg", ``accel`` might be "1|s|pres|ind". ``accel`` is actually unused during inflection, but is used
during `show_forms()`, which takes the same `slot_list` as a property upon input.
`slot_table` is a table mapping slots to associated accelerator tags and serves the same function as `slot_list`. Only
one of `slot_list` or `slot_table` must be given. For new code it is preferable to use `slot_list` because this allows
you to control the order of processing slots, which may occasionally be important.
`skip_slot` is a function of one argument, a slot name, and should return a boolean indicating whether to skip the
given slot during inflection. It can be used, for example, to skip singular slots if the overall term being inflected
is plural-only, and vice-versa.
`lang` is a language object. This is only used to generate manual transliteration. If the language is written in the
Latin script or manual transliteration cannot be specified in the input to parse_inflected_text(), this can be omitted.
(Manual transliteration is allowed if the `lang` object is set in the `props` passed to parse_inflected_text().)
`inflect_word_spec` is the function to do the actual inflection. It is passed a single argument, which is a ``word_spec``
object describing the word to be inflected and the user-provided inflection specifications. It is exactly the same as
was returned by the `parse_indicator_spec` function provided in the `props` sent on input to `parse_inflected_text`, but
has additional fields describing the word to be inflected and the surrounding text, as follows:
```{
lemma = "``lemma``",
before_text = "``text-before-word``",
before_text_no_links = "``text-before-word-no-links``",
before_text_translit = "``manual-translit-of-text-before-word``" or nil (if no manual translit or respelling was specified in the before-text)
-- Fields as described in parse_indicator_spec()
...
}```
Here ``lemma`` is the word to be inflected as specified by the user (including any links if so given), and the
`before_text*` fields describe the raw text preceding the word to be inflected. Any other fields in this object are as
set by `parse_inflected_text`, and describe things like the gender, number, conjugation/declension, etc. as specified
by the user in the <...> spec following the word to be inflected.
`inflect_word_spec` should initialize the `.forms` property of the passed-in ``word_spec`` object to the inflected forms of
the word in question. The value of this property is a table of the same format as the `.forms` property that is
ultimately generated by inflect_multiword_or_alternant_multiword_spec() and described above near the top of this
documentation: i.e. a table whose keys are slots and whose values are lists of the form
`{ form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``}`.
`get_variants` is either {nil} or a function of one argument (a string, a form value). The purpose of
this function is to ensure that in a multiword term where a given slot has more than one possible variant, the final
output has only parallel variants in it. For example, feminine nouns and adjectives in Russian have two possible
endings, one typically in -ой (-oj) and the other in -ою (-oju). If we have a feminine adjective-noun combination (or
a hyphenated feminine noun-noun combination, or similar), and we don't specify `get_variants`, we'll end up with four
values for the instrumental singular: one where both adjective and noun end in -ой, one where both end in -ою, and
two where one of the words ends in -ой and the other in -ою. In general if we have N words each with K variants, we'll
end up with an explosion of N^K possibilities. `get_variants` avoids this by returning a variant code (an arbitary
string) for each variant. If two words each have a non-empty variant code, and the variant codes disagree, the
combination will be rejected. If `get_variants` is not provided, or either variant code is an empty string, or the
variant codes agree, the combination is allowed.
The recommended way to use `get_variants` is as follows:
1. During inflection in `inflect_word_spec`, add a special character or string to each of the variants generated for a
given slot when there is more than one. (As an optimization, do this only when there is more than one word being
inflected.) Special Unicode characters can be used for this purpose, e.g. U+FFF0, U+FFF1, ..., U+FFFD, which have
no meaning in Unicode.
2. Specify `get_variants` as a function that pulls out and returns the special character(s) or string included in the
variant forms.
3. When calling show_forms(), specify a `canonicalize` function that removes the variant code character(s) or string
from each form before converting to the display form.
See [[Module:hi-verb]] and [[Module:hi-common]] for an example of doing this in a generalized fashion. (Look for
add_variant_codes(), get_variants() and remove_variant_codes().)
`combine_metadata` is a function that is invoked when combining two form objects along along with in-between text and
storing into a destination form object. When this happens, if the the form value and translit in the first form object
is empty and the in-between text is likewise empty (which regularly happens when appending the form object describing
the first word in a multiword expression to empty base text), the second form object is simply shallow-copied along with
all of its metadata, and any footnotes are combined appropriately (normally the first form object is such a case won't
have footnotes). Otherwise, a new form object is constructed by combining the form values, translit and footnotes from
the two objects and in-between text, and calling `combine_metadata` to combine any other metadata. Leave this
unspecified if there is no additional metadata or if you don't want any metadata carried over. (Examples of metadata
that should generally not be carried over are glosses of individual words, sense ID's and similar word-level properties
that can't easily be combined to generate a multiword equivalent. Examples of metadata that should be carried over and
combined are qualifiers, labels and certain boolean properties such as an uncertainty flag indicating that a given form
is uncertain. For some metadata, it is more complex; for example, if both source words have the same gender or part of
speech, the destination should keep that value, but if they differ, it may be safest to leave the field blank.) This
function, if specified, is called with a single argument as follows:
```{
slot = "__string__",
dest_form = __formobj__,
form1 = __formobj__,
form2 = __formobj__,
between_text = "__string__",
between_text_no_links = "__string__",
between_text_translit = "__string__" `or` nil
}```
Here, `slot` is the slot whose forms are being constructed. `dest_form` is the destination form object into which the
combined metadata should be written, and is pre-populated with appropriate `form`, `translit` and `footnotes` fields.
`form1` and `form2` are the two source forms being combined, and `between_text` is the text to be inserted between the
two source forms. `between_text_no_links` is the same as `between_text` but with double-bracket links removed, and
`between_text_translit` is the manual transliteration of `between_text_no_links`, if specified. The function should
return nothing, but should side-effect `dest_form` as appropriate.
`include_user_specified_links`, if given, ensures that user-specified links in the raw text surrounding a given word
are preserved in the output. If omitted or set to false, such links will be removed and the whole multiword expression
will be linked.
]==]
function export.inflect_multiword_or_alternant_multiword_spec(multiword_spec, props)
multiword_spec.forms = {}
local is_alternant_multiword = not not multiword_spec.alternant_or_word_specs
for _, word_spec in ipairs(is_alternant_multiword and multiword_spec.alternant_or_word_specs or multiword_spec.word_specs) do
if word_spec.alternants then
inflect_alternants(word_spec, props)
else
props.inflect_word_spec(word_spec)
end
iterate_slot_list_or_table(props, function(slot)
if not props.skip_slot or not props.skip_slot(slot) then
append_forms(props, multiword_spec.forms, slot, word_spec.forms[slot],
(slot:find("linked") or props.include_user_specified_links) and
word_spec.before_text or word_spec.before_text_no_links,
word_spec.before_text_no_links, word_spec.before_text_translit
)
end
end)
end
if multiword_spec.post_text ~= "" then
local pseudoform = {{form=""}}
iterate_slot_list_or_table(props, function(slot)
-- If slot is empty or should be skipped, don't try to append post-text.
if (not props.skip_slot or not props.skip_slot(slot)) and multiword_spec.forms[slot] then
append_forms(props, multiword_spec.forms, slot, pseudoform,
(slot:find("linked") or props.include_user_specified_links) and
multiword_spec.post_text or multiword_spec.post_text_no_links,
multiword_spec.post_text_no_links, multiword_spec.post_text_translit
)
end
end)
end
end
function export.map_word_specs(alternant_multiword_spec, fun)
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
fun(word_spec)
end
end
else
fun(alternant_or_word_spec)
end
end
end
function export.create_footnote_obj()
return {
notes = {},
seen_notes = {},
noteindex = 1,
seen_refs = {},
}
end
function export.get_footnote_text(footnotes, footnote_obj)
if not footnotes then
return ""
end
local link_indices = {}
local all_refs = {}
for _, footnote in ipairs(footnotes) do
local refs
footnote, refs = export.expand_footnote_or_references(footnote)
if footnote then
local this_noteindex = footnote_obj.seen_notes[footnote]
if not this_noteindex then
-- Generate a footnote index.
this_noteindex = footnote_obj.noteindex
footnote_obj.noteindex = footnote_obj.noteindex + 1
insert(footnote_obj.notes, '<sup style="color: var(--wikt-palette-red, red)">' .. this_noteindex .. '</sup>' .. footnote)
footnote_obj.seen_notes[footnote] = this_noteindex
end
insert_if_not(link_indices, this_noteindex)
end
if refs then
for _, ref in ipairs(refs) do
if not ref.name then
local this_refhash = footnote_obj.seen_refs[ref.text]
if not this_refhash then
-- Different text needs to have different auto-generated names, globally across the entire page,
-- including across different invocations of {{it-verb}} or {{it-conj}}. The easiest way to accomplish
-- this is to use a message-digest hashing function. It does not have to be cryptographically secure
-- (MD5 is insecure); it just needs to have low probability of collisions.
this_refhash = mw.hash.hashValue("md5", ref.text)
footnote_obj.seen_refs[ref.text] = this_refhash
end
ref.autoname = this_refhash
end
-- I considered using "n" as the default group rather than nothing, to more clearly distinguish regular
-- footnotes from references, but this requires referencing group "n" as <references group="n"> below,
-- which is non-obvious.
insert_if_not(all_refs, ref)
end
end
end
table.sort(link_indices)
local function sort_refs(r1, r2)
-- FIXME, we are now sorting on an arbitrary hash. Should we keep track of the order we
-- saw the autonamed references and sort on that?
if r1.autoname and r2.name then
return true
elseif r1.name and r2.autoname then
return false
elseif r1.name and r2.name then
return r1.name < r2.name
else
return r1.autoname < r2.autoname
end
end
table.sort(all_refs, sort_refs)
for i, ref in ipairs(all_refs) do
local refargs = {name = ref.name or ref.autoname, group = ref.group}
all_refs[i] = mw.getCurrentFrame():extensionTag("ref", ref.text, refargs)
end
local link_text
if #link_indices > 0 then
link_text = '<sup style="color: var(--wikt-palette-red, red)">' .. concat(link_indices, ",") .. '</sup>'
else
link_text = ""
end
local ref_text = concat(all_refs)
if link_text ~= "" and ref_text ~= "" then
return link_text .. "<sup>,</sup>" .. ref_text
else
return link_text .. ref_text
end
end
--[==[
Add links around words in a term. If multiword_only, do it only in multiword terms.
]==]
function export.add_links(form, multiword_only)
if form == "" or form == " " then
return form
end
if not form:find("%[%[") then
if ufind(form, "[%s%p]") then --optimization to avoid loading [[Module:headword]] on single-word forms
local m_headword = require("Module:headword")
if m_headword.head_is_multiword(form) then
form = m_headword.add_multiword_links(form)
end
end
if not multiword_only and not form:find("%[%[") then
form = "[[" .. form .. "]]"
end
end
return form
end
--[==[
Remove redundant link surrounding entire term.
]==]
function export.remove_redundant_links(term)
return (term:gsub("^%[%[([^%[%]|]*)%]%]$", "%1"))
end
--[==[
Add links to all before and after text; for use in inflection modules that preserve links in multiword lemmas and
include links in non-lemma forms rather than allowing the entire form to be a link. If `remember_original`, remember
the original user-specified before/after text so we can reconstruct the original spec later. `add_links` is a
function of one argument to add links to a given piece of text; if unspecified, it defaults to `export.add_links`.
]==]
function export.add_links_to_before_and_after_text(alternant_multiword_spec, remember_original, add_links)
add_links = add_links or export.add_links
local function add_links_remember_original(object, field)
if remember_original then
object["user_specified_" .. field] = object[field]
end
object[field] = add_links(object[field])
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
add_links_remember_original(alternant_or_word_spec, "before_text")
if alternant_or_word_spec.alternants then
for _, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
for _, word_spec in ipairs(multiword_spec.word_specs) do
add_links_remember_original(word_spec, "before_text")
end
add_links_remember_original(multiword_spec, "post_text")
end
end
end
add_links_remember_original(alternant_multiword_spec, "post_text")
end
--[==[
Reconstruct the original overall spec from the output of parse_inflected_text(), so we can use it in the
language-specific acceleration module in the implementation of {{tl|pt-verb form of}} and the like. `props` is an
optional table of properties. Currently only `preprocess_angle_bracket_spec` is recognized, and is an optional function
of one argument that is called to process an angle-bracket spec before inserting into the reconstructed spec.
]==]
function export.reconstruct_original_spec(alternant_multiword_spec, props)
local parts = {}
props = props or {}
local function ins(txt)
insert(parts, txt)
end
local function insert_angle_bracket_spec(spec)
if props.preprocess_angle_bracket_spec then
spec = props.preprocess_angle_bracket_spec(spec)
end
ins(spec)
end
for _, alternant_or_word_spec in ipairs(alternant_multiword_spec.alternant_or_word_specs) do
ins(alternant_or_word_spec.user_specified_before_text)
if alternant_or_word_spec.alternants then
ins("((")
for i, multiword_spec in ipairs(alternant_or_word_spec.alternants) do
if i > 1 then
ins(",")
end
for _, word_spec in ipairs(multiword_spec.word_specs) do
ins(word_spec.user_specified_before_text)
ins(word_spec.user_specified_lemma)
insert_angle_bracket_spec(word_spec.angle_bracket_spec)
end
ins(multiword_spec.user_specified_post_text)
end
ins("))")
else
ins(alternant_or_word_spec.user_specified_lemma)
insert_angle_bracket_spec(alternant_or_word_spec.angle_bracket_spec)
end
end
ins(alternant_multiword_spec.user_specified_post_text)
local retval = concat(parts)
if alternant_multiword_spec.allow_default_indicator then
-- As a special case, if we see e.g. "amar<>", remove the <>. Don't do this if there are spaces or alternants.
if not retval:find(" ") and not retval:find("%(%(") then
local retval_no_angle_brackets = retval:match("^(.*)<>$")
if retval_no_angle_brackets then
return retval_no_angle_brackets
end
end
end
return retval
end
--[==[
Convert the forms in ``formtable`` (a form table, whose keys are slots and whose values are lists of form objects, each
of which is a table of the form `form = ``form``, translit = ``manual_translit_or_nil``, footnotes = ``footnote_list_or_nil``, no_accel = ``true_to_suppress_accelerators``, ... `)
into strings. The form table is side-effected. Each form list turns into a string consisting of a comma-separated list
of linked forms, with accelerators (unless `no_accel` is set in a given form object). If `include_translit` is
specified, each string consists of a comma-separated list of form values (each formatted as a link), an HTML
`<br/>`, and a comma-separated list of transliterations. `props` is a table used in generating the strings, as
follows:
```{
lang = __lang_object__,
lemmas = {"``lemma``", "``lemma``", ...},
slot_list = {{"``slot``", "``accel``"}, {"``slot``", "``accel``"}, ...},
slot_table = {``slot`` = "``accel``", ``slot`` = "``accel``", ...},
include_translit = __boolean__,
create_footnote_obj = nil `or` __function__(),
canonicalize = nil or __function__(formval),
preprocess_forms = nil `or` __function__(data),
no_deduplicate_forms = __boolean__,
combine_metadata_during_dedup = nil `or` __function__(data),
transform_accel_obj = nil `or` __function__(slot, form, accel_obj),
format_forms = nil `or` __function__(data),
generate_link = nil `or` __function__(data),
format_tr = nil `or` __function__(data),
join_spans = nil `or` __function__(data),
footnotes = nil or {"``extra_footnote``", "``extra_footnote``", ...},
}```
`lemmas` is the list of lemmas, used in the accelerators.
`slot_list` is a list of two-element lists of slots and associated accelerator tag sets. ``slot`` should correspond
to slots generated during `inflect_multiword_or_alternant_multiword_spec()`. ``accel`` is the corresponding accelerator
tag set; e.g. if ``slot`` is "pres_1sg", ``accel`` might be "1|s|pres|ind". ``accel`` is used in generating entries for
accelerator support (see [[WT:ACCEL]]).
`slot_table` is a table mapping slots to associated accelerator tag sets and serves the same function as `slot_list`.
Only one of `slot_list` or `slot_table` must be given. For new code it is preferable to use `slot_list` because this
allows you to control the order of processing slots, which may occasionally be important.
`include_translit`, if given, causes transliteration to be included in the generated strings.
The function works as follows:
# Create an object to hold footnotes (customizable using `create_footnote_obj`).
# Generate the comma-separated lemma form values and store in `.lemma` in the form table.
# Loop over the slots specified using `slot_list` or `slot_table`. For each slot:
## Canonicalize the form values (customizable using `canonicalize`; by default does nothing).
## Preprocess the forms (customizable using `preprocess_forms`; by default does nothing).
## Unless `no_deduplicate_forms` is set, deduplicate forms in a slot sharing the same form value but possibly different
transliteration. (This happens e.g. in Russian, where it is relatively common for a given form to have two possible
transliterations, one reflecting a more nativized pronunciation where Cyrillic е triggers palatalization of the
preceding consonant, and one reflecting a more "foreign" pronunciation where this palatalization does not happen. In
such a case, the automatic transliteration would normally suffice for the more nativized pronunciation but the more
"foreign" pronunciation will need manual transliteration.) As part of deduplication, footnotes will be combined using
`combine_footnotes`; distinct manual transliterations will be combined into a list (meaning the `translit` field of
form objects in some subsequent `props` functions may hold a list; this will be noted when possible); and any
remaining metadata will be combined using the `combine_metadata_during_dedup` method, if provided.
## Add acceleration to all forms. The acceleration tag set associated with a given form comes from `slot_list` or
`slot_table`, i.e. all forms in a given slot have the same tag set. However, different forms will have different
associated transliterations stored into the accelerator object associated with the form, as well as possibly
different lemmas. In particular, when there are multiple lemma forms, this is often due to alternative ways to
pronounce the lemma (e.g. alternative stress positions or vowel lengths), and there are often associated non-lemma
forms that match each lemma. An example given in the introduction is {{m+|uk|русин||Rusyn}}, stressed in the lemma
as ''ру́син'' or ''руси́н'' with associated genitive singulars ''ру́сина'' and ''русина́''. We would like the
auto-generated accelerator entry for {{m|uk|русина}} to show the variant ''ру́сина'' as having lemma ''ру́син'' and
the variant ''русина́'' as having the lemma ''руси́н'', rather than showing both variants as having both lemmas,
which is less accurate. As a result, the code that generates acceleration objects for forms matches up forms and
lemmas one-to-one if possible. If this is not possible, the matching is usually one lemma to many forms, as in
{{m+|uk|міст||bridge}} with genitive singular ''мо́сту'' or ''моста́'' (in which case all forms get the same lemma), or
many lemmas to one form, as in {{m+|uk|черга||turn, queue}} stressed either ''че́рга'' or ''черга́'' with nominative
singular only ''че́рги'' (in which case the single form gets assicated all lemmas). If there are multiple lemmas and
multiple forms, the algorithm attempts to align them as evenly as possible (e.g. two lemma variants to four forms
means the first two forms get assigned the first lemma variant and the last two forms get assigned the second lemma
variant); this is often going to be incorrect, but (a) there's unlikely to be a single algorithm that works in all
such circumstances, and (b) these cases are very rare. Finally, note the following:
##* No acceleration is assigned to a form if any of the following apply: (a) there are no lemmas given in
`props.lemmas`; (b) the `no_accel` key in the form object has a non-falsy value; (c) the form value of the form is
{"?"} or an em-dash ({"—"}); (d) the accelerator tag set is given as a hyphen {"-"}); or (e) the form value contains
an internal link.
##* The accelerator code sets the `formval_for_link` key in each form object to the version of the form value that
should be passed to `full_link()` in [[Module:links]]. This is usually the same as the passed-in form value, but
differs when the entire form value is surrounded with a redundant internal link (which is removed).
##* The resulting accelerator object can be modified (or replaced entirely) by the `transform_accel_obj` function. This
is used, for example, in [[Module:es-verb]], [[Module:pt-verb]] and other Romance-language verb conjugation modules
(likewise [[Module:ar-verb]]) to replace the tag set with the original verb spec used to generate the verb, so that
the accelerator code can generate the appropriate call to {{tl|es-verb form of}}, {{tl|pt-verb form of}} or the
like, which computes the inflections, instead of directly listing the inflections.
## Format the forms into strings. The entire default process can be replaced using `format_forms`; otherwise the default
algorithm works as follows:
### Generate the '''form value spans''', with one entry (a linked HTML-ized version of the form value) per form. This
can be customized using `generate_link`. (Various modules do this. For example, the Arabic verb module includes
qualifiers, labels, ID's and the like that can be specified by the user; the Portuguese and reintegrated Galician
verb modules italicize certain superseded or otherwise less-desirable forms instead of linking them normally; the
German verb module adds {{m|de|dass}} to subjunctive forms and optional pronouns to imperative forms; and the German
adjective module adds articles to adjective forms normally accompanied by articles and the equivalent of "he/she is"
etc. to predicate forms.) The default uses `full_link()` in [[Module:links]] (with transliteration generation
disabled) concatenated with the appropriate footnote symbol(s) (if any).
### Generate the '''transliteration spans''', with one entry per distinct translit, auto-generated if manual translit
isn't available. Note that, due to the earlier form value deduplication step, there may be multiple translits per
form object. These translits are themselves deduplicated to get the list of spans. (Such duplication can happen, for
example, in Arabic with terms containing a glottal stop in them; there may be multiple ways of spelling the glottal
stop or ''hamza'' in Arabic, but only one way of transliterating it.) Each span consists of an object specifying
the translit and the list of footnotes. These objects are then converted to formatted strings, either using
`format_tr` if supplied or else calling `tag_translit()` in [[Module:script utilities]] and concatenating the
appropriate footnote symbol(s) (if any).
### Combine the form value and transliteration spans. If `join_spans` is supplied, use it; otherwise, concatenate the
form value spans (comma-separated) and (if available) transliteration spans (comma-separated), and (if appropriate)
combine them using {<br />}.
`create_footnote_obj` is an optional function of no arguments to create the footnote object used to track footnotes;
see `create_footnote_obj()`. Customizing it is useful to prepopulate the footnote table using `get_footnote_text()`.
`canonicalize` is an optional function of one argument (a form value) to canonicalize each form before processing; it
can return nil for no change. The most common purpose of this function is to remove variant codes from the form value.
See the documentation for `inflect_multiword_or_alternant_multiword_spec()` for a description of variant codes and their
purpose.
`preprocess_forms` is an optional function of one argument (a table of properties) to preprocess the form objects as
a whole. It runs after `canonicalize` (meaning that the form values passed in are canonicalized) and before
deduplication and the addition of acceleration info. The property table passed in has the following properties:
* `slot`: The slot being processed.
* `forms`: The list of form objects for this slot.
* `accel_tag_set`: The accelerator tag set for this slot, taken from `slot_list` or `slot_table`.
* `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default
`create_footnote_obj()` function.
`preprocess_forms` should return a list of preprocessed form objects, or {nil} to use the passed-in `forms`. If this
function does deduplication, you should set `no_deduplicate_forms` to disable the default deduplication process.
`no_deduplicate_forms`, if set, disables the deduplication step (see above).
`combine_metadata_during_dedup` is an optional function of one argument (a table of properties) to combine the metadata
of deduplicated form objects. The property table passed in has the following properties:
* `slot`: The slot being processed.
* `existing_form`: The existing form object into which a duplicated form is being combined.
* `dup_form`: The duplicated form being combined into `existing_form`.
* `existing_form_pos`: The one-based position of the existing form in the deduplicated form list (not necessarily its
original position).
* `dup_form_pos`: The one-based position of the duplicated form in its original list.
The following should be noted about the form objects passed in:
# The form values in `.form` have been canonicalized using `.canonicalize`, if provided.
# The form values in `existing_form` and `dup_form` are always the same.
# The footnotes in `existing_form` have already been combined with those in `dup_form`.
# If there was manual translit either in `existing_form` (prior to deduplication) or in `dup_form`, there will be
manual translit in `existing_form.translit` that is a list and combines any previous accumulated translits in
`existing_form` as well as the translit in `dup_form` (even if one of them was specified as {nil} indicating an
automatic translit). This means that the translit in `existing_form.translit` is always either {nil} or a list of
strings (and the same applies to `dup_form.translit`).
`transform_accel_obj` is an optional function of three arguments (``slot``, ``formobj``, ``accel_obj``) to transform the
default constructed accelerator object in ``accel_obj`` into an object that should be passed to `full_link()` in
[[Module:links]]. It should return the new accelerator object, or {nil} for no acceleration. (If {nil} is returned,
the corresponding form has no acceleration; this is unlike most customization functions, where returning {nil} causes
the default algorithm to be invoked.) The function can destructively modify the accelerator object passed in.
'''NOTE''': This is called even when the passed-in ``accel_obj`` is {nil} (see the (a) through (e) reasons above why no
acceleration may be assigned to a form). Thus, your code needs to do something sensible in this case. The description
above of how `show_forms()` works inclues various examples of modules that supply a `transform_accel_obj` function and
the reasons for doing so.
`format_forms`, if supplied, is a function that entirely replaces the formatting portion of `show_forms()`. An example
of why you might want to do this is to get a different layout than the default, e.g. one where translit is displayed
next to each form value instead of the form values and translits grouped and displayed on separate lines. Under normal
circumstances, you should not do this, but instead customize the functions that replace specific parts of the default
formatting algorithm (see below). This function is passed one argument (a table of properties) and should return a
string (the formatted forms, ready to store into the slot in the form table) or {nil} to proceed with the default
algorithm (see above). The property table passed in has the following properties:
* `slot`: The slot being processed.
* `forms`: The list of form objects, deduplicated and with accelerator info added.
* `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default
`create_footnote_obj()` function.
The following should be noted about the form objects in `forms`:
# There are extra fields `formval_for_link` and `accel_obj`. The first one is as described above under the paragraph
beginning "Add acceleration to all forms" under "The function works as follows". The second one is the accelerator
object in the format expected by [[Module:links]].
# The `translit` field, if non-{nil}, is a list of transliterations rather than a single transliteration; this is due to
the form value deduplication step.
`generate_link` is an optional function to generate the link text for a given form value. It is passed a single argument
(a table of properties) and should return a string, the formatted link. If it returns {nil}, the default algorithm (see
above) is invoked. The property table passed in has the following properties:
* `slot`: The slot being processed.
* `form`: The form to be converted to a formatted link. As with the `format_forms` function described above, the form
objects passed in contain extra fields `formval_for_link` and `accel_obj` (both of which will normally be used), and
the `translit` field, if non-{nil}, is a list.
* `pos`: The one-based position of the form being processed, in the list of form value spans. Rarely used.
* `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default
`create_footnote_obj()` function. Normally used in order to get the footnote symbol associated with any footnotes in
`footnotes`.
The description above of how `show_forms()` works inclues various examples of modules that supply a `generate_link`
function and the reasons for doing so.
`format_tr` is an optional function to generate the formatted text for a given transliteration. It is passed a single
argument (a table of properties) and should return a string, the formatted transliteration text. If it returns {nil},
the default algorithm (see above) is invoked. The property table passed in has the following properties:
* `slot`: The slot being processed.
* `tr_for_tag`: The transliteration to process.
* `pos`: The one-based position of the transliteration being processed, in the list of transliteration spans. Rarely
used.
* `footnotes`: The list of footnotes associated with all form objects with this transliteration. (If there were multiple
form objects with the same transliteration, the list of footnotes will have been generated using
`combine_footnotes()`.)
* `footnote_obj`: The footnote object returned by the `create_footnote_obj` property or the default
`create_footnote_obj()` function. Normally used in order to get the footnote symbol associated with any footnotes in
`footnotes`.
`join_spans` is an optional function to join the processed form value and transliteration spans into a formatted string.
It is passed a single argument (a table of properties) and should return the final string to store into the form table
slot. If it returns {nil}, the default algorithm (see above) is invoked. The property table passed in has the following
properties:
* `slot`: The slot being processed.
* `formval_spans`: A list of strings, the formatted form value spans.
* `tr_spans`: A list of strings, the formatted transliteration spans. If there is no transliteration, this will be an
empty list.
A custom `join_spans` is provided by [[Module:de-verb]], which concatenates the form value spans vertically (using
{"<br />"}) instead of horizontally using a comma, as is normal; this is because there is no translit and the form
values are often long, containing extra words attached during `generate_link()`. The only exception is the `aux` slot
holding the auxiliaries, which is concatenated horizontally using {" or "}. [[Module:de-adjective]] similarly provides
a custom `join_spans` function that concatenates the form value spans vertically.
]==]
function export.show_forms(formtable, props)
local footnote_obj = props.create_footnote_obj and props.create_footnote_obj() or export.create_footnote_obj()
local function fetch_formval_and_translit(entry, remove_links)
local formval, translit
if type(entry) == "table" then
formval, translit = entry.form, entry.translit
else
formval = entry
end
if remove_links then
formval = m_links.remove_links(formval)
end
return formval, translit
end
local lemma_formvals = {}
for _, lemma in ipairs(props.lemmas) do
local lemma_formval, _ = fetch_formval_and_translit(lemma)
insert_if_not(lemma_formvals, lemma_formval)
end
formtable.lemma = #lemma_formvals > 0 and concat(lemma_formvals, ", ") or
mw.loadData(headword_data_module).pagename
-- For safety, since we in-place modify `lemmas` usually before processing a given slot, make a copy.
local props_lemmas = shallow_copy(props.lemmas)
for i, lemma in ipairs(props_lemmas) do
props_lemmas[i] = shallow_copy(lemma)
end
local function do_slot(slot, accel_tag_set)
local formobjs = formtable[slot]
if formobjs then
if type(formobjs) ~= "table" then
error("Internal error: For slot '" .. slot .. "', expected table but saw " .. dump(formobjs))
end
-- Maybe canonicalize the form values (e.g. remove variant codes and monosyllabic accents).
if props.canonicalize then
for _, form in ipairs(formobjs) do
form.form = props.canonicalize(form.form) or form.form
end
end
-- Preprocess the forms as a whole if called for.
if props.preprocess_forms then
formobjs = props.preprocess_forms {
slot = slot,
forms = formobjs,
accel_tag_set = accel_tag_set,
footnote_obj = footnote_obj,
} or formobjs
end
-- Maybe deduplicate form values (happens e.g. in Russian with two terms with the same Russian form but
-- different translits).
if not props.no_deduplicate_forms then
local deduped_formobjs = {}
for i, form in ipairs(formobjs) do
local function combine_forms(existing_form, dup_form, pos)
assert(existing_form.form == dup_form.form)
-- Combine footnotes.
existing_form.footnotes = export.combine_footnotes(existing_form.footnotes, dup_form.footnotes)
-- If translit is being generated, and there's manual translit associated with either form, we
-- need to generate any missing translits and combine them, taking into account the fact that a
-- translit value may actually be a list of translits (particularly with the existing form if we
-- already combined an item with manual translit into it).
if props.include_translit and form_value_transliterable(existing_form.form) and (
existing_form.translit or dup_form.translit) then
local combined_translit
if not existing_form.translit then
combined_translit = {
props_transliterate(props, m_links.remove_links(existing_form.form))
}
elseif type(existing_form.translit) == "string" then
combined_translit = {existing_form.translit}
else
combined_translit = existing_form.translit
end
local dup_form_translit = dup_form.translit
if not dup_form_translit then
-- dup_form.form is the same as existing_form.form (see assert above), but this is
-- defensive programming in case that changes
dup_form_translit = {props_transliterate(props, m_links.remove_links(dup_form.form))}
elseif type(dup_form_translit) == "string" then
dup_form_translit = {dup_form_translit}
end
for _, translit in ipairs(dup_form_translit) do
insert_if_not(combined_translit, translit)
end
existing_form.translit = combined_translit
end
if props.combine_metadata_during_dedup then
props.combine_metadata_during_dedup {
slot = slot,
existing_form = existing_form,
existing_form_pos = pos,
dup_form = dup_form,
dup_form_pos = i,
}
end
end
insert_if_not(deduped_formobjs, form, {
key = function(formobj) return formobj.form end,
combine = combine_forms,
})
end
formobjs = deduped_formobjs
end
-- Add acceleration info to form objects.
for i, form in ipairs(formobjs) do
local formval = form.form
if not form_value_transliterable(formval) then
form.formval_for_link = formval
else
local formval_for_link
formval_for_link = formval
-- remove redundant link surrounding entire form
formval_for_link = export.remove_redundant_links(formval_for_link)
form.formval_for_link = formval_for_link
-------------------- Compute the accelerator object. -----------------
local accel_obj
-- Check if form still has links; if so, don't add accelerators because the resulting entries will
-- be wrong.
if props_lemmas[1] and not form.no_accel and accel_tag_set ~= "-" and
not formval_for_link:find("%[%[") then
-- If there is more than one form or more than one lemma, things get tricky. Often, there are
-- the same number of forms as lemmas, e.g. for Ukrainian [[зимовий]] "wintry; winter (rel.)",
-- which can be stressed зимо́вий or зимови́й with corresponding masculine/neuter genitive
-- singulars зимо́вого or зимово́го etc. In this case, usually the forms and lemmas match up so
-- we do this. If there are different numbers of forms than lemmas, it's usually one lemma
-- against several forms e.g. Ukrainian [[міст]] "bridge" with genitive singular мо́сту or моста́
-- (accent patterns b or c) or [[ложка|ло́жка]] "spoon" with nominative plural ло́жки or ложки́
-- (accent patterns a or c). Here, we should assign the same lemma to both forms. The opposite
-- can happen, e.g. [[черга]] "turn, queue" stressed че́рга or черга́ with nominative plural only
-- че́рги (accent patterns a or d). Here we should assign both lemmas to the same form. In more
-- complicated cases, with more than one lemma and form and different numbers of each, we try
-- to align them as much as possible, e.g. if there are somehow eight forms and three lemmas,
-- we assign lemma 1 to forms 1-3, lemma 2 to forms 4-6 and lemma 3 to forms 7 and 8, and
-- conversely if there are somehow three forms and eight lemmas. This is likely to be wrong, but
-- (a) there's unlikely to be a single algorithm that works in all such circumstances, and (b)
-- these cases are vanishingly rare or nonexistent. Properly we should try to remember which
-- form was generated by which lemma, but that is significant extra work for little gain.
local first_lemma, last_lemma
if #formobjs >= #props_lemmas then
-- More forms than lemmas. Try to even out the forms assigned per lemma.
local forms_per_lemma = math.ceil(#formobjs / #props_lemmas)
first_lemma = math.floor((i - 1) / forms_per_lemma) + 1
last_lemma = first_lemma
else
-- More lemmas than forms. Try to even out the lemmas assigned per form.
local lemmas_per_form = math.ceil(#props_lemmas / #formobjs)
first_lemma = (i - 1) * lemmas_per_form + 1
last_lemma = math.min(first_lemma + lemmas_per_form - 1, #props_lemmas)
end
local accel_lemma, accel_lemma_translit
if first_lemma == last_lemma then
accel_lemma, accel_lemma_translit =
fetch_formval_and_translit(props_lemmas[first_lemma], "remove links")
else
accel_lemma = {}
accel_lemma_translit = {}
for j=first_lemma, last_lemma do
local this_accel_lemma, this_accel_lemma_translit =
fetch_formval_and_translit(props_lemmas[j], "remove links")
-- Do not use insert() especially for the translit because it may be nil and in
-- that case we want gaps in the array.
accel_lemma[j - first_lemma + 1] = this_accel_lemma
accel_lemma_translit[j - first_lemma + 1] = this_accel_lemma_translit
end
end
local accel_translit
if props.include_translit and form.translit then
if type(form.translit) == "table" then
accel_translit = concat(form.translit, ", ")
elseif type(form.translit) == "string" then
accel_translit = form.translit
else
error(("Internal error: For slot '%s', form translit is not a table or string: %s"):
format(slot, dump(form.translit)))
end
end
accel_obj = {
form = accel_tag_set,
translit = accel_translit,
lemma = accel_lemma,
lemma_translit = props.include_translit and accel_lemma_translit or nil,
}
end
-- Postprocess if requested.
if props.transform_accel_obj then
accel_obj = props.transform_accel_obj(slot, form, accel_obj)
end
form.accel_obj = accel_obj
end
end
-- Format the form objects into a string for insertion into the table.
local formatted_forms
if props.format_forms then
formatted_forms = props.format_forms {
slot = slot,
forms = formobjs,
footnote_obj = footnote_obj,
}
end
if not formatted_forms then
-- Default algorithm: Separate form values and translits and concatenate on separate lines.
-- Form values have already been deduplicated but we may need to deduplicate translits (this happens
-- e.g. in Arabic where there may be multiple ways of spelling a hamza in the Arabic script but only
-- one way in transliteration).
local formval_spans = {}
local tr_spans = {}
for i, form in ipairs(formobjs) do
local link
if props.generate_link then
link = props.generate_link {
slot = slot,
pos = i,
form = form,
footnote_obj = footnote_obj,
}
end
if not link then
link = m_links.full_link {
lang = props.lang, term = form.formval_for_link, tr = "-", accel = form.accel_obj
} .. export.get_footnote_text(form.footnotes, footnote_obj)
end
formval_spans[i] = link
if props.include_translit then
local translits = form.translit or props_transliterate(props, m_links.remove_links(form.form))
if type(translits) == "string" then
translits = {translits}
end
for _, tr in ipairs(translits) do
local tr_for_tag = tr -- formerly may have differed due to old-style footnote symbols
insert_if_not(tr_spans, {
tr_for_tag = tr_for_tag,
footnotes = form.footnotes,
}, {
key = function(trobj) return trobj.tr_for_tag end,
combine = function(trobj, newtrobj)
-- Combine footnotes.
trobj.footnotes = export.combine_footnotes(trobj.footnotes, newtrobj.footnotes)
end,
})
end
end
end
for i, tr_span in ipairs(tr_spans) do
local formatted_tr
if props.format_tr then
formatted_tr = props.format_tr {
slot = slot,
pos = i,
tr_for_tag = tr_span.tr_for_tag,
footnotes = tr_span.footnotes,
footnote_obj = footnote_obj,
}
end
if not formatted_tr then
formatted_tr = require(script_utilities_module).tag_translit(tr_span.tr_for_tag, props.lang,
"default", " style=\"color: var(--wikt-palette-grey-8,#888);\"") ..
export.get_footnote_text(tr_span.footnotes, footnote_obj)
end
tr_spans[i] = formatted_tr
end
if props.join_spans then
formatted_forms = props.join_spans {
slot = slot,
formval_spans = formval_spans,
tr_spans = tr_spans,
}
end
if not formatted_forms then
local formval_span = concat(formval_spans, ", ")
local tr_span
if #tr_spans > 0 then
tr_span = concat(tr_spans, ", ")
end
if tr_span then
formatted_forms = formval_span .. "<br />" .. tr_span
else
formatted_forms = formval_span
end
end
end
formtable[slot] = formatted_forms
else
formtable[slot] = "—"
end
end
iterate_slot_list_or_table(props, do_slot)
formtable.footnote = concat(footnote_obj.notes, "<br />")
end
--[==[
Find the inflection spec for a given lemma by scraping the contents of the section for the lemma's language, looking
for the appropriate inflection template (which may in some cases be a headword template with inflections specified in
the headword, as for {{tl|en-verb}}). An inflection ID can be given; in that case, the value of the appropriate ID param
(usually `id=`) must match the specified ID; otherwise, any inflection template call of the right name will work. If
anything goes wrong in the process, a string is returned describing the error message; otherwise an object is returned,
where `template` is the template found (an object of the form returned by `find_templates` in
[[Module:template parser]]).
There is one param, an object with the following fields:
* `lemma`: The page to scrape for inflection calls.
* `langname`: The name of the language of the lemma (e.g. {"English"} or {"Icelandic"}). Only that section is scraped.
* `infltemp`: A string specifying the name of the template containing the inflections, or a function of one argument,
the template object (an object of the form returned by `find_templates` in [[Module:template parser]]), which should
return true if the given object specifies an appropriate inflection template.
* `idparam`: The name of the parameter holding the inflection ID used to disambiguate lemmas with multiple inflections.
* `inflid`: The inflection ID that must match the value of the `idparam` parameter specified by the inflection template.
If not specified, there must be exactly one matching inflection template present, and it must not have a value given
for the `idparam` parameter. Otherwise, all matching inflection templates must have a value specified for the
`idparam` parameter of the template, and there must be exactly one template whose `idparam` parameter value is the
same as the value of `inflid`.
Find the inflection spec by scraping the contents of the Icelandic section of `lemma`, looking for `infltemp` calls
(where template is e.g. "is-ndecl", "is-adecl" or "is-conj") If `inflid` is given, it must match the value of the
|id= param specified to the inflection template; otherwise, any inflection template call will work. If anything goes
wrong in the process, a string is returned describing the error message; otherwise a table of inflections is
returned, each containing a field `infl` with the inflection spec. The inflection spec comes from the |deriv=,
|deriv2=, etc. params in the inflection template if specified and `is_deriv` is given; otherwise from 1=, 2=, etc.
If `allow_empty_infl` is given, a missing inflection spec in 1= is allowed and converted to an empty string;
otherwise, an error is signaled.
]==]
function export.scrape_inflection(data)
local langname, lemma, infltemp, inflid, idparam =
data.langname, data.lemma, data.infltemp, data.inflid, data.idparam
local lang_lemma = ("%s base lemma '[[%s]]'"):format(langname, lemma)
local title = new_title(lemma)
if title then
local content = title:getContent()
if content then
local langtext = require(pages_module).get_section(content, langname)
if langtext then
local inflobjs_by_id = {}
local inflobjs_without_id = {}
local ordered_seen_ids = {}
for template in require(template_parser_module).find_templates(langtext) do
local matches
if type(infltemp) == "string" then
matches = template:get_name() == infltemp
else
matches = infltemp(template)
end
if matches then
local inflobj = {template = template}
local args = template:get_arguments()
local tid = args[idparam]
if tid then
if inflobjs_by_id[tid] then
return ("For %s base lemma '[[%s]]', saw id='%s' twice"):format(
langname, lemma, tid
)
end
inflobjs_by_id[tid] = inflobj
insert_if_not(ordered_seen_ids, tid)
else
insert(inflobjs_without_id, inflobj)
end
end
end
local function concat_ordered_seen_ids()
local quoted_seen_ids = {}
for _, seen_id in ipairs(ordered_seen_ids) do
insert(quoted_seen_ids, "'" .. seen_id .. "'")
end
return m_table.serialCommaJoin(quoted_seen_ids, {dontTag = true})
end
local formatted_template = type(infltemp) == "string" and ("[[Template:%s]]"):format(infltemp) or
"inflection template"
local formatted_template_sing_article = type(infltemp) == "string" and
("[[Template:%s]]"):format(infltemp) or "an inflection template"
local formatted_template_plural = type(infltemp) == "string" and "'s" or "s"
if ordered_seen_ids[1] and inflobjs_without_id[1] then
return ("For %s, saw %s %s%s with id=%s as well as %s %s%s without ID; this is not allowed; " ..
"with multiple %s calls, all must have id= params"):format(lang_lemma, #ordered_seen_ids,
formatted_template, ordered_seen_ids[2] and formatted_template_plural or "",
concat_ordered_seen_ids(), #inflobjs_without_id, formatted_template,
inflobjs_without_id[2] and formatted_template_plural or "", formatted_template)
elseif not ordered_seen_ids[1] and not inflobjs_without_id[1] then
return ("For %s, found %s section but couldn't find any calls to %s"):format(
lang_lemma, langname, formatted_template_sing_article)
elseif #inflobjs_without_id > 1 then
return ("For %s, found %s %s%s without ID's; this is not allowed; with multiple %s calls, " ..
"all must have id= params"):format(lang_lemma, #inflobjs_without_id, formatted_template,
formatted_template_plural, formatted_template)
elseif inflid then
if inflobjs_by_id[inflid] then
return inflobjs_by_id[inflid]
elseif ordered_seen_ids[1] then
return ("For %s, found %s section but couldn't find any inflections matching ID '%s'; " ..
"instead found ID's %s; you may have misspelled the ID"):format(lang_lemma, langname,
inflid, concat_ordered_seen_ids())
else
return ("For %s, found %s section with a single %s without ID, but ID requirement '%s' " ..
"specified; consider removing the ID requirement"):format(lang_lemma, langname,
formatted_template, inflid)
end
elseif inflobjs_without_id[1] then
-- only one infl template, and it doesn't have an id=; return it
return inflobjs_without_id[1]
elseif ordered_seen_ids[2] then
return ("For %s, saw %s %s%s with id=%s but with a request to return the inflection without " ..
"ID; consider adding an ID restriction to the scrape request, e.g. '@@:%s' for " ..
"self-scraping or '@%s:%s' for scraping from another page"):format(lang_lemma,
#ordered_seen_ids, formatted_template, formatted_template_plural, concat_ordered_seen_ids(),
ordered_seen_ids[1], usub(lemma, 1, 1), ordered_seen_ids[1])
else
return ("For %s, found %s section with a single %s with id=%s, but the scrape request didn't " ..
"specify an ID; consider removing the id= param from the %s call unless you expect to add " ..
"more such calls to the page in the future (in which case add the ID to the scrape request, " ..
"e.g. '@@:%s' for self-scraping or '@%s:%s' for scraping from another page"):format(lang_lemma,
langname, formatted_template, concat_ordered_seen_ids(), formatted_template,
ordered_seen_ids[1], usub(lemma, 1, 1), ordered_seen_ids[1])
end
else
return ("For %s, page exists but has no %s section"):format(lang_lemma, langname)
end
else
return ("For %s, couldn't fetch contents for page; page may not exist"):format(lang_lemma)
end
else
return ("Bad %s; couldn't create title object"):format(lang_lemma)
end
end
return export
nv8di3n6mnnnmvmo7fjstb2viyp4j22
Module:yesno
828
8098
54973
2026-09-30T17:40:07Z
Deadend0914
7211
Gesceop tramet þe hafaþ '-- Function allowing for consistent treatment of boolean-like wikitext input. -- It works similarly to the template {{yesno}}. local lower = string.lower local tonumber = tonumber local type = type local yesno return function (val, default) if val == nil then return nil elseif not yesno then yesno = { [true] = true, [false] = false, ["true"] = true, ["false"] = false, ["t"] = true, ["f"] = false, [1] = true, [0] = false, ["1"] = true, [...'
54973
Scribunto
text/plain
-- Function allowing for consistent treatment of boolean-like wikitext input.
-- It works similarly to the template {{yesno}}.
local lower = string.lower
local tonumber = tonumber
local type = type
local yesno
return function (val, default)
if val == nil then
return nil
elseif not yesno then
yesno = {
[true] = true, [false] = false,
["true"] = true, ["false"] = false,
["t"] = true, ["f"] = false,
[1] = true, [0] = false,
["1"] = true, ["0"] = false,
["yes"] = true, ["no"] = false,
["y"] = true, ["n"] = false,
["on"] = true, ["off"] = false,
}
end
local ret = yesno[val]
if ret ~= nil then
return ret
elseif type(val) ~= "string" then
return default
end
-- Catch inputs like "00".
ret = yesno[tonumber(val)]
if ret ~= nil then
return ret
end
-- Case-insensitive.
ret = yesno[lower(val)]
if ret ~= nil then
return ret
end
return default
end
50fw3j6qj6cod4hrcb5leyd15zs34hg
Module:JSON
828
8099
54974
2026-09-30T17:40:42Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local m_math = require("Module:math") local m_str_utils = require("Module:string utilities") local m_table = require("Module:table") local codepoint = m_str_utils.codepoint local concat = table.concat local converter -- forward declaration local find = string.find local format = string.format local gsub = string.gsub local insert = table.insert local ipairs = ipairs local is_array = m_table.isArray local is_finite_real_number = m_math.is_finit...'
54974
Scribunto
text/plain
local export = {}
local m_math = require("Module:math")
local m_str_utils = require("Module:string utilities")
local m_table = require("Module:table")
local codepoint = m_str_utils.codepoint
local concat = table.concat
local converter -- forward declaration
local find = string.find
local format = string.format
local gsub = string.gsub
local insert = table.insert
local ipairs = ipairs
local is_array = m_table.isArray
local is_finite_real_number = m_math.is_finite_real_number
local is_integer = m_math.is_integer
local is_utf8 = mw.ustring.isutf8
local match = string.match
local toNFC = mw.ustring.toNFC
local tonumber = tonumber
local pairs = pairs
local sorted_pairs = m_table.sortedPairs
local trycall = require("Module:fun/trycall")
local type = type
local function json_fromBoolean(b)
return b and "true" or "false"
end
-- Given a finite real number x, returns a string containing its JSON
-- representation, with enough precision that it should round-trip correctly
-- (depending on the well-behavedness of the system on the other end).
local function json_fromNumber(x, level)
if not is_finite_real_number(x) then
error(format("Cannot encode non-finite real number %g", x), level)
end
-- Give integers within the range RFC 7159 considers interoperable.
if is_integer(x) and x < 0x1p53 and x > -0x1p53 then
return format("%d", x)
end
-- Otherwise, give a (double) float with the %g specifier, which handles any
-- leading/trailing 0s etc. Double floats have precision ranging from 15 to
-- 17 digits, meaning rounding artefacts can appear when precision is set to
-- 16 or 17 (e.g. 1.1 is converted to 1.1000000000000001). Avoid this by
-- trying each in turn, returning the first one which converts back into the
-- original number, which avoids implying that it has higher precision than
-- it really does.
for prec = 15, 17 do
local poss = format(format("%%.%dg", prec), x)
if prec == 17 or tonumber(poss) == x then
x = poss
break
end
end
-- If there's an exponent, remove any + sign and leading 0s from it.
if find(x, "e", nil, true) then
return (gsub(x, "(e%-?)%+?0*", "%1"))
end
-- If it resembles an integer, convert it to scientific notation to avoid
-- the other end interpreting it as one.
local d, f = match(x, "^(%d)(%d-)0*$")
return d and format("%s%s%se%d", d, f == "" and "" or ".", f, #x - 1) or x
end
local function escape_codepoint(cp)
if cp < 0x10000 then
return format("\\u%04X", cp)
end
cp = cp - 0x10000
return format("\\u%04X\\u%04X", 0xD800 + (cp / 1024), 0xDC00 + (cp % 1024))
end
local escapes
local function get_escapes()
escapes, get_escapes = {
[0x8] = [[\b]], [0x9] = [[\t]], [0xA] = [[\n]], [0xC] = [[\f]],
[0xD] = [[\r]], [0x22] = [[\"]], [0x2F] = [[\/]], [0x5C] = [[\\]],
}, nil
local function _add(cp)
if escapes[cp] == nil then
escapes[cp] = escape_codepoint(cp)
end
end
local function add(cp1, cp2)
if cp2 == nil then
return _add(cp1)
end
for cp = cp1, cp2 do
_add(cp)
end
end
add(0x0000, 0x001F)
add(0x007F, 0x00A0)
add(0x00AD)
add(0x034F)
add(0x0600, 0x0605)
add(0x061C)
add(0x06DD)
add(0x070F)
add(0x0890, 0x0891)
add(0x08E2)
add(0x115F, 0x1160)
add(0x1680)
add(0x17B4, 0x17B5)
add(0x180B, 0x180F)
add(0x2000, 0x200F)
add(0x2028, 0x202F)
add(0x205F, 0x206F)
add(0x3000)
add(0x3164)
add(0xFDD0, 0xFDEF)
add(0xFE00, 0xFE0F)
add(0xFEFF)
add(0xFFA0)
add(0xFFF0, 0xFFFF)
add(0x110BD)
add(0x110CD)
add(0x1107F)
add(0x13430, 0x1343F)
add(0x16FE4)
add(0x1BC9D)
add(0x1BCA0, 0x1BCA3)
add(0x1D173, 0x1D17A)
for i = 0x2, 0x11 do
i = i * 0x10000
add(i - 2, i - 1)
end
return escapes
end
local function escape_char(ch)
local cp = codepoint(ch)
return (escapes or get_escapes())[cp] or escape_codepoint(cp)
end
local function maybe_escape_char(ch)
local cp = codepoint(ch)
if cp >= 0xE0000 and cp <= 0xE0FFF then
return escape_char(ch)
end
return (escapes or get_escapes())[cp] or ch
end
-- Given a string, escapes any illegal characters and wraps it in double-quotes.
-- Raises an error if the string is not valid UTF-8.
local function json_fromString(s, ascii, level)
if not is_utf8(s) then
error(format("Cannot encode non-UTF-8 string '%s'", s), level)
end
local pattern = '[%c"/\\\128-\255][\128-\191]*'
if not ascii then
local escaped = gsub(s, pattern, maybe_escape_char)
if escaped == toNFC(escaped) then
return '"' .. escaped .. '"'
end
end
return '"' .. gsub(s, pattern, escape_char) .. '"'
end
local function json_fromTable(t, opts, current, level)
local ret, open, close = {}
if is_array(t) then
for key, value in ipairs(t) do
ret[key] = converter(value, opts, current, level + 1) or "null"
end
open, close = "[", "]"
else
-- `seen_keys` memoizes keys already seen, to prevent collisions (e.g. 1
-- and "1").
local seen_keys, colon, ascii = {}, opts.compress and ":" or " : ", opts.ascii
for key, value in (opts.sort_keys and sorted_pairs or pairs)(t) do
local key_type = type(key)
if key_type == "boolean" then
key = json_fromBoolean(key)
elseif key_type == "number" then
key = json_fromNumber(key, level + 1)
elseif key_type ~= "string" then
error(format("Cannot use type '%s' as a table key", key_type), level)
end
key = json_fromString(key, ascii, level + 1)
if seen_keys[key] then
error(format("Collision for JSON key %s", key), level)
end
seen_keys[key] = true
insert(ret, key .. colon .. (converter(value, opts, current, level + 1) or "null"))
end
open, close = "{", "}"
end
ret = open .. (
opts.compress and concat(ret, ",") .. close or
" " .. concat(ret, ", ") .. (
#ret == 0 and "" or " "
) .. close
)
current[t] = nil
return ret
end
function converter(this, opts, current, level) -- local declared above
local val_type = type(this)
if val_type == "nil" then
return "null"
elseif val_type == "boolean" then
return json_fromBoolean(this)
elseif val_type == "number" then
return json_fromNumber(this, level + 1)
elseif val_type == "string" then
return json_fromString(this, opts.ascii, level + 1)
elseif val_type ~= "table" then
error(format("Cannot encode type '%s'", val_type), level)
elseif current[this] then
error("Cannot use recursive tables", level)
end
-- Memoize the table to enable recursion checking.
current[this] = true
if opts.ignore_toJSON then
return json_fromTable(this, opts, current, level + 1)
end
-- Check if a toJSON method can be used. Use the lua_table flag to get a Lua
-- table, as any options need to be applied to the output.
local to_json = this.toJSON
if to_json == nil then
return json_fromTable(this, opts, current, level + 1)
end
-- Try to call it.
local success, new = trycall(to_json, this, {lua_table = true})
if success then
-- If successful, use the returned value.
local ret = converter(new, opts, current, level + 1)
current[this] = nil
return ret
end
-- Otherwise, treat as a conventional value.
return json_fromTable(this, opts, current, level + 1)
end
-- This function makes an effort to convert an arbitrary Lua value to a string
-- containing a JSON representation of it.
function export.toJSON(this, opts)
return converter(this, opts == nil and {} or opts, {}, 3)
end
return export
2ak4p5c82rula80h5a4l22jokudmyrq
Module:references
828
8100
54975
2026-09-30T17:41:12Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local rsplit = mw.text.split --[==[ Parse a references spec as used in the {{para|ref}} param to {{tl|IPA}}, {{tl|IPAchar}}, {{tl|homophones}}, {{tl|rhymes}}, etc. and soon the {{para|f<var>N</var>ref}} param to {{tl|head}}. `parse_err` is a function of one argument to throw an error with the specified argument as the error message; defaults to `error`. Multiple references are separated by `!!!` (optionally with spaces around it), and the equ...'
54975
Scribunto
text/plain
local export = {}
local rsplit = mw.text.split
--[==[
Parse a references spec as used in the {{para|ref}} param to {{tl|IPA}}, {{tl|IPAchar}}, {{tl|homophones}},
{{tl|rhymes}}, etc. and soon the {{para|f<var>N</var>ref}} param to {{tl|head}}. `parse_err` is a function of one
argument to throw an error with the specified argument as the error message; defaults to `error`.
Multiple references are separated by `!!!` (optionally with spaces around it), and the equivalent of
`<nowiki><ref name="bendo">{{R:it:DiPI|bendo}}</ref><ref>{{R:it:Olivetti}}</ref></nowiki>` can be specified using a
syntax like the following:
{ {{IPA|it|ˈben.do|ˈbɛn.do|ref2={{R:it:DiPI|bendo}}<<name:bendo>> !!! {{R:it:Olivetti}}}}}
To include a group as in `<nowiki><ref name="bendo" group="pron">...</ref></nowiki>` use:
{ {{IPA|it|ˈben.do|ˈbɛn.do|ref2={{R:it:DiPI|bendo}}<<name:bendo>><<group:pron>>}}}
To reference a prior name, as in `<nowiki><ref name="bendo"/></nowiki>`, leave the reference text blank:
{ {{IPA|it|ˈben.do|ˈbɛn.do|ref2=<<name:bendo>>}}}
Similarly, to reference a prior name in a particular group, as in `<nowiki><ref name="bendo" group="pron"/></nowiki>`, use:
{ {{IPA|it|ˈben.do|ˈbɛn.do|ref2=<<name:bendo>><<group:pron>>}}}
The return value consists of a list of objects of the form { {text = TEXT, name = NAME, group = GROUP}}.
This is the same format as is expected in the `part.refs` in [[Module:headword]] and `item.refs` in
[[Module:IPA]].
]==]
function export.parse_references(text, parse_err)
parse_err = parse_err or error
local refs = {}
local raw_notes = rsplit(text, "%s*!!!%s*")
for _, raw_note in ipairs(raw_notes) do
local note
if raw_note:find("<<") then
local splitvals = require("Module:string utilities").split(raw_note, "(<<[a-z]+:.->>)")
note = {text = splitvals[1]}
for i = 2, #splitvals, 2 do
local key, value = splitvals[i]:match("^<<([a-z]+):(.*)>>$")
if not key then
parse_err("Internal error: Can't parse " .. splitvals[i])
end
if key == "name" or key == "group" then
note[key] = value
else
parse_err("Unrecognized key '" .. key .. "' in " .. splitvals[i])
end
if splitvals[i + 1] ~= "" then
parse_err("Extraneous text '" .. splitvals[i + 1] .. "' after " .. splitvals[i])
end
end
else
note = raw_note
end
table.insert(refs, note)
end
return refs
end
--[==[
Format a list of reference specs, using a parser function. The return string contains a footnote number that hyperlinks
to the actual reference, located in the `<nowiki><references /></nowiki>` section. The format an individual reference
spec is either a string containing the reference text (typically a call to a citation template such as {{tl|cite-book}},
or a template wrapping such a call), or an object with fields `text` (the reference text), `name` (the name of the
reference, as in `<nowiki><ref name="foo">...</ref></nowiki>` or `<nowiki><ref name="foo" /></nowiki>`) and/or `group`
(the group of the reference, as in `<nowiki><ref name="foo" group="bar">...</ref></nowiki>` or
`<nowiki><ref name="foo" group="bar"/></nowiki>`).
]==]
function export.format_references(refspecs)
local refs = {}
for _, refspec in ipairs(refspecs) do
if type(refspec) ~= "table" then
refspec = {text = refspec}
end
local refargs
if refspec.name or refspec.group then
refargs = {name = refspec.name, group = refspec.group}
end
table.insert(refs, mw.getCurrentFrame():extensionTag("ref", refspec.text, refargs))
end
return table.concat(refs)
end
return export
49olnq52uhabuj9xvknmmj73r6ycrd7
Module:gender and number utilities
828
8101
54976
2026-09-30T17:41:50Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local parameter_utilities_module = "Module:parameter utilities" local parse_interface_module = "Module:parse interface" --[==[ Parse a gender and number spec, possibly with multiple comma-separated gender/number specs and potentially with inline modifiers. Used by [[Module:parameters]]. `data` is an object with the following fields: * `spec`: The unparsed gender/number spec. * `paramname`: The name of the parameter where the gender/number spec...'
54976
Scribunto
text/plain
local export = {}
local parameter_utilities_module = "Module:parameter utilities"
local parse_interface_module = "Module:parse interface"
--[==[
Parse a gender and number spec, possibly with multiple comma-separated gender/number specs and potentially with
inline modifiers. Used by [[Module:parameters]]. `data` is an object with the following fields:
* `spec`: The unparsed gender/number spec.
* `paramname`: The name of the parameter where the gender/number spec was retrieved from, for error messages. Optional.
* `parse_err`: Optional function to display a parse error message, in the format required for
`parse_inline_modifiers` in [[Module:parse utilities]].
* `allow_multiple`: If true, allow multiple comma-separated gender/number specs. If given, the return value will be a
list of parsed gender/number specs instead of a single such object.
* `allow_space_around_comma`: If true, spaces can be placed around the comma separating multiple gender/number specs.
It's not generally recommended to set this.
The return value is a gender/number spec of the sort accepted by [[Module:gender and number]]; or, if `allow_multiple`
is given, a list of such specs. In each spec, the field `spec` contains the gender/number spec itself (e.g. `m-in-p`)
and other fields contain parsed inline modifiers. The following modifiers are supported:
* `q`: Left qualifier.
* `qq`: Right qualifier.
* `l`: Left label(s).
* `ll`: Right label(s).
* `ref`: Reference(s).
]==]
function export.parse_gender_and_number_spec(data)
local spec = data.spec
if not spec:find("[,<]") then
return data.allow_multiple and {{spec = spec}} or {spec = spec}
end
local param_mods = require(parameter_utilities_module).construct_param_mods {
{group = {"q", "l", "ref"}},
}
local function generate_obj(term, parse_err)
return {
spec = term
}
end
return require(parse_interface_module).parse_inline_modifiers(spec, {
paramname = data.paramname,
param_mods = param_mods,
generate_obj = generate_obj,
parse_err = data.parse_err,
escape_fun = false,
unescape_fun = false,
splitchar = data.allow_multiple and (data.allow_space_around_comma and "%s*,%s*" or ",") or nil,
})
end
return export
2ikdohznxczh7f3kgp9efot9mt9h6p8
Module:wikimedia languages
828
8102
54977
2026-09-30T17:42:19Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local languages_module = "Module:languages" local language_like_module = "Module:language-like" local load_module = "Module:load" local wm_languages_data_module = "Module:wikimedia languages/data" local get_by_code -- Defined below. local gmatch = string.gmatch local is_known_language_tag = mw.language.isKnownLanguageTag local make_object -- Defined below. local require = require local setmetatable = setmetatable local type = type --[==[ Load...'
54977
Scribunto
text/plain
local export = {}
local languages_module = "Module:languages"
local language_like_module = "Module:language-like"
local load_module = "Module:load"
local wm_languages_data_module = "Module:wikimedia languages/data"
local get_by_code -- Defined below.
local gmatch = string.gmatch
local is_known_language_tag = mw.language.isKnownLanguageTag
local make_object -- Defined below.
local require = require
local setmetatable = setmetatable
local type = type
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function get_lang(...)
get_lang = require(languages_module).getByCode
return get_lang(...)
end
local function get_lang_data_module_name(...)
get_lang_data_module_name = require(languages_module).getDataModuleName
return get_lang_data_module_name(...)
end
local function load_data(...)
load_data = require(load_module).load_data
return load_data(...)
end
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local wm_languages_data
local function get_wm_languages_data()
wm_languages_data, get_wm_languages_data = load_data(wm_languages_data_module), nil
return wm_languages_data
end
local WikimediaLanguage = {}
WikimediaLanguage.__index = WikimediaLanguage
function WikimediaLanguage:getCode()
return self._code
end
function WikimediaLanguage:getCanonicalName()
return self._data[1]
end
--function WikimediaLanguage:getAllNames()
-- return self._data.names
--end
--[==[Returns a table of types as a lookup table (with the types as keys).
Currently, the only possible type is {Wikimedia language}.]==]
function WikimediaLanguage:getTypes()
local types = self._types
if types == nil then
types = {["Wikimedia language"] = true}
local rawtypes = self._data.type
if rawtypes then
for t in gmatch(rawtypes, "[^,]+") do
types[t] = true
end
end
self._types = types
end
return types
end
--[==[Given a list of types as strings, returns true if the Wikimedia language has all of them.]==]
function WikimediaLanguage:hasType(...)
WikimediaLanguage.hasType = require(language_like_module).hasType
return self:hasType(...)
end
function WikimediaLanguage:getWiktionaryLanguage()
local object = self._wiktionaryLanguageObject
if object == nil then
object = get_lang(self._data.wiktionary_code, nil, "allow etym")
self._wiktionaryLanguageObject = object
end
return object
end
-- Do NOT use this method!
-- All uses should be pre-approved on the talk page!
function WikimediaLanguage:getData()
return self._data
end
--[==[Returns the name of the module containing the Wikimedia language's data (if any). Currently, this is always [[Module:wikimedia languages/data]].]==]
function WikimediaLanguage:getDataModuleName()
return wm_languages_data_module
end
function export.makeObject(code, data)
local data_type = type(data)
if data_type ~= "table" then
error(("bad argument #2 to 'makeObject' (table expected, got %s)"):format(data_type))
end
return setmetatable({_data = data, _code = code}, WikimediaLanguage)
end
make_object = export.makeObject
function export.getByCode(code)
-- Only accept codes the software recognises.
if not is_known_language_tag(code) then
return nil
end
local data = (wm_languages_data or get_wm_languages_data())[code]
-- If there is no specific Wikimedia code, then "borrow" the information
-- from the general Wiktionary language code.
local name, wiktionary_code
if data ~= nil then
name, wiktionary_code = data[1], data.wiktionary_code
if not (name == nil or wiktionary_code == nil) then
return make_object(code, data)
end
end
-- Get the associated Wiktionary language, using the wiktionary_code key or
-- else the input code.
if wiktionary_code == nil then
wiktionary_code = code
end
local lang = get_lang(wiktionary_code, nil, "allow etym", "allow family")
if lang ~= nil then
return make_object(code, {
name == nil and lang:getCanonicalName() or name,
wiktionary_code = wiktionary_code,
})
end
-- If there's no Wiktionary language for the relevant code, throw an error.
-- This should never happen.
local msg, arg3
if data == nil then
msg = "code '%s' is a valid Wikimedia language code, but there is no corresponding data in [[%s]], [[%s]] or [[Module:families/data]]"
elseif wiktionary_code ~= code then
msg = "code '%s' is a valid Wikimedia language code and has data in [[%s]], but its 'wiktionary_code' key '%s' is not valid"
arg3 = wiktionary_code
else
msg = "code '%s' is a valid Wikimedia language code and has data in [[%s]], but no corresponding data in [[%s]] or [[Module:families/data]]"
end
error(msg:format(code, wm_languages_data_module, arg3 or get_lang_data_module_name(code)))
end
get_by_code = export.getByCode
function export.getByCodeWithFallback(code)
local object = get_by_code(code)
if object ~= nil then
return object
end
local lang = get_lang(code, nil, "allow etym")
return lang ~= nil and lang:getWikimediaLanguages()[1] or nil
end
return export
ef45ys2do2au63pewsjhq7qzjxsy1nr
Module:table/append
828
8103
54978
2026-09-30T17:43:20Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local select = select local function append(n, i, t, t_len, item, ...) local k = 0 while true do k = k + 1 local v = item[k] if v ~= nil then t_len = t_len + 1 t[t_len] = v elseif i == n then return t else return append(n, i + 1, t, t_len, ...) end end end --[==[ Appends any number of lists together as a new list.]==] return function(...) local n, t = select("#", ...), {} return n == 0 and t or append(n, 1, t, 0, ...) end'
54978
Scribunto
text/plain
local select = select
local function append(n, i, t, t_len, item, ...)
local k = 0
while true do
k = k + 1
local v = item[k]
if v ~= nil then
t_len = t_len + 1
t[t_len] = v
elseif i == n then
return t
else
return append(n, i + 1, t, t_len, ...)
end
end
end
--[==[
Appends any number of lists together as a new list.]==]
return function(...)
local n, t = select("#", ...), {}
return n == 0 and t or append(n, 1, t, 0, ...)
end
kr9qgu53vdzydjiqsbbxw5wnf2llyao
Module:table/compressSparseArray
828
8104
54979
2026-09-30T17:43:51Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_num_keys_module = "Module:table/numKeys" local function num_keys(...) num_keys = require(table_num_keys_module) return num_keys(...) end --[==[ Given a list that may contain gaps (e.g. {1, 2, nil, 4}), returns a new gapless list in the same order.]==] return function(t) local list, keys, i = {}, num_keys(t), 0 while true do i = i + 1 local k = keys[i] if k == nil then return list end list[i] = t[k] end end'
54979
Scribunto
text/plain
local table_num_keys_module = "Module:table/numKeys"
local function num_keys(...)
num_keys = require(table_num_keys_module)
return num_keys(...)
end
--[==[
Given a list that may contain gaps (e.g. {1, 2, nil, 4}), returns a new gapless list in the same order.]==]
return function(t)
local list, keys, i = {}, num_keys(t), 0
while true do
i = i + 1
local k = keys[i]
if k == nil then
return list
end
list[i] = t[k]
end
end
23nyyesbtac5ffo4gwnkyx8uhxrigj0
Module:table/contains
828
8105
54980
2026-09-30T17:44:13Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_find_module = "Module:table/find" local function table_find(...) table_find = require(table_find_module) return table_find(...) end --[==[ Given a list and a value to be found, returns {true} if the value is in the array portion of the list, or {false} if not. `options` is an optional table of additional options to control the behavior of the operation. The following options are recognized: * `comparison`: Function of two arguments to compare whe...'
54980
Scribunto
text/plain
local table_find_module = "Module:table/find"
local function table_find(...)
table_find = require(table_find_module)
return table_find(...)
end
--[==[
Given a list and a value to be found, returns {true} if the value is in the array portion of the list, or {false} if not.
`options` is an optional table of additional options to control the behavior of the operation. The following options are recognized:
* `comparison`: Function of two arguments to compare whether `item` is equal to an existing item in `list`. If unspecified, items are considered equal if either the standard equality operator {==} or {deepEquals} return {true}. As a special case, if the string value {"=="} is specified, then the standard equality operator alone will be used.
* `key`: Function of one argument to return a comparison key, which will be used with the comparison function. The key function is applied to both `item` and the existing item in `list` to compare against, and the comparison is done against the results.]==]
return function(...)
return table_find(...) and true or false
end
avq83e5r7vp9ry6mn3nxu1gtka33xms
Module:table/deepEquals
828
8106
54981
2026-09-30T17:44:45Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local getmetatable = getmetatable local next = next local pairs = pairs local type = type local function is_eq(a, b, seen, include_mt) -- If `a` and `b` have been compared before, return the memoized result. This will usually be true, since failures normally fail the whole check outright, but match failures are tolerated during the laborious check without this happening, since it compares key/value pairs until it finds a match, so it could be false. local memo...'
54981
Scribunto
text/plain
local getmetatable = getmetatable
local next = next
local pairs = pairs
local type = type
local function is_eq(a, b, seen, include_mt)
-- If `a` and `b` have been compared before, return the memoized result. This will usually be true, since failures normally fail the whole check outright, but match failures are tolerated during the laborious check without this happening, since it compares key/value pairs until it finds a match, so it could be false.
local memo_a = seen[a]
if memo_a then
local result = memo_a[b]
if result ~= nil then
return result
end
-- To avoid recursive references causing infinite loops, assume the tables currently being compared are equivalent by memoizing the comparison as true; this will be corrected to false if there's a match failure.
memo_a[b] = true
else
memo_a = {[b] = true}
seen[a] = memo_a
end
-- Don't bother checking `memo_b` for `a`, since if `a` and `b` had been compared before then `b` would be in `memo_a`, but it isn't.
local memo_b = seen[b]
if memo_b then
memo_b[a] = true
else
memo_b = {[a] = true}
seen[b] = memo_b
end
-- If `include_mt` is set, check the metatables are equivalent.
if include_mt then
local mt_a, mt_b = getmetatable(a), getmetatable(b)
if not (mt_a == mt_b or type(mt_a) == "table" and type(mt_b) == "table" and is_eq(mt_a, mt_b, seen, true)) then
memo_a[b], memo_b[a] = false, false
return false
end
end
-- Copy all key/values pairs in `b` to `remaining_b`, and count the size: this uses `pairs`, which will also be used to iterate over `a`, ensuring that `a` and `b` are iterated over using the same iterator. This is necessary to ensure that `deepEquals(a, b)` and `deepEquals(b, a)` always give the same result. Simply iterating over `a` while accessing keys in `b` for comparison would ignore any `__pairs` metamethod that `b` has, which could cause asymmetrical outputs if `__pairs` returns more or less than the complete set of key/value pairs accessible via `__index`, so using `pairs` for both `a` and `b` prevents this.
-- TODO: handle exotic `__pairs` methods which return the same key multiple times with different values.
local remaining_b, size_b = {}, 0
for k_b, v_b in pairs(b) do
remaining_b[k_b], size_b = v_b, size_b + 1
end
-- Fast check: iterate over the keys in `a`, checking if an equivalent value exists at the same key in `remaining_b`. As matches are found, key/value pairs are removed from `remaining_b`. If any keys in `a` or `remaining_b` are tables, the fast check will only work if the exact same object exists as a key in the other table. Any others from `a` that don't match anything in `remaining_b` are added to `remaining_a`, while those in `remaining_b` that weren't found will still remain once the loop ends. `remaining_a` and `remaining_b` are then compared at the end with the laborious check.
local size_a, remaining_a = 0
for k, v_a in pairs(a) do
local v_b = remaining_b[k]
-- If `k` isn't in `remaining_b`, `a` and `b` can't be equivalent unless it's a table.
if v_b == nil then
if type(k) ~= "table" then
memo_a[b], memo_b[a] = false, false
return false
-- Otherwise, add the `k`/`v_a` pair to `remaining_a` for the laborious check.
elseif not remaining_a then
remaining_a = {}
end
remaining_a[k], size_a = v_a, size_a + 1
-- Otherwise, if `k` exists in `a` and `remaining_b`, `v_a` and `v_b` must be equivalent for there to be a match.
elseif v_a == v_b or type(v_a) == "table" and type(v_b) == "table" and is_eq(v_a, v_b, seen, include_mt) then
remaining_b[k], size_b = nil, size_b - 1
else
memo_a[b], memo_b[a] = false, false
return false
end
end
-- Must be the same number of remaining keys in each table.
if size_a ~= size_b then
memo_a[b], memo_b[a] = false, false
return false
-- If the size is 0, there's nothing left to check.
elseif size_a == 0 then
return true
end
-- Laborious check: since it's not possible to use table lookups to check if two keys are equivalent when they're tables, check each key/value pair in `remaining_a` against every key/value pair in `remaining_b` until a match is found, removing the matching key/value pair from `remaining_b` each time, to ensure one-to-one equivalence.
for k_a, v_a in next, remaining_a do
local success
for k_b, v_b in next, remaining_b do
-- Keys/value pairs must be equivalent in order to match.
if ( -- More efficient to compare the values first, as they might not be tables.
(v_a == v_b or type(v_a) == "table" and type(v_b) == "table" and is_eq(v_a, v_b, seen, include_mt)) and
(k_a == k_b or type(k_a) == "table" and type(k_b) == "table" and is_eq(k_a, k_b, seen, include_mt))
) then
-- Remove matched key from `remaining_b`, and break the inner loop.
success, remaining_b[k_b] = true, nil
break
end
end
-- Fail if `remaining_b` runs out of keys, as the `k_a`/`v_a` pair still hasn't matched.
if not success then
memo_a[b], memo_b[a] = false, false
return false
end
end
-- If every key/value pair in `remaining_a` matched with one in `remaining_b`, `a` and `b` must be equivalent. Note that `remaining_b` will now be empty, since the laborious check only starts if `remaining_a` and `remaining_b` are the same size.
return true
end
--[==[
Recursively compare two values that may be tables, and returns true if all key-value pairs are structurally equivalent. Note that this handles arbitrary nesting of subtables (including recursive nesting) to any depth, for keys as well as values.
If `include_mt` is true, then metatables are also compared.]==]
return function(a, b, include_mt)
-- Do simple checks before calling is_eq to avoid generating an unnecessary memo.
-- Simple equality check and type check; if not a ~= b, a and b can only be equivalent if they're both tables.
return a == b or type(a) == "table" and type(b) == "table" and is_eq(a, b, {}, include_mt)
end
6rgswepb0gwhbiojbyrcr1at7m4h27n
Module:table/empty
828
8107
54982
2026-09-30T17:45:11Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_get_metamethod = "Module:table/getMetamethod" local next = next local pairs = pairs local pairs_default_iter = pairs{} local function get_metamethod(...) get_metamethod = require(table_get_metamethod) return get_metamethod(...) end -- Not possible to simply iterate with a for-loop, since removing a key means -- it can't be given to next() on the subsequent iteration. Instead, repeatedly -- giving next() the initial state and key will either retur...'
54982
Scribunto
text/plain
local table_get_metamethod = "Module:table/getMetamethod"
local next = next
local pairs = pairs
local pairs_default_iter = pairs{}
local function get_metamethod(...)
get_metamethod = require(table_get_metamethod)
return get_metamethod(...)
end
-- Not possible to simply iterate with a for-loop, since removing a key means
-- it can't be given to next() on the subsequent iteration. Instead, repeatedly
-- giving next() the initial state and key will either return some valid key,
-- which can then be removed, or nil, once the table is empty.
local function empty_raw(t, iter, state, init)
while true do
local k = iter(state, init)
if k == nil then
return t
end
t[k] = nil
end
end
--[==[
Given a table, sets all keys to {nil}. If `raw` is set, then metamethods will be ignored.
Warning: this function is only recommended for tables with a small handful of keys, where reusing a table will avoid creating a large number of temporary objects. In the vast majority of situations, it is more efficient to simply create a new table.]==]
return function(t, raw)
if raw then
return empty_raw(t, next, t)
end
-- If `raw` is not set, try to check for a __pairs metamethod. The check
-- will fail if there's a protected metatable, but if it succeeds and
-- there's no __pairs metamethod, use empty_raw() as an optimisation.
local success, __pairs = get_metamethod(t, "__pairs")
if success and __pairs == nil then
return empty_raw(t, next, t)
end
-- If the iterator returned by the __pairs metamethod is just the default
-- pairs iterator or next(), use empty_raw() with the returned state and
-- key. This is most likely to happen if a protected metatable doesn't have
-- a __pairs metamethod.
local iter, state, k = pairs(t)
if iter == pairs_default_iter or iter == next then
return empty_raw(t, iter, state, k)
end
-- If the iterator returns nothing, `t` is already empty.
k = iter(state, k)
if k == nil then
return t -- already empty
end
-- Otherwise, build a table of keys first, then iterate over it to remove
-- keys from `t`, which ensures removals don't disrupt the loop.
local keys = {[k] = true}
for k in iter, state, k do
keys[k] = true
end
for k in next, keys do
t[k] = nil
end
return t
end
5cjvu27spz7eb4k7wsc8euj5vvq30dg
Module:table/extendIfNot
828
8108
54983
2026-09-30T17:45:42Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_insert_if_not_module = "Module:table/insertIfNot" local table_shallow_copy_module = "Module:table/shallowCopy" local require = require local type = type local function insert_if_not(...) insert_if_not = require(table_insert_if_not_module) return insert_if_not(...) end local function shallow_copy(...) shallow_copy = require(table_shallow_copy_module) return shallow_copy(...) end local function extend_with_pos(list, new_items, options) local i...'
54983
Scribunto
text/plain
local table_insert_if_not_module = "Module:table/insertIfNot"
local table_shallow_copy_module = "Module:table/shallowCopy"
local require = require
local type = type
local function insert_if_not(...)
insert_if_not = require(table_insert_if_not_module)
return insert_if_not(...)
end
local function shallow_copy(...)
shallow_copy = require(table_shallow_copy_module)
return shallow_copy(...)
end
local function extend_with_pos(list, new_items, options)
local i = 1
while true do
local item = new_items[i]
if item == nil then
return list
end
local success = insert_if_not(list, item, options)
if success then
options.pos = options.pos + 1
end
i = i + 1
end
end
local function extend_with_options_as_pos(list, new_items, pos)
local i = 1
while true do
local item = new_items[i]
if item == nil then
return list
end
local success = insert_if_not(list, item, pos)
if success then
pos = pos + 1
end
i = i + 1
end
end
local function extend_without_pos(list, new_items, options)
local i = 1
while true do
local item = new_items[i]
if item == nil then
return list
end
insert_if_not(list, item, options)
i = i + 1
end
end
--[==[
Extend an existing list by a new list, using {insertIfNot()} for each item.
`options` is an optional table of additional options to control the behavior of the operation. The following options are recognized:
* `pos`: As in {insertIfNot()}.
* `comparison`: As in {insertIfNot()}.
* `key`: As in {insertIfNot()}.
* `combine`: As in {insertIfNot()}.
Unlike {insertIfNot()}, this function does not return a boolean indicating whether any items were inserted.]==]
return function(list, new_items, options)
if options == nil then
return extend_without_pos(list, new_items)
elseif type(options) == "number" then
return extend_with_options_as_pos(list, new_items, options)
end
local pos = options.pos
if pos == nil then
return extend_without_pos(list, new_items, options)
end
return extend_with_pos(list, new_items, shallow_copy(options))
end
crdbb3ps6pmk0j0qpkle192t06mpn8v
Module:table/find
828
8109
54984
2026-09-30T17:46:03Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_deep_equals_module = "Module:table/deepEquals" local deep_equals local function get_deep_equals() deep_equals, get_deep_equals = require(table_deep_equals_module), nil return deep_equals end local function equality_op(a, b) return a == b end --[==[ Given a list and a value to be found, returns the value's index if the value is in the array portion of the list, or {nil} if not found. `options` is an optional table of additional options to contro...'
54984
Scribunto
text/plain
local table_deep_equals_module = "Module:table/deepEquals"
local deep_equals
local function get_deep_equals()
deep_equals, get_deep_equals = require(table_deep_equals_module), nil
return deep_equals
end
local function equality_op(a, b)
return a == b
end
--[==[
Given a list and a value to be found, returns the value's index if the value is in the array portion of the list, or {nil} if not found.
`options` is an optional table of additional options to control the behavior of the operation. The following options are recognized:
* `comparison`: Function of two arguments to compare whether `item` will be deemed equal to an existing item in `list`. If unspecified, items are deemed equal if {deepEquals} returns {true} (which is always the case if the equality operator {==} would return true). As a special case, if the string value {"=="} is specified, then the equality operator alone will be used.
* `key`: Function of one argument to return a comparison key, which will be used with the comparison function. The key function is applied to both `item` and the existing item in `list` to compare against, and the comparison is done against the results.]==]
return function(list, x, options)
local i, key_func, comp_func = 1
if options == nil then
comp_func = deep_equals or get_deep_equals()
else
key_func, comp_func = options.key, options.comparison
if key_func ~= nil then
x = key_func(x)
end
if comp_func == nil then
comp_func = deep_equals or get_deep_equals()
elseif comp_func == "==" then
comp_func = equality_op
end
end
while true do
local v = list[i]
if v == nil then
return nil
elseif key_func ~= nil then
v = key_func(v)
end
if comp_func(v, x) then
return i
end
i = i + 1
end
end
rwdwjaudop2hyb464ozqxzbdhpka0td
Module:table/flatten
828
8110
54985
2026-09-30T17:46:26Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local error = error local insert = table.insert local type = type local function flatten(t, list, n, current) current[t] = true local i = 0 while true do i = i + 1 local v = t[i] if v == nil then current[t] = nil return n elseif type(v) ~= "table" then n = n + 1 list[n] = v elseif current[v] then error("loop in input list") else n = flatten(v, list, n, current) end end end --[==[ Given a list, which may contain sublists, flat...'
54985
Scribunto
text/plain
local error = error
local insert = table.insert
local type = type
local function flatten(t, list, n, current)
current[t] = true
local i = 0
while true do
i = i + 1
local v = t[i]
if v == nil then
current[t] = nil
return n
elseif type(v) ~= "table" then
n = n + 1
list[n] = v
elseif current[v] then
error("loop in input list")
else
n = flatten(v, list, n, current)
end
end
end
--[==[
Given a list, which may contain sublists, flatten it into a single list. For example, {flatten({ "a", { "b", "c" }, "d" })} ->
{ { "a", "b", "c", "d" }}]==]
return function(t)
local list = {}
flatten(t, list, 0, {})
return list
end
529c10ci266kmiv1iiqgwuyg3hgav1d
Module:table/indexIpairs
828
8111
54986
2026-09-30T17:47:00Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable" local error = error local ipairs = ipairs local ipairs_iter = ipairs{} local pcall = pcall local rawget = rawget local type = type local function get_unprotected_metatable(...) get_unprotected_metatable = require(table_get_unprotected_metatable) return get_unprotected_metatable(...) end local current = {} -- Iterator used when there is no __ipairs metamethod to deal with. local f...'
54986
Scribunto
text/plain
local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable"
local error = error
local ipairs = ipairs
local ipairs_iter = ipairs{}
local pcall = pcall
local rawget = rawget
local type = type
local function get_unprotected_metatable(...)
get_unprotected_metatable = require(table_get_unprotected_metatable)
return get_unprotected_metatable(...)
end
local current = {}
-- Iterator used when there is no __ipairs metamethod to deal with.
local function default_iter(t, i)
i = i + 1
local v = t[i]
if v ~= nil then
return i, v
end
end
local function index_ipairs(t)
if current[t] then
return ipairs_iter, t, 0
end
local mt = get_unprotected_metatable(t)
if mt == nil then
return ipairs_iter, t, 0
elseif not mt then
current[t] = true
-- Use pcall(), so that `t` can always be unset from `current`.
local success, iter, state, init = pcall(ipairs, t)
current[t] = nil
-- If there was an error, raise it.
if not success then
error(iter)
end
return iter, state, init
end
-- Iterate up through __index metamethods.
local parent
repeat
-- If an __ipairs metamethod is found, use the custom iterator.
local ipairs_mm = rawget(mt, "__ipairs")
if not (ipairs_mm == nil or ipairs_mm == index_ipairs) then
break
end
-- If __index isn't a table, use default_iter().
parent = rawget(mt, "__index")
if not (parent and type(parent) == "table") then
return default_iter, t, 0
end
-- If there's no metatable, use default_iter().
mt = get_unprotected_metatable(parent)
if mt == nil then
return default_iter, t, 0
end
-- get_unprotected_metatable() will return false if the metatable is
-- protected, which means there could be an __ipairs metamethod, so use the
-- custom iterator.
until not mt
local iter, state
return function(t, i)
local new_i = i + 1
local v = t[new_i]
if v ~= nil then
return new_i, v
-- Use iter() as a fallback.
elseif iter == nil then
iter, state = ipairs(parent)
end
return iter(state, i)
end, t, 0
end
return index_ipairs
8r3dgh93aivb4vx7t0eff6tqdguttv8
Module:table/indexPairs
828
8112
54987
2026-09-30T17:47:32Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable" local error = error local next = next local pairs = pairs local pcall = pcall local rawget = rawget local type = type local function get_unprotected_metatable(...) get_unprotected_metatable = require(table_get_unprotected_metatable) return get_unprotected_metatable(...) end local current = {} local index_pairs -- Takes a table, and returns: -- * The value at __index in its metata...'
54987
Scribunto
text/plain
local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable"
local error = error
local next = next
local pairs = pairs
local pcall = pcall
local rawget = rawget
local type = type
local function get_unprotected_metatable(...)
get_unprotected_metatable = require(table_get_unprotected_metatable)
return get_unprotected_metatable(...)
end
local current = {}
local index_pairs
-- Takes a table, and returns:
-- * The value at __index in its metatable.
-- * A boolean stating whether it has a __pairs metamethod.
-- * The three iterator values returned by calling pairs() on it.
local function get_iter(t)
local mt = get_unprotected_metatable(t)
if mt == nil then
return nil, false, next, t, nil
-- If the metatable is protected, it's not possible to get the __index or
-- __pairs metamethods, so call pairs() on `t` and return the result. If
-- __pairs is index_pairs, set current[t] so that it knows to return next()
-- instead of getting into an infinite loop.
elseif not mt then
current[t] = true
-- Use pcall(), so that `t` can always be unset from `current`.
local success, iter, state, init = pcall(pairs, t)
current[t] = nil
-- If there was an error, raise it.
if not success then
error(iter)
end
return nil, true, iter, state, init
end
-- If there's a __pairs metamethod that isn't index_pairs(), use it.
local pairs_mm = rawget(mt, "__pairs")
if not (pairs_mm == nil or pairs_mm == index_pairs) then
return nil, true, pairs_mm(t)
end
local parent = rawget(mt, "__index")
if type(parent) == "function" then
parent = nil
end
return parent, false, next, t, nil
end
function index_pairs(t)
if current[t] then
return next, t, nil
end
local parent, is_pairs_mm, iter, state, k = get_iter(t)
-- If there is no parent, just return the iterator.
if parent == nil then
return iter, state, k
end
local v, seen_k, seen_t, catch_values
return function()
while true do
if is_pairs_mm then
return catch_values(iter(state, k))
end
while true do
k, v = iter(state, k)
if k == nil then
break
-- If a repeated key is found, skip and iterate again.
elseif not seen_k then
seen_k = {[k] = true}
return k, v
elseif not seen_k[k] then
seen_k[k] = true
return k, v
end
end
if parent == nil then
return
elseif seen_t == nil then
seen_t = {}
end
seen_t[t] = true
t, parent, is_pairs_mm, iter, state, k = parent, get_iter(parent)
if seen_t[t] then
error("loop in gettable")
elseif is_pairs_mm then
-- Skip keys which have already been seen, but the return is
-- arbitrary, so allow repeats if the metamethod generates them.
function catch_values(new_k, ...)
if seen_k[new_k] then
return catch_values(iter(state, new_k))
end
k = new_k
return new_k, ...
end
end
end
end
end
return index_pairs
rm6y8vh41zlq8hc2bo1vk41ur43xx7v
Module:table/insertIfNot
828
8113
54988
2026-09-30T17:48:01Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_find_module = "Module:table/find" local insert = table.insert local type = type local function table_find(...) table_find = require(table_find_module) return table_find(...) end --[==[ Given a `list` and a `new_item` to be inserted, append the value to the end of the list if not already present (or insert at an arbitrary position, if `options.pos` is given; see below). Comparison is by value, using {deepEquals}. `options` is an optional table of...'
54988
Scribunto
text/plain
local table_find_module = "Module:table/find"
local insert = table.insert
local type = type
local function table_find(...)
table_find = require(table_find_module)
return table_find(...)
end
--[==[
Given a `list` and a `new_item` to be inserted, append the value to the end of the list if not already present (or insert at an arbitrary position, if `options.pos` is given; see below). Comparison is by value, using {deepEquals}.
`options` is an optional table of additional options to control the behavior of the operation. The following options are recognized:
* `pos`: Position at which insertion happens (i.e. before the existing item at position `pos`).
* `comparison`: Function of two arguments to compare whether `item` is equal to an existing item in `list`. If unspecified, items are considered equal if either the standard equality operator {==} or {deepEquals} return {true}. As a special case, if the string value {"=="} is specified, then the standard equality operator alone will be used.
* `key`: Function of one argument to return a comparison key, which will be used with the comparison function. The key function is applied to both `item` and the existing item in `list` to compare against, and the comparison is done against the results. This is useful when inserting a complex structure into an existing list while avoiding duplicates.
* `combine`: Function of three arguments (the existing item, the new item and the position, respectively) to combine an existing item with `new_item`, when `new_item` is found in `list`. If unspecified, the existing item is left alone.
Returns {false} if an entry is already found, or {true} if inserted. By default, {false} indicates that no change was made to the input table, but if the `combine` is used, {false} indicates that the pre-existing entry was modified.
For compatibility, `pos` can be specified directly as the third argument in place of `options`, but this is not recommended for new code.
NOTE: This function is O(N) in the size of the existing list. If you use this function in a loop to insert several items, you will get O(M*(M+N)) behavior, effectively O((M+N)^2). Thus it is not recommended to use this unless you are sure the total number of items will be small. (An alternative for large lists is to insert all the items without checking for duplicates, and use {removeDuplicates()} at the end.)]==]
return function(list, new_item, options)
local pos
if options then
if type(options) == "number" then
pos, options = options, nil
else
pos = options.pos
end
end
local i = table_find(list, new_item, options)
if not i then
if pos == nil then
insert(list, new_item)
else
insert(list, pos, new_item)
end
return true
elseif options ~= nil then
local combine_func = options.combine
if combine_func ~= nil then
local newval = combine_func(list[i], new_item, i)
if newval ~= nil then
list[i] = newval
end
end
end
return false
end
tjy2hflim20bhcfnmmalkf8958c64ib
Module:table/invert
828
8114
54989
2026-09-30T17:48:22Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local pairs = pairs --[==[ Invert a table. For example, {invert({ "a", "b", "c" })} -> { { a = 1, b = 2, c = 3 }}]==] return function(t) local map = {} for k, v in pairs(t) do map[v] = k end return map end'
54989
Scribunto
text/plain
local pairs = pairs
--[==[
Invert a table. For example, {invert({ "a", "b", "c" })} -> { { a = 1, b = 2, c = 3 }}]==]
return function(t)
local map = {}
for k, v in pairs(t) do
map[v] = k
end
return map
end
584zgb0j9tdibi3oq5xt0rnaq3wjeoe
Module:table/isArray
828
8115
54990
2026-09-30T17:48:45Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local pairs = pairs --[==[ Returns true if all keys in the table are consecutive integers starting from 1.]==] return function(t) -- pairs() is unordered, but can be assumed to return every key in `t`, so -- if every integer key from 1 to n (the number of keys in `t`) is in use, -- then all the keys in `t` form an integer range from 1 to n, making `t` -- a contiguous array. local i = 0 for _ in pairs(t) do i = i + 1 if t[i] == nil then return false...'
54990
Scribunto
text/plain
local pairs = pairs
--[==[
Returns true if all keys in the table are consecutive integers starting from 1.]==]
return function(t)
-- pairs() is unordered, but can be assumed to return every key in `t`, so
-- if every integer key from 1 to n (the number of keys in `t`) is in use,
-- then all the keys in `t` form an integer range from 1 to n, making `t`
-- a contiguous array.
local i = 0
for _ in pairs(t) do
i = i + 1
if t[i] == nil then
return false
end
end
return true
end
g82iokgqr1uqfaagx0is49bas3cpq0c
Module:table/isSubsetList
828
8116
54991
2026-09-30T17:49:13Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_list_to_set_module = "Module:table/listToSet" local pairs = pairs local function list_to_set(...) list_to_set = require(table_list_to_set_module) return list_to_set(...) end --[==[ Returns true if the first list, taken as a set, is a subset of the second list, taken as a set.]==] return function(t1, t2) t2 = list_to_set(t2) for _, v in pairs(t1) do if t2[v] == nil then return false end end return true end'
54991
Scribunto
text/plain
local table_list_to_set_module = "Module:table/listToSet"
local pairs = pairs
local function list_to_set(...)
list_to_set = require(table_list_to_set_module)
return list_to_set(...)
end
--[==[
Returns true if the first list, taken as a set, is a subset of the second list, taken as a set.]==]
return function(t1, t2)
t2 = list_to_set(t2)
for _, v in pairs(t1) do
if t2[v] == nil then
return false
end
end
return true
end
pcdp2epgqndvuy22amynwhyqbhyrhpl
Module:table/isSubsetMap
828
8117
54992
2026-09-30T17:49:41Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local pairs = pairs --[==[ Returns true if the first map, taken as a set, is a subset of the second map, taken as a set.]==] return function(t1, t2) for k in pairs(t1) do if t2[k] == nil then return false end end return true end'
54992
Scribunto
text/plain
local pairs = pairs
--[==[
Returns true if the first map, taken as a set, is a subset of the second map, taken as a set.]==]
return function(t1, t2)
for k in pairs(t1) do
if t2[k] == nil then
return false
end
end
return true
end
oqh54e8vhwwpi0kv29wuqnbe8n0creu
Module:table/maxIndex
828
8118
54993
2026-09-30T17:50:07Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local math_module = "Module:math" local maxn = table.maxn local pairs = pairs local function is_positive_integer(...) is_positive_integer = require(math_module).is_positive_integer return is_positive_integer(...) end --[==[ Returns the highest positive integer index of a table or array that possibly has holes in it, or otherwise 0 if no positive integer keys are found. Note that this differs from `table.maxn`, which returns the highest positive numerical ind...'
54993
Scribunto
text/plain
local math_module = "Module:math"
local maxn = table.maxn
local pairs = pairs
local function is_positive_integer(...)
is_positive_integer = require(math_module).is_positive_integer
return is_positive_integer(...)
end
--[==[
Returns the highest positive integer index of a table or array that possibly has holes in it, or otherwise 0 if no positive integer keys are found. Note that this differs from `table.maxn`, which returns the highest positive numerical index, even if it is not an integer.]==]
if maxn == nil then
return function(t)
local max_key = 0
for k in pairs(t) do
if is_positive_integer(k) and k > max_key then
max_key = k
end
end
return max_key
end
else -- table.maxn() is deprecated, but use it if it's available
return function(t)
-- Quick check: if maxn() returns an integer >= 0, return it.
local max_key = maxn(t)
if is_positive_integer(max_key, "include_0") then
return max_key
end
-- Slow check.
max_key = 0
for k in pairs(t) do
if is_positive_integer(k) and k > max_key then
max_key = k
end
end
return max_key
end
end
e1j8nlmbua7wpbkz82e5y2727u2uoir
Module:table/pack
828
8119
54994
2026-09-30T17:50:32Z
Deadend0914
7211
Gesceop tramet þe hafaþ '--[==[ An implementation of the {table.pack} function found in Lua 5.2+, which returns a table containing a list of the arguments passed. The returned table also contains the key {n}, which is an integer giving the number of arguments passed. Note that the list in the returned table will contain gaps if any of the arguments passed were {nil}. This will also be reflected in the value of {n}, which always matches the number of arguments passed (e.g. {pack("foo")}...'
54994
Scribunto
text/plain
--[==[
An implementation of the {table.pack} function found in Lua 5.2+, which returns a table containing a list of the arguments passed. The returned table also contains the key {n}, which is an integer giving the number of arguments passed.
Note that the list in the returned table will contain gaps if any of the arguments passed were {nil}. This will also be reflected in the value of {n}, which always matches the number of arguments passed (e.g. {pack("foo")} returns the table { {"foo", n = 1} }, whereas {pack("foo", nil, nil)} returns { {"foo", n = 3} }).]==]
return table.pack or function(...)
-- On Lua 5.1, use the hidden `arg` variable with `...`, which is the same
-- as table.pack(...) on Lua 5.2+. Both are strictly equivalent to
-- {n = select("#", ...), ...}, but more performant.
return arg
end
ai5lyp0zhpjyp9n736jj7c60lm0cvm7
Module:table/removeDuplicates
828
8120
54995
2026-09-30T17:51:00Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_deep_equals_module = "Module:table/deepEquals" local table_get_metamethod_module = "Module:table/getMetamethod" local rawequal = rawequal local require = require local type = type local function get_metamethod(...) get_metamethod = require(table_get_metamethod_module) return get_metamethod(...) end local deep_equals local function get_deep_equals() deep_equals, get_deep_equals = require(table_deep_equals_module), nil return deep_equals end loc...'
54995
Scribunto
text/plain
local table_deep_equals_module = "Module:table/deepEquals"
local table_get_metamethod_module = "Module:table/getMetamethod"
local rawequal = rawequal
local require = require
local type = type
local function get_metamethod(...)
get_metamethod = require(table_get_metamethod_module)
return get_metamethod(...)
end
local deep_equals
local function get_deep_equals()
deep_equals, get_deep_equals = require(table_deep_equals_module), nil
return deep_equals
end
local function equal(a, b)
return a == b
end
-- Sentinel for nil.
local _nil = {}
local __eq_types = {
table = true,
userdata = true,
}
local function seen_map_check(seen_map, v_comp, comp_func)
if not comp_func(v_comp, v_comp) then
return false, seen_map
end
local v_seen_map = v_comp == nil and _nil or v_comp
if not seen_map then
seen_map = {[v_seen_map] = true}
return false, seen_map
elseif seen_map[v_seen_map] then
return true, seen_map
end
seen_map[v_seen_map] = true
return false, seen_map
end
-- Differs from [[Module:table/contains]], as keys might be nil.
local function seen_array_contains(v_comp, seen_array, seen_array_n, comp_func)
for i = 1, seen_array_n do
if comp_func(v_comp, seen_array[i]) then
return true
end
end
return false
end
local function default_seen_array_check(v_comp, seen_array, seen_array_n, comp_func)
local v_comp_type = type(v_comp)
if not __eq_types[v_comp_type] then
return false, seen_array, seen_array_n
elseif not seen_array then
return false, {v_comp}, 1
-- If `comp_func` is `deep_equals` and `v_comp` is a table, `seen_array`
-- must be checked.
elseif comp_func == deep_equals and v_comp_type == "table" then
if seen_array_contains(v_comp, seen_array, seen_array_n, comp_func) then
return true, seen_array, seen_array_n
end
else
-- If `v_comp` has an `__eq` metamethod (or could have one, if it's not
-- possible to retrieve due to a protected metatable), then check with
-- `seen_array_contains`.
local success, __eq = get_metamethod(v_comp, "__eq")
if (
not (success and __eq == nil) and
seen_array_contains(v_comp, seen_array, seen_array_n, comp_func)
) then
return true, seen_array, seen_array_n
end
end
seen_array_n = seen_array_n + 1
seen_array[seen_array_n] = v_comp
return false, seen_array, seen_array_n
end
local function other_seen_array_check(v_comp, seen_array, seen_array_n, comp_func)
if not seen_array then
return false, {v_comp}, 1
elseif seen_array_contains(v_comp, seen_array, seen_array_n, comp_func) then
return true, seen_array, seen_array_n
end
seen_array_n = seen_array_n + 1
seen_array[seen_array_n] = v_comp
return false, seen_array, seen_array_n
end
return function(list, options)
-- Values are converted to comparison-keys using `key_func`, if supplied, or else the value itself is used as the comparison-key; these are then compared with `comp_func` to determine duplicates.
-- To avoid comparing all comparison-keys with each other, which is O(n^2), `seen_map`, a lookup table, and `seen_array`, an array, are used as memos to store already-seen comparison-keys.
-- Comparison-keys are stored in `seen_map` if comp_func(x, x) evaluates as true.
-- This is always the case if `comp_func` is `rawequal`, `==` or `deep_equals`, unless the comparison-key is NaN.
-- If `comp_func` is anything else, the value is checked against itself before storing it.
-- If the comparison-key isn't found in `seen_map`, it's checked against each comparison-key stored in `seen_array`.
-- This is never needed if `comp_func` is `rawequal`, which only returns true for primitive equality (i.e. `seen_map` is sufficient for everything).
-- If `comp_func` is `==` or `deep_equals`, `seen_array` is used for data types which could have `__eq` metamethods, which can result in non-primitively equal values being deemed equal (i.e. new comparison-keys need to be compared in turn).
-- If `comp_func` is anything else, all comparison-keys will need to be stored in `seen_array`.
local key_func, comp_func, seen_array_check
if options == nil then
comp_func, seen_array_check = deep_equals or get_deep_equals(), default_seen_array_check
else
key_func, comp_func = options.key, options.comparison
if comp_func == nil then
comp_func, seen_array_check = deep_equals or get_deep_equals(), default_seen_array_check
elseif comp_func == "==" then
comp_func, seen_array_check = equal, default_seen_array_check
-- `seen_array_check` is not necessary if `comp_func` is `rawequal`.
elseif comp_func ~= rawequal then
if comp_func == (deep_equals or get_deep_equals()) then
seen_array_check = default_seen_array_check
else
seen_array_check = other_seen_array_check
end
end
end
local output, i, n, seen_map, seen_array, seen_array_n = {}, 1, 0
while true do
local v = list[i]
if v == nil then
return output
end
local v_comp, seen = v
if key_func ~= nil then
v_comp = key_func(v_comp)
end
seen, seen_map = seen_map_check(seen_map, v_comp, comp_func)
if not seen then
if seen_array_check then
seen, seen_array, seen_array_n = seen_array_check(v_comp, seen_array, seen_array_n, comp_func)
end
if not seen then
n = n + 1
output[n] = v
end
end
i = i + 1
end
end
8he00zs6o0bh1d04bio2gq4rh3i45vu
Module:table/reverse
828
8121
54996
2026-09-30T17:51:24Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_length_module = "Module:table/length" local function table_len(...) table_len = require(table_length_module) return table_len(...) end --[==[ Takes a list, and returns a new list with the values in the reverse order: { { "a", "b", "c" }} -> { { "c", "b", "a" }}.]==] return function(t) local list, t_len = {}, table_len(t) for i = 1, t_len do list[i] = t[t_len - i + 1] end return list end'
54996
Scribunto
text/plain
local table_length_module = "Module:table/length"
local function table_len(...)
table_len = require(table_length_module)
return table_len(...)
end
--[==[
Takes a list, and returns a new list with the values in the reverse order: { { "a", "b", "c" }} -> { { "c", "b", "a" }}.]==]
return function(t)
local list, t_len = {}, table_len(t)
for i = 1, t_len do
list[i] = t[t_len - i + 1]
end
return list
end
5ohrlsa4osfo3dybez4sja0s8ze1ftr
Module:table/reverseIpairs
828
8122
54997
2026-09-30T17:51:57Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local ipairs = ipairs --[==[ Iterates through a table using `ipairs` in reverse. `__ipairs` metamethods will be used, including those which return arbitrary (i.e. non-array) keys, but note that this function assumes that the first return value is a key which can be used to retrieve a value from the input table via a table lookup. As such, `__ipairs` metamethods for which this assumption is not true will not work correctly. If the value `nil` is encountered ear...'
54997
Scribunto
text/plain
local ipairs = ipairs
--[==[
Iterates through a table using `ipairs` in reverse.
`__ipairs` metamethods will be used, including those which return arbitrary (i.e. non-array) keys, but note that this function assumes that the first return value is a key which can be used to retrieve a value from the input table via a table lookup. As such, `__ipairs` metamethods for which this assumption is not true will not work correctly.
If the value `nil` is encountered early (e.g. because the table has been modified), the loop will terminate early.]==]
return function(t)
-- `__ipairs` metamethods can return arbitrary keys, so compile a list.
local keys, i = {}, 0
for k in ipairs(t) do
i = i + 1
keys[i] = k
end
return function()
if i == 0 then
return nil
end
local k = keys[i]
-- Retrieve `v` from the table. These aren't stored during the initial
-- ipairs loop, so that they can be modified during the loop.
local v = t[k]
-- Return if not an early nil.
if v ~= nil then
i = i - 1
return k, v
end
end
end
g7u5t487acciyypwe4jwka7ibmfy9m2
Module:Table/setParent
828
8123
54998
2026-09-30T17:52:21Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable" local table_index_ipairs_module = "Module:table/indexIpairs" local table_index_pairs_module = "Module:table/indexPairs" local rawset = rawset local require = require local setmetatable = setmetatable local function get_unprotected_metatable(...) get_unprotected_metatable = require(table_get_unprotected_metatable) return get_unprotected_metatable(...) end local index_ipairs local f...'
54998
Scribunto
text/plain
local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable"
local table_index_ipairs_module = "Module:table/indexIpairs"
local table_index_pairs_module = "Module:table/indexPairs"
local rawset = rawset
local require = require
local setmetatable = setmetatable
local function get_unprotected_metatable(...)
get_unprotected_metatable = require(table_get_unprotected_metatable)
return get_unprotected_metatable(...)
end
local index_ipairs
local function get_index_ipairs()
index_ipairs, get_index_ipairs = require(table_index_ipairs_module), nil
return index_ipairs
end
local index_pairs
local function get_index_pairs()
index_pairs, get_index_pairs = require(table_index_pairs_module), nil
return index_pairs
end
return function(t1, t2, setter)
-- get_unprotected_metatable() returns nil if there's no metamethod or false
-- if it's protected. A protected metatable will cause an error to be thrown
-- once setmetatable() is called anyway, but this way avoids the problem of
-- getmetatable() returning arbitrary values from a __metatable metamethod.
local mt = get_unprotected_metatable(t1)
-- Use rawset() if `t1` already has a metatable, as metamethods are accessed
-- with rawget() when invoked.
if mt then
rawset(mt, "__index", t2)
rawset(mt, "__ipairs", index_ipairs or get_index_ipairs())
rawset(mt, "__pairs", index_pairs or get_index_pairs())
if setter then
rawset(mt, "__newindex", t2)
end
else
mt = {
__index = t2,
__ipairs = index_ipairs or get_index_ipairs(),
__pairs = index_pairs or get_index_pairs(),
}
if setter then
mt.__newindex = t2
end
end
setmetatable(t1, mt)
return t1
end
48v0hyjeqk1dtojaswx8l471lw9lk9m
Module:table/size
828
8124
54999
2026-09-30T17:52:40Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local next = next local pairs = pairs --[==[ This returns the size of a key/value pair table. If `raw` is set, then metamethods will be ignored, giving the true table size. For arrays, it is faster to use `export.length`.]==] return function(t, raw) local i, iter, state, init = 0 if raw then iter, state, init = next, t, nil else iter, state, init = pairs(t) end for _ in iter, state, init do i = i + 1 end return i end'
54999
Scribunto
text/plain
local next = next
local pairs = pairs
--[==[
This returns the size of a key/value pair table. If `raw` is set, then metamethods will be ignored, giving the true table size.
For arrays, it is faster to use `export.length`.]==]
return function(t, raw)
local i, iter, state, init = 0
if raw then
iter, state, init = next, t, nil
else
iter, state, init = pairs(t)
end
for _ in iter, state, init do
i = i + 1
end
return i
end
cngitqlralebfbp34hnb48xd0vu0xxk
Module:table/slice
828
8125
55000
2026-09-30T17:53:01Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local index_module = "Module:index" local table_length_module = "Module:table/length" local require = require local function index_range(...) index_range = require(index_module).range return index_range(...) end local function table_len(...) table_len = require(table_length_module) return table_len(...) end --[==[ Given a list, returns a new list consisting of the items between the start index `i` and end index `j` (inclusive). `i` defaults to `1`, and `j...'
55000
Scribunto
text/plain
local index_module = "Module:index"
local table_length_module = "Module:table/length"
local require = require
local function index_range(...)
index_range = require(index_module).range
return index_range(...)
end
local function table_len(...)
table_len = require(table_length_module)
return table_len(...)
end
--[==[
Given a list, returns a new list consisting of the items between the start index `i` and end index `j` (inclusive). `i` defaults to `1`, and `j` defaults to the length of the input list.]==]
return function(t, i, j)
local slice, n = {}, 0
i, j = index_range(t, i, j, table_len)
while not (j and i > j) do
local v = t[i]
if v == nil then
return slice
end
n = n + 1
slice[n] = v
i = i + 1
end
return slice
end
51m7d23hu7mq9wf29dau6rffwgjl02o
Module:sparseConcat
828
8126
55001
2026-09-30T17:53:24Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_sparse_ipairs_module = "Module:table/sparseIpairs" local concat = table.concat local function sparse_ipairs(...) sparse_ipairs = require(table_sparse_ipairs_module) return sparse_ipairs(...) end --[==[ Concatenates all values in a table that are indexed by a number, in order. * {sparseConcat{ a, nil, c, d }} => {"acd"} * {sparseConcat{ nil, b, c, d }} => {"bcd"}]==] return function(t, sep, i, j) local list, k = {}, 0 for _, v in sparse_ipairs(t...'
55001
Scribunto
text/plain
local table_sparse_ipairs_module = "Module:table/sparseIpairs"
local concat = table.concat
local function sparse_ipairs(...)
sparse_ipairs = require(table_sparse_ipairs_module)
return sparse_ipairs(...)
end
--[==[
Concatenates all values in a table that are indexed by a number, in order.
* {sparseConcat{ a, nil, c, d }} => {"acd"}
* {sparseConcat{ nil, b, c, d }} => {"bcd"}]==]
return function(t, sep, i, j)
local list, k = {}, 0
for _, v in sparse_ipairs(t) do
k = k + 1
list[k] = v
end
return concat(list, sep, i, j)
end
bjv07xjo7s2ffvu9stl8xil2x52wjqt
Module:table/sparseConcat
828
8127
55002
2026-09-30T17:53:54Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_sparse_ipairs_module = "Module:table/sparseIpairs" local concat = table.concat local function sparse_ipairs(...) sparse_ipairs = require(table_sparse_ipairs_module) return sparse_ipairs(...) end --[==[ Concatenates all values in a table that are indexed by a number, in order. * {sparseConcat{ a, nil, c, d }} => {"acd"} * {sparseConcat{ nil, b, c, d }} => {"bcd"}]==] return function(t, sep, i, j) local list, k = {}, 0 for _, v in sparse_ipairs(t...'
55002
Scribunto
text/plain
local table_sparse_ipairs_module = "Module:table/sparseIpairs"
local concat = table.concat
local function sparse_ipairs(...)
sparse_ipairs = require(table_sparse_ipairs_module)
return sparse_ipairs(...)
end
--[==[
Concatenates all values in a table that are indexed by a number, in order.
* {sparseConcat{ a, nil, c, d }} => {"acd"}
* {sparseConcat{ nil, b, c, d }} => {"bcd"}]==]
return function(t, sep, i, j)
local list, k = {}, 0
for _, v in sparse_ipairs(t) do
k = k + 1
list[k] = v
end
return concat(list, sep, i, j)
end
bjv07xjo7s2ffvu9stl8xil2x52wjqt
Module:table/sparseIpairs
828
8128
55003
2026-09-30T17:54:10Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_num_keys_module = "Module:table/numKeys" local function num_keys(...) num_keys = require(table_num_keys_module) return num_keys(...) end --[==[ An iterator which works like `ipairs`, but which works for sparse arrays.]==] return function(t) local keys, i = num_keys(t), 0 return function() i = i + 1 local k = keys[i] if k ~= nil then return k, t[k] end end end'
55003
Scribunto
text/plain
local table_num_keys_module = "Module:table/numKeys"
local function num_keys(...)
num_keys = require(table_num_keys_module)
return num_keys(...)
end
--[==[
An iterator which works like `ipairs`, but which works for sparse arrays.]==]
return function(t)
local keys, i = num_keys(t), 0
return function()
i = i + 1
local k = keys[i]
if k ~= nil then
return k, t[k]
end
end
end
awvh1kj2qsubuy2anwvpmp6t7xxz299
beds
0
8129
55014
2026-09-30T18:44:31Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|en}}== ===Rihtstefn=== IPA:/bɛdz/ ===Nama=== {{head|en|noun form}} #''manigfeald of'' '''[[bed]]''''
55014
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
IPA:/bɛdz/
===Nama===
{{head|en|noun form}}
#''manigfeald of'' '''[[bed]]'''
2cvkcjq6kbjdh2q2xt3lonsa4bldncs
girl
0
8130
55015
2026-09-30T18:53:56Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|en}}== ===Rihtstefn=== * IPA: /ˈɡɜːl/ ===Wordstǣr=== Of {{inh|en|enm|gerle}}, wēninga of {{inh|en|ang|*gyrele}}, of {{inh|en|gmw-pro|*gurilā}}.'
55015
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /ˈɡɜːl/
===Wordstǣr===
Of {{inh|en|enm|gerle}}, wēninga of {{inh|en|ang|*gyrele}}, of {{inh|en|gmw-pro|*gurilā}}.
ald9y7sk9w3rrv9682zu1i7n4yek8w1
55016
55015
2026-09-30T18:58:33Z
Deadend0914
7211
/* {{sprǣc|en}} */
55016
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /ˈɡɜːl/
===Wordstǣr===
Of {{inh|en|enm|gerle}}, wēninga of {{inh|en|ang|*gyrele}}, of {{inh|en|gmw-pro|*gurilā}}.
===Nama===
{{en-noun|~}}
# mægden
#* a [[seven-year-old]] girl
#*: seofonwintre mægden
eiesgupludu2j37b2pss7scxmgdkcu8
55019
55016
2026-09-30T19:14:42Z
Deadend0914
7211
/* Nama */
55019
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /ˈɡɜːl/
===Wordstǣr===
Of {{inh|en|enm|gerle}}, wēninga of {{inh|en|ang|*gyrele}}, of {{inh|en|gmw-pro|*gurilā}}.
===Nama===
{{en-noun|~}}
# [[mægden]]
#* a [[seven-year-old]] girl
#*: seofonwintre mægden
gt32m5etv5dn2by2jsdx03jfdyy2kg3
Bysen:etymon
10
8131
55020
2026-09-30T19:17:13Z
Deadend0914
7211
Gesceop tramet þe hafaþ '<includeonly>{{#invoke:etymon|main}}</includeonly><noinclude>{{documentation}}</noinclude>'
55020
wikitext
text/x-wiki
<includeonly>{{#invoke:etymon|main}}</includeonly><noinclude>{{documentation}}</noinclude>
5t3xwy26hk5cfvmzk7ny2oqnf3fvdz7
Module:etymon
828
8132
55021
2026-09-30T19:17:52Z
Deadend0914
7211
Gesceop tramet þe hafaþ '--[=[ This module implements the {{etymon}} template for structured etymology data on Wiktionary. It enables the creation of etymology trees and text by parsing etymon chains, scraping linked pages for their own {{etymon}} data, and recursively building a tree of derivational relationships. Authors: - Original implementation: [[User:Ioaxxere]] - Full refactor (September 2025): [[User:Fenakhay]] ([[Special:Diff/86717746]]) Modules: - [[Module:etymon]]: main mod...'
55021
Scribunto
text/plain
--[=[
This module implements the {{etymon}} template for structured etymology data on Wiktionary.
It enables the creation of etymology trees and text by parsing etymon chains,
scraping linked pages for their own {{etymon}} data, and recursively building a tree
of derivational relationships.
Authors:
- Original implementation: [[User:Ioaxxere]]
- Full refactor (September 2025): [[User:Fenakhay]] ([[Special:Diff/86717746]])
Modules:
- [[Module:etymon]]: main module handling parsing, validation, tree building, and page scraping
- [[Module:etymon/data]]: keyword definitions, configuration, and status constants
- [[Module:etymon/tree]]: etymology tree rendering
- [[Module:etymon/text]]: etymology text generation
- [[Module:etymon/categories]]: category generation logic
- [[Module:etymon/tracking]]: tracking
]=]
local export = {}
local __state = {
cached_etymon_args = {},
cached_etymon_pages = {},
cached_descendants_checks = {},
senseid_parent_etymon = {},
available_etymon_ids = {},
single_etymons = {},
entry_title = nil,
entry_lang_code = nil,
current_page_has_inline_etymology = false,
current_page_has_redundant_etymology = false,
used_idless_etymon = false,
toplevel_has_inline_etymology = false,
toplevel_redundant_etymology = false,
toplevel_idless_etymon = false,
has_mismatched_id = false,
linked_page_multiple_etymons_idless = false,
linked_page_partial_etymology_sections = false,
partial_etymology_targets = {},
skip_partial_etymology_category = false,
max_depth_reached = 0,
total_nodes = 0,
language_count = {},
toplevel_keyword_stats = {},
id_stats = nil,
warnings = {},
}
local function reset_invocation_state()
__state.current_page_has_inline_etymology = false
__state.current_page_has_redundant_etymology = false
__state.used_idless_etymon = false
__state.toplevel_has_inline_etymology = false
__state.toplevel_redundant_etymology = false
__state.toplevel_idless_etymon = false
__state.has_mismatched_id = false
__state.linked_page_multiple_etymons_idless = false
__state.linked_page_partial_etymology_sections = false
__state.max_depth_reached = 0
__state.total_nodes = 0
__state.language_count = {}
__state.toplevel_keyword_stats = {}
__state.warnings = {}
end
local M = require("Module:module loader").init({
require = {
data = "Module:etymon/data",
tree = "Module:etymon/tree",
text = "Module:etymon/text",
categories = "Module:etymon/categories",
tracking = "Module:etymon/tracking",
descendants = "Module:etymon/descendants",
affix = "Module:affix",
anchors = "Module:anchors",
etydate = "Module:etydate",
etymology = "Module:etymology",
families = "Module:families",
languages = "Module:languages",
languages_errorgetby = "Module:languages/errorGetBy",
links = "Module:links",
pages = "Module:pages",
parameters = "Module:parameters",
string_utilities = "Module:string utilities",
template_parser = "Module:template parser",
utilities = "Module:utilities",
debug = "Module:debug",
en_utilities = "Module:en-utilities",
parameter_utilities = "Module:parameter utilities",
parse_utilities = "Module:parse utilities",
template_styles = "Module:TemplateStyles",
script_utilities = "Module:script utilities",
JSON = "Module:JSON",
yesno = "Module:yesno",
},
loadData = {
headword_data = "Module:headword/data",
parameters_data = "Module:parameters/data",
text_allowed = "Module:etymon/data/text_allowed",
},
})
local Util = {}
function Util.format_error(message, preview_only)
if preview_only and not M.pages.is_preview() then
return nil
end
return '<span class="error">' .. message .. '</span>'
end
function Util.add_warning(message, preview_only)
local formatted = Util.format_error(message, preview_only)
if formatted then
table.insert(__state.warnings, formatted)
end
end
function Util.is_text_param_allowed_for_lang(lang)
if not lang or type(lang) ~= "table" then
return false
end
local types = lang.getTypes and lang:getTypes()
if types and types.family then
local code = lang.getCode and lang:getCode()
return code and M.text_allowed.families[code] == true
end
local full_code = lang.getFullCode and lang:getFullCode()
if full_code and M.text_allowed.langs[full_code] then
return true
end
if lang.inFamily then
for family_code in pairs(M.text_allowed.families) do
if lang:inFamily(family_code) then
return true
end
end
end
return false
end
function Util.get_lang(code, no_error)
if no_error then
return M.languages.getByCode(code, nil, true)
end
return M.languages.getByCode(code, nil, true) or M.languages_errorgetby.code(code, true, true)
end
-- Match a term language against a text=:lang stop target (supports etymology-only codes).
function Util.lang_matches_stop_code(term_lang, stop_code)
if not term_lang or not stop_code or stop_code == "" then
return false
end
local stop_lang = Util.get_lang(stop_code, true)
if not stop_lang then
return false
end
if term_lang:getCode() == stop_lang:getCode() then
return true
end
if stop_lang:getFullCode() == stop_lang:getCode() then
return term_lang:getFullCode() == stop_lang:getCode()
end
return false
end
function Util.get_family(code)
return M.families.getByCode(code)
end
function Util.get_lang_exception(lang)
-- Families have no language-specific exceptions
if lang.getTypes and lang:getTypes().family then
return nil
end
local code = lang:getCode()
local lang_exceptions = M.data.config.lang_exceptions
if lang_exceptions[code] then
return lang_exceptions[code]
end
for norm_code, exc in pairs(lang_exceptions) do
if exc.normalize_to and code == exc.normalize_to then
return exc
end
if exc.normalize_from_families then
local should_normalize = false
for _, family in ipairs(exc.normalize_from_families) do
if lang:inFamily(family) then
should_normalize = true
break
end
end
if should_normalize and exc.normalize_exclude_families then
for _, family in ipairs(exc.normalize_exclude_families) do
if lang:inFamily(family) then
should_normalize = false
break
end
end
end
if should_normalize then
local ret = {}
for k, v in pairs(exc) do
ret[k] = v
end
ret.suppress_tr = nil
return ret
end
end
end
return nil
end
function Util.get_norm_lang(lang)
local exc = Util.get_lang_exception(lang)
if exc and exc.normalize_to then
return M.languages.getByCode(exc.normalize_to)
end
return lang
end
function Util.resolve_context_lang(lang, node_args)
if type(node_args) ~= "table" then return lang end
if node_args.status == M.data.STATUS.INLINE then return lang end
if not (lang.hasType and lang:hasType("etymology-only")) then return lang end
local full = lang.getFull and lang:getFull()
if not full or full:getCode() == lang:getCode() then return lang end
if full.hasAncestor and full:hasAncestor(lang) then return lang end
return full
end
-- Add default values for boolean modifiers (e.g., <unc> becomes <unc:1>)
-- This is needed because Module:parse utilities expects boolean modifiers to have explicit values
function Util.add_boolean_defaults(str, param_mods)
local result = str
for name, spec in pairs(param_mods) do
if spec.type == "boolean" then
-- Replace <name> with <name:1> (but not <name:...> which already has a value)
result = result:gsub("<" .. name .. ">", "<" .. name .. ":1>")
end
end
return result
end
local REQUEST_TEMPLATE_PARAM_MODS = {
rfe = M.parameter_utilities.construct_param_mods {
{ param = { "sort", "y", "m", "fragment", "section" } },
{ param = { "nocat", "box", "noes" }, type = "boolean" },
},
etystub = M.parameter_utilities.construct_param_mods {
{ param = "sort" },
{ param = { "nocat", "nocap", "nodot" }, type = "boolean" },
},
}
function Util.expand_request_template(frame, template_name, param_value, lang_code)
local param_mods = REQUEST_TEMPLATE_PARAM_MODS[template_name]
local with_defaults = Util.add_boolean_defaults(param_value, param_mods)
local parsed = M.parse_utilities.parse_inline_modifiers(with_defaults, {
param_mods = param_mods,
generate_obj = function(text)
if M.yesno(text, false) then
return { is_boolean = true }
end
return { text = text }
end,
})
local template_args = { [1] = lang_code }
for name in pairs(param_mods) do
template_args[name] = parsed[name]
end
if not parsed.is_boolean then
template_args[2] = parsed.text
end
return " " .. frame:expandTemplate({
title = template_name,
args = template_args,
})
end
-- Centralized term formatting: handles suppress_term (-), unknown_term (empty/+), and regular terms
function Util.format_term(term, is_toplevel, opts)
opts = opts or {}
-- suppress_term (-) returns nil
if term.suppress_term then
return nil
end
local lang = term.lang
local exc = Util.get_lang_exception(lang)
if is_toplevel then
local display_text = term.alt or term.title or ""
local sc = term.sc or lang:findBestScript(display_text)
local bold_text = tostring(mw.html.create("strong")
:addClass("selflink")
:wikitext(display_text))
return M.script_utilities.tag_text(bold_text, lang, sc, "term")
end
local link_params = { lang = lang }
link_params.term = not term.unknown_term and term.title or nil
link_params.alt = term.alt
link_params.id = (not term.unknown_term and term.id and term.id ~= "") and term.id or nil
if not (exc and exc.suppress_tr) then
link_params.tr = term.tr
link_params.ts = term.ts
else
link_params.suppress_tr = true
end
link_params.lit = (opts.lit ~= "suppress") and term.lit or nil
if opts.gloss ~= "suppress" then
link_params.gloss = term.gloss
end
link_params.genders = term.genders
if opts.pos ~= "suppress" then
link_params.pos = term.pos
link_params.ng = term.ng
link_params.infl = term.infl
end
if exc and exc.suppress_tr then
link_params.lit = nil
end
if opts.tree_ql ~= "suppress" then
if term.q then
link_params.q = term.q
end
if term.qq then
link_params.qq = term.qq
end
if term.l then
link_params.l = term.l
end
if term.ll then
link_params.ll = term.ll
end
link_params.show_decorations = term.q or term.qq or term.l or term.ll
end
return M.links.full_link(link_params, "term")
end
local __is_content_page_cached
function Util.is_content_page()
if __is_content_page_cached == nil then
__is_content_page_cached = M.pages.is_content_page(mw.title.getCurrentTitle())
end
return __is_content_page_cached
end
local __page_data_cached
function Util.get_page_data()
if not __page_data_cached then
__page_data_cached = M.headword_data.page
end
return __page_data_cached
end
-- Extract base keyword from param (without modifiers)
local function get_keyword_base(param)
if type(param) ~= "string" then return nil end
local base = param:match("^:?([^<]+)") or param:gsub("^:", "")
return base
end
local function is_keyword(param, allow_colon_less)
if type(param) ~= "string" then return false end
local keywords = M.data.keywords
if param:sub(1, 1) == ":" then
local base = get_keyword_base(param)
return keywords[base] ~= nil
end
if allow_colon_less then
local base = get_keyword_base(param)
return keywords[base] ~= nil
end
return false
end
local function get_keyword(param, allow_colon_less)
if type(param) ~= "string" then return nil end
local keywords = M.data.keywords
if param:sub(1, 1) == ":" then
return get_keyword_base(param)
end
if allow_colon_less then
local base = get_keyword_base(param)
if keywords[base] then
return base
end
end
return nil
end
local function normalize_keyword(keyword)
if keyword:sub(1, 1) == ":" then
return keyword
end
return ":" .. keyword
end
-- Resolve keyword (possibly an alias) to its canonical form. Used only at input boundaries
local function get_canonical_keyword(keyword)
if not keyword then return keyword end
return M.data.keyword_canonical[keyword] or keyword
end
local function is_affix_group_keyword(keyword)
local config = keyword and M.data.keywords[keyword]
return config and config.affix_categories or false
end
local function reject_removed_surf_keyword(param)
local base = get_keyword_base(param)
if base == "surf" then
error("The `:surf` keyword has been removed. Use `<surf>` on a formation keyword instead (e.g. `:af<surf>`, `:bor<surf>`).")
end
end
local function copy_keyword_info(source)
local copy = {}
for k, v in pairs(source) do
copy[k] = v
end
return copy
end
local function lowercase_glossary_display(text)
return text:gsub("(%[%[Appendix:Glossary#[^|]+|)([^%]])([^%]]*)%]%]", function(prefix, first, rest)
return prefix .. mw.ustring.lower(first) .. rest .. "]]"
end)
end
local function surf_should_keep_formation_phrase(base)
if not base.phrase then
return false
end
if base.glossary then
return true
end
return not (base.phrase == "from" and (base.text == "From" or base.text == "from"))
end
-- Runtime overrides when <surf> is present on a keyword.
local function get_effective_keyword_info(keyword, modifiers)
local base = M.data.keywords[keyword]
if not base or not modifiers or not modifiers.surf then
return base
end
local effective = copy_keyword_info(base)
local surf_text = "By [[Appendix:Glossary#surface_analysis|surface analysis]],"
local surf_phrase = "by surface analysis,"
effective.new_sentence = true
effective.invisible = "tree"
if surf_should_keep_formation_phrase(base) then
effective.phrase = surf_phrase .. " " .. base.phrase
if base.text then
effective.text = surf_text .. " " .. lowercase_glossary_display(base.text)
else
effective.text = surf_text .. " " .. base.phrase
end
else
effective.text = surf_text
effective.phrase = surf_phrase
end
return effective
end
-- Build text/phrase for nominalization with <g:code> (uses data module for codes only).
local function get_nominalization_label_for_g(code)
if not code or code == "" then return nil end
local codes = M.data.nominalization_g_codes
local adj = codes[code]
if not adj and #code == 2 then
local gender_adj = codes[code:sub(1, 1)]
local number_adj = codes[code:sub(2, 2)]
if gender_adj and number_adj then
adj = gender_adj .. " " .. number_adj
end
end
if not adj then return nil end
local text = adj:gsub("^%l", function(c) return string.upper(c) end) .. " [[Appendix:Glossary#nominalization|nominalization]] of"
local phrase = M.en_utilities.add_indefinite_article(adj .. " [[Appendix:Glossary#nominalization|nominalization]] of", false)
return { text = text, phrase = phrase }
end
local EtymonParser = {}
-- Keyword modifier definitions
EtymonParser.keyword_param_mods = M.parameter_utilities.construct_param_mods {
{ group = "ref" },
{ param = "conj" }, -- conjunction for alternatives: "and", "or", "and/or", etc.
{ param = { "unc", "surf" }, type = "boolean" },
{ param = "text", restrict = { keywords = { "from", "derived" } } },
{ param = "lit", restrict = { affix_group = true } },
{ param = "g", restrict = { keywords = { "nominalization" } } },
{ param = "senseid", restrict = { keywords = { "semantic loan" } } },
}
-- Term modifier definitions
EtymonParser.etymon_param_mods = M.parameter_utilities.construct_param_mods {
{group = {"link", "q", "l", "ref", "infl"}, exclude = {"sc"}},
{param = {"ety", "postype"}},
{param = "unc", type = "boolean"},
{param = "aftype", restrict = {affix_group = true}},
{param = {"bor", "slbor", "lbor"}, type = "boolean", restrict = {affix_group = true}},
}
local function get_clean_param_mods(param_mods)
local clean = {}
for mod_name, mod_def in pairs(param_mods) do
clean[mod_name] = {}
for key, value in pairs(mod_def) do
if key ~= "restrict" then
clean[mod_name][key] = value
end
end
end
return clean
end
function EtymonParser.check_modifier_restrictions(modifiers, current_keyword, param_mods)
for mod_name, mod_value in pairs(modifiers) do
-- Only check restrictions if the modifier has a non-false/nil value
if mod_value then
local mod_def = param_mods[mod_name]
if mod_def and mod_def.restrict then
if mod_def.restrict.affix_group then
if not is_affix_group_keyword(current_keyword) then
local mod_display = mod_value == true and "<" .. mod_name .. ">" or "<" .. mod_name .. ":" .. tostring(mod_value) .. ">"
error("The modifier `" .. mod_display .. "` is only allowed for affix-group keywords (e.g. `:af`, `:blend`, `:univ`).")
end
elseif mod_def.restrict.keywords then
local allowed_keywords = mod_def.restrict.keywords
local is_allowed = false
for _, allowed_keyword in ipairs(allowed_keywords) do
if current_keyword == allowed_keyword then
is_allowed = true
break
end
end
if not is_allowed then
local keyword_list = {}
for _, kw in ipairs(allowed_keywords) do
table.insert(keyword_list, ":" .. kw)
end
local keyword_str = table.concat(keyword_list, #keyword_list == 2 and " or " or ", ")
if #keyword_list > 2 then
-- Replace last comma with "or"
keyword_str = keyword_str:gsub(", ([^,]+)$", " or %1")
end
local mod_display = mod_value == true and "<" .. mod_name .. ">" or "<" .. mod_name .. ":" .. tostring(mod_value) .. ">"
error("The modifier `" .. mod_display .. "` is only allowed for the keyword" .. (#keyword_list > 1 and "s " or " ") .. keyword_str .. ".")
end
end
end
end
end
end
local TERM_RULE_DISALLOW = {
suppress = { field = "suppress_term", label = "suppressed" },
unknown = { field = "unknown_term", label = "unknown" },
family = { field = "is_family", label = "family" },
}
function EtymonParser.check_etymon_limits(count, limits, label, opts)
if not limits then
return
end
opts = opts or {}
local min_etymons = limits.min_etymons
if min_etymons == nil and not opts.skip_default_min then
min_etymons = 1
end
if min_etymons and count < min_etymons then
if min_etymons > 1 then
error("Detected " .. label .. " group with fewer than " .. min_etymons .. " etymons.")
else
error("Detected " .. label .. " with no etymons.")
end
end
if limits.max_etymons and count > limits.max_etymons then
local unit = (limits.max_etymons == 1) and "etymon" or "etymons"
error("Detected " .. label .. " with more than " .. limits.max_etymons .. " " .. unit .. ".")
end
end
function EtymonParser.check_term_rules(etymon_data, entry_lang, rules, label)
label = label or "term"
if rules and rules.disallow then
local disallowed = {}
for _, typ in ipairs(rules.disallow) do
local spec = TERM_RULE_DISALLOW[typ]
if spec and etymon_data[spec.field] then
table.insert(disallowed, spec.label)
end
end
if #disallowed > 0 then
error(label .. " does not support " ..
mw.text.listToText(disallowed, "or") .. " etymons.")
end
end
if etymon_data.is_family then
if rules and rules.family == "disallowed" then
error(label .. " does not support family codes" .. (rules.family_suffix or "."))
elseif not etymon_data.suppress_term then
error("Family codes require suppressed term (use family:-).")
end
end
if rules then
if rules.require_term and (not etymon_data.term or etymon_data.term == "") then
error(label .. " requires a term for each listed form.")
end
if rules.entry_lang then
if Util.get_norm_lang(etymon_data.lang):getFullCode() ~=
Util.get_norm_lang(entry_lang):getFullCode() then
error(label .. " terms must be in the entry language (" ..
entry_lang:getFullCode() .. "), got '" .. etymon_data.lang:getFullCode() .. "'.")
end
end
if rules.ancestor_check then
M.etymology.check_ancestor(entry_lang, etymon_data.lang)
end
elseif etymon_data.is_family and not etymon_data.suppress_term then
error("Family codes require suppressed term (use family:-).")
end
end
function EtymonParser.check_keyword_term(etymon_data, entry_lang, keyword)
local config = M.data.keywords[keyword]
EtymonParser.check_term_rules(etymon_data, entry_lang, config and config.term_rules, "`:" .. keyword .. "`")
end
function EtymonParser.check_supplement_term(etymon_data, entry_lang, supplement_type)
local config = M.data.supplements[supplement_type]
EtymonParser.check_term_rules(etymon_data, entry_lang, config and config.term_rules, "|" .. supplement_type .. "=")
end
-- Parse keyword with modifiers (e.g., ":bor<unc>" or ":bor<ref:{{R:example}}>")
function EtymonParser.parse_keyword_modifiers(param)
if type(param) ~= "string" then return nil, {} end
local base_keyword = get_keyword_base(param)
if not base_keyword then return nil, {} end
local canonical_keyword = get_canonical_keyword(base_keyword)
-- Check if there are any modifiers
if not param:find("<", 1, true) then
return canonical_keyword, {}
end
-- Parse modifiers using the same mechanism as etymon parsing
local rest_with_defaults = Util.add_boolean_defaults(param, EtymonParser.keyword_param_mods)
local function generate_obj(ignored)
return {}
end
local parsed = M.parse_utilities.parse_inline_modifiers(rest_with_defaults:gsub("^:?[^<]+", ""),
{ param_mods = get_clean_param_mods(EtymonParser.keyword_param_mods), generate_obj = generate_obj })
local modifiers = {
unc = parsed.unc or false,
refs = parsed.refs,
text = parsed.text,
lit = parsed.lit,
conj = parsed.conj,
g = parsed.g,
surf = parsed.surf or false,
senseid = parsed.senseid,
}
-- Validate modifiers against restrictions
EtymonParser.check_modifier_restrictions(modifiers, canonical_keyword, EtymonParser.keyword_param_mods)
return canonical_keyword, modifiers
end
local function normalize_keyword_param(keyword_with_mods)
local trimmed = M.string_utilities.trim(keyword_with_mods)
reject_removed_surf_keyword(trimmed:match("^:") and trimmed or (":" .. trimmed))
local base = get_keyword_base(trimmed)
if not base or not M.data.keywords[base] then
error("Invalid keyword '" .. trimmed .. "' in inline etymology")
end
local canonical_base = get_canonical_keyword(base)
local without_colon = trimmed:gsub("^:", "")
local mods_part = without_colon:sub(#base + 1)
local kw_param = normalize_keyword(canonical_base .. mods_part)
EtymonParser.parse_keyword_modifiers(kw_param)
return kw_param
end
local function get_keyword_mod_names()
local names = {}
for mod_name in pairs(EtymonParser.keyword_param_mods) do
names[mod_name] = true
end
return names
end
local function parse_inline_ety_run(ety_string)
local body = ety_string or ""
if body == "" then
error("Empty inline etymology")
end
local keyword_mod_names = get_keyword_mod_names()
local pos = 1
local len = #body
local function parse_err(msg)
error(msg .. " in inline etymology: '" .. body .. "'")
end
local function peek_double()
return body:sub(pos, pos + 1) == "<<"
end
local function mod_name_from_unwrapped(unwrapped)
return unwrapped:match("^<([^:>]+)")
end
local function is_keyword_mod(unwrapped)
local name = mod_name_from_unwrapped(unwrapped)
return name and keyword_mod_names[name] or false
end
local function read_double_bracket()
if not peek_double() then
return nil
end
local start = pos
pos = pos + 2
while pos <= len - 1 do
if body:sub(pos, pos + 1) == ">>" then
local token = body:sub(start, pos + 1)
pos = pos + 2
return token, token:sub(2, -2)
end
pos = pos + 1
end
parse_err("Unmatched <<")
end
local function read_angle_cell()
if body:sub(pos, pos) ~= "<" or peek_double() then
return nil
end
local open = pos
pos = pos + 1
local depth = 1
local i = pos
while i <= len do
local ch = body:sub(i, i)
if ch == "<" then
depth = depth + 1
elseif ch == ">" then
depth = depth - 1
if depth == 0 then
local inner = body:sub(open + 1, i - 1)
pos = i + 1
return inner
end
end
i = i + 1
end
parse_err("Unmatched <")
end
local function read_bare_run()
local start = pos
while pos <= len and body:sub(pos, pos) ~= "<" do
pos = pos + 1
end
return body:sub(start, pos - 1)
end
local function absorb_double_keyword_mods(keyword_str)
while peek_double() do
local saved = pos
local _, unwrapped = read_double_bracket()
if is_keyword_mod(unwrapped) then
keyword_str = keyword_str .. unwrapped
else
pos = saved
break
end
end
return keyword_str
end
local kw_start = pos
while pos <= len and body:sub(pos, pos) ~= "<" do
pos = pos + 1
end
local keyword = body:sub(kw_start, pos - 1)
if keyword:match("^%s*$") then
parse_err("Missing keyword")
end
keyword = absorb_double_keyword_mods(keyword)
local cells = {}
while pos <= len do
if peek_double() then
local _, unwrapped = read_double_bracket()
if is_keyword_mod(unwrapped) then
parse_err("Unexpected keyword modifier " .. unwrapped .. " outside of a keyword")
end
table.insert(cells, "+" .. unwrapped)
elseif body:sub(pos, pos) == "<" then
local inner = read_angle_cell()
if inner ~= "" then
table.insert(cells, inner)
end
else
local bare = read_bare_run()
if bare ~= "" then
if bare:sub(1, 1) ~= ":" then
parse_err("Unexpected bare text '" .. bare .. "' (use :keyword for nested keywords in inline etymology)")
end
if not is_keyword(bare, true) then
parse_err("Invalid keyword '" .. bare .. "' in inline etymology")
end
table.insert(cells, absorb_double_keyword_mods(bare))
end
end
end
return {
keyword = keyword,
cells = cells,
}
end
function EtymonParser.inline_ety_to_pipe(ety_string)
local run = parse_inline_ety_run(ety_string)
if not run.keyword or run.keyword:match("^%s*$") then
return "|"
end
local pipe_parts = { normalize_keyword_param(M.string_utilities.trim(run.keyword)) }
for _, segment in ipairs(run.cells) do
if is_keyword(segment, true) then
table.insert(pipe_parts, normalize_keyword_param(segment))
else
table.insert(pipe_parts, segment)
end
end
return "|" .. table.concat(pipe_parts, "|") .. "|"
end
function EtymonParser.pipe_to_inline_ety(pipe_string)
local cells = {}
for cell in pipe_string:gmatch("([^|]+)") do
if cell ~= "" then
table.insert(cells, cell)
end
end
if #cells == 0 then
return ""
end
local inline_parts = {}
for index, cell in ipairs(cells) do
local base = get_keyword_base(cell)
if base and M.data.keywords[base] then
local without_colon = cell:gsub("^:", "")
local kw_base, mods = without_colon:match("^([^<]+)(.*)$")
local inline_kw = (kw_base or without_colon) .. (mods or ""):gsub("<([^>]+)>", "<<%1>>")
if index > 1 then
inline_kw = ":" .. inline_kw
end
table.insert(inline_parts, inline_kw)
elseif cell:sub(1, 1) == "+" then
local mod = cell:sub(2)
if mod:match("^<.->$") then
mod = mod:sub(2, -2)
end
table.insert(inline_parts, "<<" .. mod .. ">>")
else
table.insert(inline_parts, "<" .. cell .. ">")
end
end
return table.concat(inline_parts, "")
end
function EtymonParser.parse_inline_ety(ety_string, context_lang)
local run = parse_inline_ety_run(ety_string)
local keyword = M.string_utilities.trim(run.keyword)
reject_removed_surf_keyword(":" .. keyword)
if not is_keyword(keyword, true) then
error("Invalid keyword '" .. keyword .. "' in inline etymology <ety:" .. keyword .. "...>")
end
local args = { context_lang:getCode(), normalize_keyword_param(keyword) }
for _, segment in ipairs(run.cells) do
if is_keyword(segment, true) then
table.insert(args, normalize_keyword_param(segment))
else
table.insert(args, segment)
end
end
return args
end
function EtymonParser.parse_etymon(param, context_lang)
if is_keyword(param) then
return nil
end
if type(param) ~= "string" then
return nil
end
local lang, rest
local is_family = false
local before_bracket = param:match("^([^<]*)") or param
local lang_code, rest_match = before_bracket:match("^([a-zA-Z][a-zA-Z0-9._-]*):(.*)$")
if lang_code then
local potential_lang = Util.get_lang(lang_code, true)
if potential_lang then
lang = potential_lang
rest = param:sub(#lang_code + 2)
else
local potential_family = Util.get_family(lang_code)
if potential_family then
lang = potential_family
rest = param:sub(#lang_code + 2)
is_family = true
else
lang = context_lang
rest = param
end
end
else
lang = context_lang
rest = param
end
M.tracking.track_term(rest)
if rest == "" or rest == "+" then
return {
lang = lang,
term = nil,
unknown_term = true,
is_family = is_family,
}
end
if rest == "-" then
return {
lang = lang,
term = nil,
suppress_term = true,
is_family = is_family,
}
end
if not rest:find("<", 1, true) then
return {
lang = lang,
term = M.string_utilities.trim(rest),
is_family = is_family,
}
end
local term_text = rest:match("^([^<]*)") or ""
local is_unknown = (term_text == "" or term_text == "+")
local is_suppress = (term_text == "-")
local function generate_obj(ignored_term)
return { term = (is_unknown or is_suppress) and nil or M.string_utilities.trim(term_text) }
end
local rest_with_defaults = Util.add_boolean_defaults(rest, EtymonParser.etymon_param_mods)
local parsed_obj = M.parse_utilities.parse_inline_modifiers(rest_with_defaults,
{ param_mods = get_clean_param_mods(EtymonParser.etymon_param_mods), generate_obj = generate_obj })
if parsed_obj.id and parsed_obj.id:match("^!") then
parsed_obj.id = parsed_obj.id:sub(2)
parsed_obj.override = true
end
parsed_obj.lang = lang
parsed_obj.is_family = is_family
if is_unknown then
parsed_obj.unknown_term = true
elseif is_suppress then
parsed_obj.suppress_term = true
end
return parsed_obj
end
function EtymonParser.validate(lang, args, id, title, pos, starts_with_lang_code)
-- id is now optional, so only validate if provided
if id then
if mw.ustring.len(id) < 2 then
error("The `id` parameter must have at least two characters.")
end
if id == title or id == Util.get_page_data().pagename then
error("The `id` parameter must not be the same as the page title.")
end
end
local valid_pos = { prefix = true, suffix = true, interfix = true, infix = true, root = true, word = true }
if pos and not valid_pos[pos] then
error("Unknown value provided for `pos`. Valid values: " .. table.concat(require("Module:table").keysToList(valid_pos), ", ") .. ".")
end
local current_keyword = "from"
local current_keyword_explicit = false
local keyword_etymons = {}
local keywords = M.data.keywords
local function checkKeyword()
local config = keywords[current_keyword]
if current_keyword == "from" and not current_keyword_explicit and #keyword_etymons == 0 then
keyword_etymons = {}
return
end
EtymonParser.check_etymon_limits(#keyword_etymons, config, "`:" .. current_keyword .. "`")
keyword_etymons = {}
end
local start_index = starts_with_lang_code and 2 or 1
for i = start_index, #args do
local param = args[i]
if type(param) ~= "string" then
elseif param:sub(1, 1) == ":" and not is_keyword(param) then
reject_removed_surf_keyword(param)
error("Invalid keyword '" .. param .. "'. Did you mean a valid keyword like ':bor', ':inh', etc.?")
elseif is_keyword(param) then
checkKeyword()
current_keyword = get_canonical_keyword(get_keyword(param))
current_keyword_explicit = true
else
local etymon_data = EtymonParser.parse_etymon(param, lang)
if etymon_data then
table.insert(keyword_etymons, param)
EtymonParser.check_keyword_term(etymon_data, lang, current_keyword)
-- Check modifier restrictions
EtymonParser.check_modifier_restrictions(etymon_data, current_keyword, EtymonParser.etymon_param_mods)
-- postype must be "root" or "word"
local VALID_POSTYPES = { root = true, word = true }
if etymon_data.postype and not VALID_POSTYPES[etymon_data.postype] then
error("Invalid <postype:" .. etymon_data.postype .. ">; must be \"root\" or \"word\".")
end
if etymon_data.ety then
local inline_args = EtymonParser.parse_inline_ety(etymon_data.ety, etymon_data.lang)
EtymonParser.validate(etymon_data.lang, inline_args, nil, nil, nil, true)
end
else
table.insert(keyword_etymons, param)
end
end
end
checkKeyword()
end
local DataRetriever = {}
local function format_etymon_id_hint(id_data, idx)
local id = type(id_data) == "table" and id_data.id or id_data
local pos = type(id_data) == "table" and id_data.pos
if id and id ~= "" and id ~= "*" then
return '"' .. id .. '"'
end
if pos and pos ~= "" then
return "unnamed (|pos=" .. pos .. "|)"
end
return "etymon #" .. idx .. " (no |id= on page)"
end
local function etymon_target_page_link(page, norm_lang)
return M.links.full_link({
term = page,
lang = norm_lang,
no_generate_alternants = true,
}, "term")
end
-- Summarize {{etymon}} id slots on a linked page for preview warnings.
local function summarize_available_etymon_ids(ids)
local id_list = {}
local all_idless = true
local target_has_idless = false
local any_pos = false
for i, id_data in ipairs(ids) do
local id = type(id_data) == "table" and id_data.id or id_data
local pos = type(id_data) == "table" and id_data.pos
if id and id ~= "" and id ~= "*" then
all_idless = false
else
target_has_idless = true
end
if pos and pos ~= "" then
any_pos = true
end
table.insert(id_list, format_etymon_id_hint(id_data, i))
end
return {
id_list = id_list,
all_idless = all_idless,
target_has_idless = target_has_idless,
any_pos = any_pos,
count = #ids,
options_text = mw.text.listToText(id_list),
}
end
local function ambiguous_etymon_suggestion(page_link, summary)
if summary.all_idless then
if summary.any_pos then
return " None set `|id=` yet; add a unique `|id=` to each on " .. page_link
.. ", then `<id:identifier>` after the term here. Section order / hints: "
.. summary.options_text .. "."
end
return " None set `|id=` yet; add a unique `|id=` to each {{etymon}} in that section from top to bottom, then `<id:identifier>` after the term here (same value as `|id=`)."
end
return " Specify which one with `<id:identifier>` after the term. Options: " .. summary.options_text .. "."
end
local function warn_ambiguous_etymon_link(page, norm_lang, ids, is_toplevel)
local page_link = etymon_target_page_link(page, norm_lang)
local summary = summarize_available_etymon_ids(ids)
if is_toplevel and summary.target_has_idless then
__state.linked_page_multiple_etymons_idless = true
end
local lang_name = norm_lang:getCanonicalName()
local lead = "Etymology link to " .. page_link .. " is ambiguous (" .. summary.count
.. " {{etymon}} templates for " .. lang_name .. ")."
Util.add_warning(lead .. ambiguous_etymon_suggestion(page_link, summary), true)
end
local function is_mismatched_explicit_id(base_key, cached_args, parent_etymon)
return cached_args == M.data.STATUS.MISSING and not parent_etymon
and #(__state.available_etymon_ids[base_key] or {}) > 0
end
local function maybe_flag_partial_etymology_reference(base_key, etymon_data, cached_args, is_toplevel)
if not is_toplevel or __state.skip_partial_etymology_category then
return
end
if not __state.partial_etymology_targets[base_key] then
return
end
if etymon_data.id and type(cached_args) == "table" then
return
end
__state.linked_page_partial_etymology_sections = true
end
local function is_nonlemma_etymon_template(template_args)
return template_args and M.yesno(template_args.nl, false)
end
local function warn_mismatched_explicit_id(page, norm_lang, base_key, etymon_id)
local page_link = etymon_target_page_link(page, norm_lang)
local summary = summarize_available_etymon_ids(__state.available_etymon_ids[base_key] or {})
local lang_name = norm_lang:getCanonicalName()
local lead = "Etymology link to " .. page_link .. " uses `<id:" .. etymon_id
.. ">`, but no {{etymon}} on that page has `|id=" .. etymon_id .. "|` for " .. lang_name .. "."
Util.add_warning(lead .. " Valid IDs: " .. summary.options_text .. ".", true)
end
-- Given an etymon data, scrape its page and cache the result in the global state object.
function DataRetriever.cache_page_etymons(etymon_page, etymon_title, key, etymon_lang, etymon_id, redirected_from, descendants_is_toplevel)
local content = etymon_title:getContent()
if not content then
__state.cached_etymon_args[key] = M.data.STATUS.REDLINK
return
end
-- Check if the linked page is a redirect. If it is, the template parsing
-- code below will be effectively skipped, and `scrape_page` will be called
-- again on the redirect target (see the bottom of this function)
local lang_section_for_descendants = nil
local redirect_target = etymon_title.redirect_target
if not redirect_target then
content = M.pages.get_section(content, etymon_lang:getFullName(), 2)
if not content then
__state.cached_etymon_args[key] = M.data.STATUS.MISSING
return
end
lang_section_for_descendants = content
end
local etymon_lang_code = etymon_lang:getFullCode()
local lang_page_key = etymon_lang_code .. ":" .. etymon_page
local found_templates_for_lang = {}
local found_ids = {}
local get_node_class = M.template_parser.class_else_type
-- Look for all {{etymon}} templates within the page content using the template parser
-- This way the same page is never parsed more than once
-- Build a map from senseids to their parent etymonids.
local active_etymon_args = nil
local etymology_section_count = 0
local etymology_sections_with_etymon = 0
local current_etymology_has_etymon = false
local current_etymology_has_nonlemma = false
local function finalize_current_etymology_section()
if etymology_section_count == 0 then
return
end
if current_etymology_has_etymon or current_etymology_has_nonlemma then
etymology_sections_with_etymon = etymology_sections_with_etymon + 1
end
current_etymology_has_etymon = false
current_etymology_has_nonlemma = false
end
for node in M.template_parser.parse(content):iterate_nodes() do
local node_class = get_node_class(node)
if node_class == "heading" then
-- A new L2 or etymology section acts as a barrier: an {{etymon}} usage
-- used previously cannot be the parent of any subsequent senseids.
-- Note that we don't have to check for L2s due to the usage of `M.pages.get_section` above.
if node:get_name():find("^Etymology") then
finalize_current_etymology_section()
etymology_section_count = etymology_section_count + 1
active_etymon_args = nil
end
elseif node_class == "template" then
local template_name = node:get_name()
if template_name == "etymon" then
local template_args = node:get_arguments()
-- Check if this etymon is for our language
if template_args[1] == etymon_lang_code then
if is_nonlemma_etymon_template(template_args) then
if etymology_section_count > 0 then
current_etymology_has_nonlemma = true
end
else
if etymology_section_count > 0 then
current_etymology_has_etymon = true
end
table.insert(found_templates_for_lang, template_args)
if template_args.id then
local etymon_key = lang_page_key .. ":" .. template_args.id
__state.cached_etymon_args[etymon_key] = template_args
__state.cached_etymon_pages[etymon_key] = tostring(etymon_page)
table.insert(found_ids, template_args.id)
active_etymon_args = template_args
else
-- Store idless etymon with default key
local etymon_key = lang_page_key .. ":*"
__state.cached_etymon_args[etymon_key] = template_args
__state.cached_etymon_pages[etymon_key] = tostring(etymon_page)
table.insert(found_ids, "*")
active_etymon_args = template_args
end
end
end
elseif active_etymon_args and template_name == "senseid" then
local template_args = node:get_arguments()
-- This should always be true for proper usages of {{senseid}}.
if template_args[1] == etymon_lang_code and template_args[2] then
local sense_id_key = lang_page_key .. ":" .. template_args[2]
__state.senseid_parent_etymon[sense_id_key] = active_etymon_args
__state.cached_etymon_pages[sense_id_key] = tostring(etymon_page)
end
end
end
end
finalize_current_etymology_section()
if lang_section_for_descendants
and etymology_section_count > 1
and etymology_sections_with_etymon > 0
and etymology_sections_with_etymon < etymology_section_count
then
__state.partial_etymology_targets[lang_page_key] = true
end
if descendants_is_toplevel and lang_section_for_descendants and #found_templates_for_lang > 0 then
M.descendants.cache_page_checks({
lang_section = lang_section_for_descendants,
etymon_lang_code = etymon_lang_code,
found_templates_for_lang = found_templates_for_lang,
entry_title = __state.entry_title,
entry_lang_code = __state.entry_lang_code,
entry_lang = __state.entry_lang_code and Util.get_lang(__state.entry_lang_code, true) or nil,
cached_descendants_checks = __state.cached_descendants_checks,
lang_page_key = lang_page_key,
redirected_from = redirected_from,
})
end
local id_data_list = {}
for _, args in ipairs(found_templates_for_lang) do
local id = args.id or "*"
table.insert(id_data_list, { id = id, pos = args.pos })
end
__state.available_etymon_ids[lang_page_key] = id_data_list
if #found_templates_for_lang == 1 then
__state.single_etymons[lang_page_key] = found_templates_for_lang[1]
end
if redirected_from and __state.available_etymon_ids[lang_page_key] then
__state.available_etymon_ids[redirected_from] = __state.available_etymon_ids[redirected_from] or {}
for _, id_data in ipairs(__state.available_etymon_ids[lang_page_key]) do
table.insert(__state.available_etymon_ids[redirected_from], id_data)
end
end
if __state.cached_etymon_args[key] ~= nil or __state.senseid_parent_etymon[key] ~= nil then
-- All done!
return
elseif redirect_target and not redirected_from then
-- Try scraping the redirect.
etymon_page = redirect_target.prefixedText
DataRetriever.cache_page_etymons(etymon_page, redirect_target, lang_page_key .. ":" .. etymon_id, etymon_lang, etymon_id, lang_page_key, descendants_is_toplevel)
__state.cached_etymon_args[key] = __state.cached_etymon_args[etymon_lang_code .. ":" .. etymon_page .. ":" .. etymon_id]
else
__state.cached_etymon_args[key] = M.data.STATUS.MISSING
end
end
local function has_linkable_term(etymon_data)
if etymon_data.is_family or etymon_data.suppress_term or etymon_data.unknown_term then
return false
end
local term = etymon_data.term
if term == nil or term == "" then
return false
end
return M.string_utilities.trim(term) ~= ""
end
local function map_affix_part(etymon_data)
if etymon_data.alt or not has_linkable_term(etymon_data) or etymon_data.term:find("^%^") then
return
end
local affix_type = etymon_data.aftype and (M.data.aftype_aliases[etymon_data.aftype] or etymon_data.aftype) or nil
local _, link_term, display_term = M.affix.parse_term_for_affixes(etymon_data.term, etymon_data.lang, nil,
affix_type, true, nil, etymon_data.id)
if link_term ~= display_term then
etymon_data.term = link_term
etymon_data.alt = display_term
end
end
local function record_term_id_tracking(etymon_data)
if not has_linkable_term(etymon_data) then
return
end
local term_page = M.links.get_link_page(etymon_data.term, etymon_data.lang)
M.tracking.record_term_id_usage(__state.id_stats, etymon_data, term_page)
end
-- Given an etymon object, scrape its page (if necessary) and return its own etymon arguments as well as the page name.
function DataRetriever.get_etymon_args(etymon_data, is_toplevel)
if not has_linkable_term(etymon_data) then
return M.data.STATUS.MISSING, nil, nil, nil
end
local page = M.links.get_link_page(etymon_data.term, etymon_data.lang)
local norm_lang = Util.get_norm_lang(etymon_data.lang)
local base_key = norm_lang:getFullCode() .. ":" .. page
if etymon_data.id then
local key = base_key .. ":" .. etymon_data.id
local cached_args = __state.cached_etymon_args[key] or __state.senseid_parent_etymon[key]
if cached_args == nil then
local title = mw.title.new(page)
if not title then error('Invalid page title "' .. page .. '" encountered.') end
DataRetriever.cache_page_etymons(page, title, key, norm_lang, etymon_data.id, nil, is_toplevel)
end
cached_args = __state.cached_etymon_args[key] or __state.senseid_parent_etymon[key] -- refresh
-- Get etymon_id from parent if this was resolved via senseid
local parent_etymon = __state.senseid_parent_etymon[key]
local resolved_etymon_id = parent_etymon and parent_etymon.id
local descendants_check = M.descendants.get_lookup_check({
cached_descendants_checks = __state.cached_descendants_checks,
is_toplevel = is_toplevel,
base_key = base_key,
lookup = {
explicit_id = etymon_data.id,
parent_etymon = parent_etymon,
},
})
if is_toplevel and descendants_check == nil then
local title = mw.title.new(page)
if title then
DataRetriever.cache_page_etymons(page, title, key, norm_lang, etymon_data.id, nil, true)
descendants_check = M.descendants.get_lookup_check({
cached_descendants_checks = __state.cached_descendants_checks,
is_toplevel = true,
base_key = base_key,
lookup = {
explicit_id = etymon_data.id,
parent_etymon = parent_etymon,
},
})
end
end
local mismatched_id = is_mismatched_explicit_id(base_key, cached_args, parent_etymon)
if mismatched_id and is_toplevel then
__state.has_mismatched_id = true
M.tracking.record_mismatched_id_usage(__state.id_stats, norm_lang, page, etymon_data.id)
warn_mismatched_explicit_id(page, norm_lang, base_key, etymon_data.id)
end
maybe_flag_partial_etymology_reference(base_key, etymon_data, cached_args, is_toplevel)
return cached_args, __state.cached_etymon_pages[key], resolved_etymon_id, descendants_check
else
__state.used_idless_etymon = true
if is_toplevel then
__state.toplevel_idless_etymon = true
end
if __state.available_etymon_ids[base_key] == nil then
local title = mw.title.new(page)
if not title then error('Invalid page title "' .. page .. '" encountered.') end
DataRetriever.cache_page_etymons(page, title, base_key .. ":*", norm_lang, "*", nil, is_toplevel)
end
local ids = __state.available_etymon_ids[base_key] or {}
local count = #ids
-- Try to filter by postype if available and we have multiple candidates
if count > 1 and etymon_data.postype then
local matching_ids = {}
for _, id_data in ipairs(ids) do
if id_data.pos == etymon_data.postype then
table.insert(matching_ids, id_data)
end
end
if #matching_ids == 1 then
local matched_id = matching_ids[1].id
local matched_key = base_key .. ":" .. matched_id
M.tracking.record_idless_resolution(__state.id_stats, norm_lang, page, "postype")
local descendants_check = M.descendants.get_lookup_check({
cached_descendants_checks = __state.cached_descendants_checks,
is_toplevel = is_toplevel,
base_key = base_key,
lookup = { id = matched_id },
})
if is_toplevel and descendants_check == nil then
local title = mw.title.new(page)
if title then
DataRetriever.cache_page_etymons(page, title, base_key .. ":*", norm_lang, "*", nil, true)
descendants_check = M.descendants.get_lookup_check({
cached_descendants_checks = __state.cached_descendants_checks,
is_toplevel = true,
base_key = base_key,
lookup = { id = matched_id },
})
end
end
local matched_args = __state.cached_etymon_args[matched_key]
maybe_flag_partial_etymology_reference(base_key, etymon_data, matched_args, is_toplevel)
return matched_args, __state.cached_etymon_pages[matched_key], nil, descendants_check
end
end
if count == 1 then
local only_id_data = ids[1]
local only_id = (type(only_id_data) == "table" and only_id_data.id) or only_id_data or "*"
M.tracking.record_idless_resolution(__state.id_stats, norm_lang, page, "single")
local descendants_check = M.descendants.get_lookup_check({
cached_descendants_checks = __state.cached_descendants_checks,
is_toplevel = is_toplevel,
base_key = base_key,
lookup = { id_data = only_id_data },
})
if is_toplevel and descendants_check == nil then
local title = mw.title.new(page)
if title then
DataRetriever.cache_page_etymons(page, title, base_key .. ":*", norm_lang, "*", nil, true)
descendants_check = M.descendants.get_lookup_check({
cached_descendants_checks = __state.cached_descendants_checks,
is_toplevel = true,
base_key = base_key,
lookup = { id_data = only_id_data },
})
end
end
local single_args = __state.single_etymons[base_key]
maybe_flag_partial_etymology_reference(base_key, etymon_data, single_args, is_toplevel)
return single_args, __state.cached_etymon_pages[base_key .. ":" .. only_id], nil, descendants_check
elseif count > 1 then
M.tracking.record_idless_resolution(__state.id_stats, norm_lang, page, "ambiguous")
warn_ambiguous_etymon_link(page, norm_lang, ids, is_toplevel)
maybe_flag_partial_etymology_reference(base_key, etymon_data, M.data.STATUS.AMBIGUOUS, is_toplevel)
return M.data.STATUS.AMBIGUOUS, nil, nil, nil
else
M.tracking.record_idless_resolution(__state.id_stats, norm_lang, page, "missing")
maybe_flag_partial_etymology_reference(base_key, etymon_data, M.data.STATUS.MISSING, is_toplevel)
return M.data.STATUS.MISSING, nil, nil, nil
end
end
end
local function keyword_invisible_in_tree(keyword_info)
if not keyword_info then
return false
end
local inv = keyword_info.invisible
return inv == "all" or inv == true or inv == "tree"
end
-- True when the node has at least one top-level child container visible in the tree.
local function node_has_visible_tree_children(node)
for _, container in ipairs(node.children or {}) do
if not keyword_invisible_in_tree(container.keyword_info) then
return true
end
end
return false
end
-- Count visible term nodes in the tree.
local function get_visible_tree_depth(node, skip_child_rendering)
local max_depth = 1
if skip_child_rendering or not node then
return max_depth
end
for _, container in ipairs(node.children or {}) do
local keyword_info = container.keyword_info
if not keyword_invisible_in_tree(keyword_info) then
local skip_grandchildren = keyword_info and keyword_info.no_child_categories
for _, term in ipairs(container.terms or {}) do
if term.is_duplicate then
if term.original_has_children then
max_depth = math.max(max_depth, 2)
end
else
max_depth = math.max(max_depth, 1 + get_visible_tree_depth(term, skip_grandchildren))
end
end
end
end
return max_depth
end
local function as_param_list(val)
if val == nil then
return {}
end
if type(val) == "table" then
return val
end
if type(val) == "string" and val ~= "" then
return { val }
end
return {}
end
local TreeBuilder = {}
-- Build a unique key for deduplication in the seen table
function TreeBuilder.build_key(lang, title, args)
local norm_lang_code = Util.get_norm_lang(lang):getFullCode()
local is_table = type(args) == "table"
local id = (is_table and args.id) or ""
if title then
return norm_lang_code .. ":" .. M.links.get_link_page(title, lang) .. ":" .. id
end
if is_table and args.status == M.data.STATUS.INLINE then
local content_parts = {}
for i = 1, #args do
content_parts[i] = tostring(args[i])
end
return norm_lang_code .. ":*:" .. id .. "\0" .. table.concat(content_parts, "\0")
end
return norm_lang_code .. ":*:" .. id
end
-- Copy parsed etymon modifiers onto a tree/supplement term node.
function TreeBuilder.apply_etymon_fields(term, etymon_data)
term.id = etymon_data.id
term.gloss = etymon_data.gloss
term.tr = etymon_data.tr
term.ts = etymon_data.ts
term.alt = etymon_data.alt
term.genders = etymon_data.genders
term.pos = etymon_data.pos
term.ng = etymon_data.ng
term.infl = etymon_data.infl
term.refs = etymon_data.refs
term.is_uncertain = etymon_data.unc
term.lit = etymon_data.lit
term.q = etymon_data.q
term.qq = etymon_data.qq
term.l = etymon_data.l
term.ll = etymon_data.ll
term.suppress_term = etymon_data.suppress_term
term.unknown_term = etymon_data.unknown_term
term.is_family = etymon_data.is_family
term.override = etymon_data.override
term.aftype = etymon_data.aftype
term.postype = etymon_data.postype
term.bor = etymon_data.bor
term.lbor = etymon_data.lbor
term.slbor = etymon_data.slbor
end
function TreeBuilder.build_supplement_term(etymon_data, entry_lang, supplement_type)
EtymonParser.check_supplement_term(etymon_data, entry_lang, supplement_type)
local term = {
lang = etymon_data.lang,
title = etymon_data.term,
children = {},
status = M.data.STATUS.OK,
}
TreeBuilder.apply_etymon_fields(term, etymon_data)
return term
end
function TreeBuilder.build_supplement_terms(entry_lang, supplement_type, param_value)
local terms = {}
for _, term_param in ipairs(as_param_list(param_value)) do
if type(term_param) == "string" and term_param ~= "" then
local etymon_data = EtymonParser.parse_etymon(term_param, entry_lang)
if etymon_data then
table.insert(terms, TreeBuilder.build_supplement_term(etymon_data, entry_lang, supplement_type))
end
end
end
return terms
end
-- Attach a |param= supplement defined in etymon_data.supplements (e.g. doublet=).
function TreeBuilder.append_term_supplement(data_tree, entry_lang, supplement_type, param_value)
local config = M.data.supplements[supplement_type]
if not config then
error("Unknown supplement '" .. tostring(supplement_type) .. "'.")
end
local terms = TreeBuilder.build_supplement_terms(entry_lang, supplement_type, param_value)
if #terms == 0 then
return
end
data_tree.supplements = data_tree.supplements or {}
table.insert(data_tree.supplements, {
type = supplement_type,
config = config,
terms = terms,
})
M.tracking.record_keyword_usage(__state.toplevel_keyword_stats, supplement_type, entry_lang, entry_lang, true)
end
function TreeBuilder.build(lang, title, args, seen, depth, stop_recursion)
seen = seen or {}
depth = depth or 0
local is_toplevel = (depth == 0)
if depth > __state.max_depth_reached then
__state.max_depth_reached = depth
end
__state.total_nodes = __state.total_nodes + 1
local lang_code = lang:getCode()
__state.language_count[lang_code] = (__state.language_count[lang_code] or 0) + 1
local current_id = (type(args) == "table" and args.id) or ""
local key = TreeBuilder.build_key(lang, title, args)
local node = { lang = lang, title = title, id = current_id, args = args, children = {}, status = M.data.STATUS.OK }
if type(args) ~= "table" or seen[key] then
node.status = args or M.data.STATUS.MISSING
-- Mark as duplicate if we've seen this node before
if seen[key] then
node.is_duplicate = true
node.duplicate_key = key
local original_node = seen[key]
if type(original_node) == "table" and original_node.children and #original_node.children > 0 then
node.original_has_children = true
end
end
return node
end
node.status = args.status or M.data.STATUS.OK
seen[key] = node
-- If stop_recursion is set, skip parsing children but check for visible children
if stop_recursion then
local keywords = M.data.keywords
local has_visible_children = false
for i = 2, #args do
local param = args[i]
if type(param) == "string" then
local keyword_base = get_keyword_base(param)
if keyword_base and keywords[keyword_base] then
local _, kw_modifiers = EtymonParser.parse_keyword_modifiers(param:sub(1, 1) == ":" and param or (":" .. param))
if not keyword_invisible_in_tree(get_effective_keyword_info(keyword_base, kw_modifiers)) then
has_visible_children = true
break
end
elseif param:sub(1, 1) ~= ":" then
-- It's a term (not a keyword), so there are visible children
has_visible_children = true
break
end
end
end
node.has_visible_children = has_visible_children
return node
end
-- Parse args into keyword containers
local current_keyword = "from"
local current_keyword_modifiers = {}
local current_container = nil
local function ensure_container()
if not current_container or current_container.keyword ~= current_keyword then
local keyword_info = get_effective_keyword_info(current_keyword, current_keyword_modifiers)
current_container = {
keyword = current_keyword,
keyword_info = keyword_info,
keyword_modifiers = current_keyword_modifiers,
terms = {},
}
table.insert(node.children, current_container)
-- Override keyword text/phrase for nominalization with <g:code>
if current_keyword_modifiers.g and current_keyword == "nominalization" then
local labels = get_nominalization_label_for_g(current_keyword_modifiers.g)
if not labels then
local codes = {}
for c in pairs(M.data.nominalization_g_codes) do table.insert(codes, c) end
table.sort(codes)
error("Invalid <g:" .. tostring(current_keyword_modifiers.g) .. ">. Supported codes for nominalization: " .. table.concat(codes, ", "))
end
current_container.keyword_info = copy_keyword_info(keyword_info)
current_container.keyword_info.text = labels.text
current_container.keyword_info.phrase = labels.phrase
end
end
return current_container
end
local parse_context_lang = Util.resolve_context_lang(lang, args)
for i = 2, #args do
local param = args[i]
if is_keyword(param) then
local keyword, modifiers = EtymonParser.parse_keyword_modifiers(param)
if not keyword then
error("Invalid keyword '" .. param .. "'.")
end
current_keyword = keyword
current_keyword_modifiers = modifiers
current_container = nil -- Force new container for new keyword
elseif type(param) == "string" and param:sub(1, 1) == ":" then
reject_removed_surf_keyword(param)
error("Invalid keyword '" .. param .. "'. Did you mean a valid keyword like ':bor', ':inh', etc.?")
elseif type(param) == "string" then
local etymon_data = EtymonParser.parse_etymon(param, parse_context_lang)
if etymon_data and is_affix_group_keyword(current_keyword) then
map_affix_part(etymon_data)
end
if etymon_data then
-- Track keyword usage at top level
M.tracking.record_keyword_usage(__state.toplevel_keyword_stats, current_keyword, lang, etymon_data.lang, is_toplevel)
local term_node = {}
local container
-- Handle suppress_term (-) and unknown_term (empty or +) directly
if etymon_data.suppress_term or etymon_data.unknown_term then
container = ensure_container()
if etymon_data.ety then
local inline_args = EtymonParser.parse_inline_ety(etymon_data.ety, etymon_data.lang)
inline_args.id = etymon_data.id
inline_args.status = M.data.STATUS.INLINE
term_node = TreeBuilder.build(etymon_data.lang, nil, inline_args, seen, depth + 1)
else
term_node = {
lang = etymon_data.lang,
children = {},
status = M.data.STATUS.OK,
}
end
TreeBuilder.apply_etymon_fields(term_node, etymon_data)
else
-- Regular term: fetch arguments from page
record_term_id_tracking(etymon_data)
local etymon_args, page_of, resolved_etymon_id, descendants_check =
DataRetriever.get_etymon_args(etymon_data, is_toplevel)
-- Check for <ety> inline parameter doesn't override the scraped arguments, unless the latter are missing
if etymon_data.ety then
if etymon_args == M.data.STATUS.REDLINK or etymon_args == M.data.STATUS.MISSING then
__state.current_page_has_inline_etymology = true
if is_toplevel then
__state.toplevel_has_inline_etymology = true
end
local inline_args = EtymonParser.parse_inline_ety(etymon_data.ety, etymon_data.lang)
-- Track inline ety keywords too
local inline_keyword = get_keyword(inline_args[2], true)
if inline_keyword and #inline_args >= 3 then
local inline_etymon = EtymonParser.parse_etymon(inline_args[3], etymon_data.lang)
if inline_etymon then
M.tracking.record_keyword_usage(__state.toplevel_keyword_stats, inline_keyword, etymon_data.lang, inline_etymon.lang, is_toplevel)
end
end
inline_args.id = etymon_data.id
inline_args.status = M.data.STATUS.INLINE
etymon_args = inline_args
term_node.page_of = __state.cached_etymon_pages[key] -- term node is on the same page as the parent
else
-- Scraped arguments exist, <ety> is redundant and ignored
__state.current_page_has_redundant_etymology = true
if is_toplevel then
__state.toplevel_redundant_etymology = true
end
end
end
-- Ensure container exists before checking keyword info
container = ensure_container()
-- Check if current keyword has no_child_categories - if so, stop recursion
local keyword_info = container.keyword_info
local should_stop_recursion = (stop_recursion or (keyword_info and keyword_info.no_child_categories))
term_node = TreeBuilder.build(etymon_data.lang, etymon_data.term, etymon_args, seen, depth + 1, should_stop_recursion)
term_node.target_key = Util.get_norm_lang(etymon_data.lang):getFullCode() ..
":" .. M.links.get_link_page(etymon_data.term, etymon_data.lang)
term_node.etymon_id = resolved_etymon_id -- The actual etymon id when resolved via senseid
term_node.page_of = page_of
TreeBuilder.apply_etymon_fields(term_node, etymon_data)
term_node.missing_descendants_header, term_node.missing_descendants_entry =
M.descendants.get_term_sync_flags(current_keyword, term_node.status, descendants_check)
end
table.insert(container.terms, term_node)
end
end
end
return node
end
-- Convert etymology tree to JSON-serializable table
local function tree_to_json(node)
local obj = {
term = node.title,
lang = node.lang:getCode(),
lang_name = node.lang:getCanonicalName(),
id = (node.id and node.id ~= "") and node.id or nil,
status = node.status,
is_uncertain = node.is_uncertain or nil,
is_duplicate = node.is_duplicate or nil,
gloss = node.gloss,
transliteration = node.tr,
transcription = node.ts,
alt = node.alt,
g = node.genders,
pos = node.pos,
ng = node.ng,
infl = node.infl,
children = {},
}
for _, container in ipairs(node.children or {}) do
local keyword_info = container.keyword_info
if keyword_info then
local container_obj = {
keyword = container.keyword,
keyword_label = keyword_info.text,
keyword_abbrev = keyword_info.abbrev,
is_group = keyword_info.is_group or nil,
is_invisible = keyword_info.invisible or nil,
is_uncertain = (container.keyword_modifiers and container.keyword_modifiers.unc) or nil,
terms = {},
}
for _, term in ipairs(container.terms or {}) do
table.insert(container_obj.terms, tree_to_json(term))
end
table.insert(obj.children, container_obj)
end
end
return obj
end
-- Build and return the etymology data tree for a given term.
function export.get_tree(lang, title, args, options)
options = options or {}
__state.entry_title = title
__state.entry_lang_code = lang:getCode()
__state.id_stats = M.tracking.new_id_stats()
__state.skip_partial_etymology_category = options.skip_partial_etymology_category == true
if options.validate then
EtymonParser.validate(lang, args, options.id, title, options.pos, false)
end
local lang_code = lang:getCode()
local start_index = (args[1] == lang_code) and 2 or 1
local tree_args = { [1] = lang_code, id = options.id or args.id }
for i = start_index, #args do
table.insert(tree_args, args[i])
end
__state.cached_etymon_args[lang_code .. ":" .. title .. ":" .. (tree_args.id or "")] = tree_args
local ety_data_tree = TreeBuilder.build(lang, title, tree_args)
if options.json then
return M.JSON.toJSON(tree_to_json(ety_data_tree))
end
return ety_data_tree
end
-- Given a language code, page name and optionally the id= parameter,
-- render the tree and only the etymology tree for the relevant page.
-- Fetches and parses the corresponding {{etymon}} from the requested page,
-- and any further pages needed to render the tree.
-- Parameters can be passed either through the #invoke or as
-- template parameters *through* an #invoke.
function export.render_tree_for_etymon_on_page(frame)
local frame_args = frame.args
local parent_args = frame:getParent().args
local langcode = frame_args[1] or parent_args[1]
local pagename = frame_args[2] or parent_args[2]
local id = frame_args["id"] or parent_args["id"]
local display_title = frame_args["title"] or parent_args["title"]
local parsed_title = mw.title.new(pagename, 0)
local title
if parsed_title.namespace == 0 then
title = M.pages.safe_page_name(parsed_title)
elseif parsed_title.namespace == 118 then
title = "*" .. M.pages.safe_page_name(parsed_title)
else
error("Unsupported namespace for render_tree_for_etymon_on_page: " .. parsed_title.namespace)
end
local lang = Util.get_lang(langcode)
__state.entry_title = title
__state.entry_lang_code = lang:getCode()
__state.id_stats = M.tracking.new_id_stats()
-- Construct etymon_data for DataRetriever.get_args.
local etymon_data = {
lang = lang,
term = title,
id = id
}
local args, pagename = DataRetriever.get_etymon_args(etymon_data, true)
if args == M.data.STATUS.MISSING then
error("The etymon template was not found (language " ..
langcode ..
", title '" ..
title ..
"'" ..
(id and ", ID '" .. id .. "'" or ", no ID given") .. "). Page contents may have changed in the interim.")
end
local tree_title = display_title or title
if lang:stripDiacritics(M.links.remove_links(tree_title)) ~= lang:stripDiacritics(M.links.remove_links(title)) then
M.tracking.track_title_pagename_mismatch(lang)
end
reset_invocation_state()
local ety_data_tree = export.get_tree(lang, tree_title, args, {
validate = true,
id = id,
})
local output = {}
table.insert(output, M.template_styles("Module:etymon/styles.css"))
table.insert(output, M.tree.render({
data_tree = ety_data_tree,
format_term_func = function(term, is_toplevel)
return Util.format_term(term, is_toplevel, {
gloss = "suppress",
pos = "suppress",
lit = "suppress",
tree_ql = "suppress",
})
end,
}))
return table.concat(output)
end
function export.main(frame)
local parent_args = frame:getParent().args
local args = M.parameters.process(parent_args, M.parameters_data.etymon)
local lang = args[1]
local etymon_args = args[2]
local id = args.id
local title = args.title
local text = args.text
local tree = args.tree
local etydate = args.etydate
local doublet = args.doublet
local rfe = args.rfe
local etystub = args.etystub
local is_nonlemma = M.yesno(args.nl, false)
local page_data = Util.get_page_data()
if not title then
title = page_data.pagename
if page_data.namespace == "Reconstruction" then title = "*" .. title end
end
local entry_pagename = page_data.pagename
if page_data.namespace == "Reconstruction" then
entry_pagename = "*" .. entry_pagename
end
if lang:stripDiacritics(M.links.remove_links(title)) ~= lang:stripDiacritics(M.links.remove_links(entry_pagename)) then
M.tracking.track_title_pagename_mismatch(lang)
end
local current_L2 = M.pages.get_current_L2()
if current_L2 then
local norm_lang = Util.get_norm_lang(lang)
local norm_name = norm_lang:getCanonicalName()
if current_L2 ~= norm_name then
local lang_desc = lang:getCode() .. " (" .. lang:getCanonicalName() .. ")"
if norm_lang:getCode() ~= lang:getCode() then
lang_desc = lang_desc .. ", normalized to " .. norm_lang:getCode() .. " (" .. norm_name .. ")"
end
error("Language '" .. lang_desc .. "' does not match the L2 header (" .. current_L2 .. ").")
end
end
reset_invocation_state()
local ety_data_tree = export.get_tree(lang, title, etymon_args, {
validate = true,
pos = args.pos,
id = id,
json = args.json,
skip_partial_etymology_category = is_nonlemma,
})
if args.json then
return ety_data_tree
end
local output = {}
local text_allowlist_mode = M.text_allowed.default_mode or "off"
if text and text_allowlist_mode ~= "off" and not Util.is_text_param_allowed_for_lang(lang) then
local msg = "Etymology texts (parameter <code>text=</code>) are not allowed for " .. lang:getFullName() ..
"; see [[Template:etymon#Text allowlist|Template:etymon § Text allowlist]] for the list of languages that may use the <code>text=</code> parameter."
if text_allowlist_mode == "error" then
error(msg)
else
Util.add_warning(msg, true)
end
end
local lang_exc = Util.get_lang_exception(lang)
if lang_exc and lang_exc.disallow then
local disallow = lang_exc.disallow
local error_text = " for " .. lang:getFullName()
if disallow.ref then
error_text = error_text .. "; see " .. disallow.ref
else
error_text = error_text .. "."
end
if tree and disallow.tree then
error("Etymology trees are not allowed" .. error_text)
end
if text and disallow.text then
error("Etymology texts are not allowed" .. error_text)
end
end
if etydate then
local etydate_param_mods = M.parameter_utilities.construct_param_mods {
{ group = "ref" },
{ param = "refn" },
{ param = "nocap", type = "boolean" },
}
local function generate_etydate_obj(etydate_text)
local etydate_specs = {}
for spec in etydate_text:gmatch("[^,]+") do
table.insert(etydate_specs, mw.text.trim(spec))
end
return { [1] = etydate_specs }
end
local parsed_etydate = M.parse_utilities.parse_inline_modifiers(etydate, { param_mods = etydate_param_mods, generate_obj = generate_etydate_obj })
local etydate_args = {
[1] = parsed_etydate[1],
nocap = parsed_etydate.nocap or false,
}
ety_data_tree.supplements = ety_data_tree.supplements or {}
table.insert(ety_data_tree.supplements, {
type = "etydate",
etydate_text = M.etydate.format_etydate(etydate_args, { omit_refs = true }),
etydate_refs = (parsed_etydate.refs and #parsed_etydate.refs > 0) and parsed_etydate.refs or nil,
})
end
TreeBuilder.append_term_supplement(ety_data_tree, lang, "doublet", doublet)
local has_visible_children = node_has_visible_tree_children(ety_data_tree)
-- Suppress trees for multiword entries and one-step chains
local visible_tree_depth = get_visible_tree_depth(ety_data_tree)
local is_trivial_tree = visible_tree_depth <= 2
local is_multiword = title:find("%s") ~= nil or title:find("_") ~= nil
if tree and (is_multiword or is_trivial_tree) then
tree = false
end
if tree then
table.insert(output, M.template_styles("Module:etymon/styles.css"))
table.insert(output, M.tree.render({
data_tree = ety_data_tree,
format_term_func = function(term, is_toplevel)
return Util.format_term(term, is_toplevel, {
gloss = "suppress",
pos = "suppress",
lit = "suppress",
tree_ql = "suppress",
})
end,
}))
end
local tree_disallowed = lang_exc and lang_exc.disallow and lang_exc.disallow.tree
local ety_tree_json = M.JSON.toJSON(tree_to_json(ety_data_tree))
local anchor = M.anchors.etymonid(lang, id, {
no_tree = args.notree,
title = title,
empty_tree = (not has_visible_children) or tree_disallowed,
ety_tree_json = ety_tree_json,
})
table.insert(output, anchor)
local text_stop_lang_missing = nil
if text then
local max_depth, stop_at_blue_link, stop_at_lang, stop_at_lang_or_bluelink
if text == "++" then
max_depth, stop_at_blue_link = false, false
elseif text == "+" then
max_depth, stop_at_blue_link = 1, false
elseif text == "*" then
max_depth, stop_at_blue_link = false, true
elseif text:match("^:[^*]+%*$") then
-- Stop at a specific language OR first bluelink after it, e.g., ":ota*"
-- If the target language is a redlink, continue to the first bluelink
local lang_code = text:match("^:([^*]+)%*$")
if lang_code and lang_code ~= "" then
local lang_obj = Util.get_lang(lang_code, true)
if lang_obj then
stop_at_lang_or_bluelink = lang_code
else
Util.add_warning('Invalid language code "' .. lang_code .. '" in text parameter. Showing full chain instead.')
max_depth, stop_at_blue_link = false, false
end
else
Util.add_warning('Empty language code in text parameter. Showing full chain instead.')
max_depth, stop_at_blue_link = false, false
end
elseif text:sub(1, 1) == ":" then
-- Stop at a specific language, e.g., ":ar" stops at first Arabic term
local lang_code = text:sub(2)
if lang_code ~= "" then
-- Validate the language code
local lang_obj = Util.get_lang(lang_code, true)
if lang_obj then
stop_at_lang = lang_code
else
Util.add_warning('Invalid language code "' .. lang_code .. '" in text parameter. Showing full chain instead.')
max_depth, stop_at_blue_link = false, false -- default to ++
end
else
Util.add_warning('Empty language code in text parameter. Showing full chain instead.')
max_depth, stop_at_blue_link = false, false -- default to ++
end
else
local num = tonumber(text)
if num and num >= 1 then
max_depth, stop_at_blue_link = num, false
else
error('Invalid text value "' ..
text .. '". Valid values are: "++" (full chain), "+" (first step only), "*" (until first blue link), a number (max steps), ":lang" (stop at language), or ":lang*" (stop at language or first bluelink if redlink)')
end
end
local text_output, text_render_meta = M.text.render({
data_tree = ety_data_tree,
format_term_func = Util.format_term,
lang_matches_stop_code = Util.lang_matches_stop_code,
max_depth = max_depth,
stop_at_blue_link = stop_at_blue_link,
curr_page = page_data.pagename,
nodot = args.nodot,
dot = args.dot,
stop_at_lang = stop_at_lang,
stop_at_lang_or_bluelink = stop_at_lang_or_bluelink,
})
table.insert(output, text_output)
if stop_at_lang and text_render_meta and not text_render_meta.stop_lang_reached then
M.tracking.track_text_stop_lang_missing(lang, stop_at_lang)
text_stop_lang_missing = stop_at_lang
end
end
if rfe then
table.insert(output, Util.expand_request_template(frame, "rfe", rfe, lang:getCode()))
end
if etystub then
table.insert(output, Util.expand_request_template(frame, "etystub", etystub, lang:getCode()))
end
if is_nonlemma then
table.insert(output, " " .. frame:expandTemplate({
title = "nonlemma",
args = {},
}))
end
local categories = {}
if Util.is_content_page() then
M.tracking.track_tree_metrics({
max_depth_reached = __state.max_depth_reached,
total_nodes = __state.total_nodes,
language_count = __state.language_count,
lang = lang,
})
categories = M.categories.build({
data_tree = ety_data_tree,
page_lang = lang,
available_etymon_ids = __state.available_etymon_ids,
senseid_parent_etymon = __state.senseid_parent_etymon,
get_norm_lang_func = Util.get_norm_lang,
lang_exc = lang_exc,
suppress_categories = lang_exc and lang_exc.suppress_categories,
nocat = args.nocat,
tree = tree,
text = text,
exnihilo = args.exnihilo,
toplevel_has_inline_etymology = __state.toplevel_has_inline_etymology,
toplevel_redundant_etymology = __state.toplevel_redundant_etymology,
toplevel_idless_etymon = __state.toplevel_idless_etymon,
has_mismatched_id = __state.has_mismatched_id,
linked_page_multiple_etymons_idless = __state.linked_page_multiple_etymons_idless,
linked_page_partial_etymology_sections = __state.linked_page_partial_etymology_sections,
text_stop_lang_missing = text_stop_lang_missing,
})
M.tracking.track_keywords(__state.toplevel_keyword_stats, lang)
M.tracking.track_page_id(lang, id)
M.tracking.track_ids(__state.id_stats, lang)
end
if #categories > 0 then
table.insert(output, M.categories.format(categories, lang))
end
if __state.warnings then
for i, warning in ipairs(__state.warnings) do
table.insert(output, (i == 1 and "\n" or "") .. warning .. "\n")
end
end
return table.concat(output)
end
return export
a9o2xk8i75wom7ia6inch310f4dka6m
Module:parameters/data
828
8133
55022
2026-09-30T19:18:20Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local list_to_set = require("Module:table").listToSet local alias_of_2 = {alias_of = 2} local boolean = {type = "boolean"} local boolean_default_false = {type = "boolean", default = false} local list = {list = true} local list_allow_holes_separate_no_index = {list = true, allow_holes = true, separate_no_index = true} local no_parameters = {} local required = {required = true} local required_default_ = {required = true, default = ""} local required_lang_default_u...'
55022
Scribunto
text/plain
local list_to_set = require("Module:table").listToSet
local alias_of_2 = {alias_of = 2}
local boolean = {type = "boolean"}
local boolean_default_false = {type = "boolean", default = false}
local list = {list = true}
local list_allow_holes_separate_no_index = {list = true, allow_holes = true, separate_no_index = true}
local no_parameters = {}
local required = {required = true}
local required_default_ = {required = true, default = ""}
local required_lang_default_und = {required = true, type = "language", default = "und"}
local type_labels = {type = "labels"}
local type_qualifier = {type = "qualifier"}
local type_references = {type = "references"}
local m = {}
-- [[Module:anchors]]
m["anchor"] = {
[1] = {required = true, list = true, disallow_holes = true},
}
m["senseid"] = {
[1] = required_lang_default_und,
[2] = required_default_,
id = alias_of_2,
tag = {set = list_to_set{"li", "p"}, default = "li"},
}
m["etymid"] = {
[1] = required_lang_default_und,
[2] = required_default_,
id = alias_of_2,
}
-- [[Module:etymon]]
m["etymon"] = {
[1] = required_lang_default_und,
[2] = {list = true, disallow_holes = true},
id = true,
title = true,
tree = boolean,
text = true,
exnihilo = boolean,
etydate = true,
doublet = {sublist = "comma without whitespace"},
rfe = true,
etystub = true,
nl = boolean,
pos = true,
notree = boolean_default_false,
nocat = boolean_default_false,
nodot = boolean_default_false,
dot = true,
json = boolean_default_false,
}
-- [[Module:transclude]]
m["transclude"] = {
[1] = {required = true, type = "language"},
[2] = {list = true, required = true},
id = true,
sort = true,
nogloss = boolean_default_false,
no_truncate_gloss = boolean,
include_place_extra_info = boolean,
place_translation_follows = boolean,
place_addl = true,
lb = true,
nolb = true,
nocat = boolean,
to = boolean,
t = list,
indent = true,
dot = boolean,
pagename = true,
}
-- [[Module:translations]]
m["translation"] = {
[1] = required_lang_default_und,
[2] = true,
[3] = list,
alt = true,
id = true,
sc = {type = "script"},
tr = true,
ts = true,
lit = true,
l = type_labels,
ll = type_labels,
q = type_qualifier,
qq = type_qualifier,
ref = type_references,
}
m["t-needed"] = {
[1] = required_lang_default_und,
[2] = {set = list_to_set{"usex", "quote"}},
nocat = boolean,
sort = true,
}
m["trans-top"] = {
[1] = true,
id = true,
["column-width"] = true,
}
m["trans-top-also"] = {
[1] = required,
[2] = list,
id = list_allow_holes_separate_no_index,
["column-width"] = true,
}
m["checktrans-top"] = {
[1] = true,
["column-width"] = true,
}
m["trans-bottom"] = no_parameters
m["trans-see"] = {
[1] = required,
[2] = list,
id = list_allow_holes_separate_no_index,
}
m["translation subpage"] = no_parameters
m["no equivalent translation"] = {
[1] = required_lang_default_und,
noend = boolean,
}
m["no attested translation"] = {
[1] = required_lang_default_und,
noend = boolean,
sort = true,
}
m["not used"] = {
[1] = required_lang_default_und,
[2] = true,
}
return m
5s1ntnu4nh24xfe4y9oqjeouq18285i
Module:languages/data/3/g
828
8134
55023
2026-09-30T19:18:46Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local m_langdata = require("Module:languages/data") -- Loaded on demand, as it may not be needed (depending on the data). local function u(...) u = require("Module:string utilities").char return u(...) end local c = m_langdata.chars local p = m_langdata.puaChars local s = m_langdata.shared local m = {} m["gaa"] = { "Ga", 33287, "alv-gda", "Latn", } m["gab"] = { "Gabri", 3441237, "cdc-est", "Latn", } m["gac"] = { "Mixed Great Andamanese", 5632963...'
55023
Scribunto
text/plain
local m_langdata = require("Module:languages/data")
-- Loaded on demand, as it may not be needed (depending on the data).
local function u(...)
u = require("Module:string utilities").char
return u(...)
end
local c = m_langdata.chars
local p = m_langdata.puaChars
local s = m_langdata.shared
local m = {}
m["gaa"] = {
"Ga",
33287,
"alv-gda",
"Latn",
}
m["gab"] = {
"Gabri",
3441237,
"cdc-est",
"Latn",
}
m["gac"] = {
"Mixed Great Andamanese",
56329630,
"qfa-adn",
"Latn",
}
m["gad"] = { -- not to be confused with gdk, gdg
"Gaddang",
3438830,
"phi",
"Latn",
}
m["gae"] = {
"Baniwa of Guainia", -- Warekena, distinguish from awd-war
1091095,
"awd-nwk",
"Latn",
}
m["gaf"] = {
"Gende",
3100425,
"ngf-gor",
"Latn",
}
m["gag"] = {
"Gagauz",
33457,
"trk-ogz",
"Latn, Cyrl",
ancestors = "trk-oat",
dotted_dotless_i = true,
sort_key = {
Latn = {
from = {
"i", -- Ensure "i" comes after "ı".
"ä", "ç", "ê", "ı", "ö", "ş", "ţ", "ü"
},
to = {
"i" .. p[1],
"a" .. p[1], "c" .. p[1], "e" .. p[1], "i", "o" .. p[1], "s" .. p[1], "t" .. p[1], "u" .. p[1]
}
},
},
}
m["gah"] = {
"Alekano",
3441595,
"ngf-gah",
"Latn",
}
m["gai"] = {
"Borei",
6799756,
"paa-ott",
"Latn",
}
m["gaj"] = {
"Gadsup",
5516467,
"ngf-gau",
"Latn",
}
m["gak"] = {
"Gamkonora",
5520226,
"paa-sah",
"Latn",
}
m["gal"] = {
"Galoli",
35322,
"poz-tim",
"Latn",
}
m["gam"] = {
"Kandawo",
6361369,
"ngf-jim",
"Latn",
}
m["gan"] = {
"Gan",
33475,
"zhx",
"Hants",
ancestors = "ltc",
generate_alternants = "zh-generatealternants",
translit = "zh-translit",
sort_key = "Hani-sortkey",
}
m["gao"] = {
"Gants",
5521529,
"ngf-eso",
"Latn",
}
m["gap"] = {
"Gal",
5517742,
"ngf-han",
"Latn",
}
m["gaq"] = {
"Gata'",
3501920,
"mun",
"Orya",
}
m["gar"] = {
"Galeya",
5518509,
"poz-ocw",
"Latn",
}
m["gas"] = {
"Adiwasi Garasia",
12953522,
"inc-bhi",
"Deva, Gujr",
ancestors = "bhb",
}
m["gat"] = {
"Kenati",
4219330,
"ngf-kgo",
"Latn",
}
m["gau"] = {
"Kondekor",
12952433,
"dra-pgd",
"Telu",
}
m["gaw"] = {
"Nobonob",
11732205,
"ngf-han",
"Latn",
}
m["gay"] = {
"Gayo",
33286,
"poz-nws",
"Latn",
}
m["gbb"] = {
"Kaytetye",
6380709,
"aus-rnd",
"Latn",
}
m["gbd"] = {
"Karadjeri",
3913837,
"aus-pam",
"Latn",
}
m["gbe"] = {
"Niksek",
56375,
"paa-sep",
"Latn",
}
m["gbf"] = {
"Gaikundi",
5517032,
"paa-nnd",
"Latn",
}
m["gbg"] = {
"Gbanziri",
35306,
"nic-nkg",
"Latn",
}
m["gbh"] = {
"Defi Gbe",
12952446,
"alv-gbe",
"Latn",
}
m["gbi"] = {
"Galela",
3094570,
"paa-gto",
"Latn",
}
m["gbj"] = {
"Bodo Gadaba",
3347070,
"mun",
"Orya",
}
m["gbk"] = {
"Gaddi",
17455500,
"him",
"Deva, Takr",
translit = {Deva = "hi-translit"},
}
m["gbl"] = {
"Gamit",
2731717,
"inc-bhi",
"Deva, Gujr",
}
m["gbm"] = {
"Garhwali",
33459,
"inc-pac",
"Deva",
translit = "hi-translit",
}
m["gbn"] = {
"Mo'da",
12755683,
"csu-bbk",
"Latn",
}
m["gbo"] = {
"Northern Grebo",
11157042,
"grb",
"Latn",
}
m["gbp"] = {
"Gbaya-Bossangoa",
11011295,
"gba-wes",
"Latn",
}
m["gbq"] = {
"Gbaya-Bozoum",
4952879,
"gba-wes",
"Latn",
}
m["gbr"] = {
"Gbagyi",
11015105,
"alv-ngb",
"Latn",
}
m["gbs"] = {
"Gbesi Gbe",
12952448,
"alv-pph",
"Latn",
}
m["gbu"] = {
"Gagadu",
35677,
"aus-arn",
"Latn",
}
m["gbv"] = {
"Gbanu",
3914945,
"gba-eas",
"Latn",
}
m["gbw"] = {
"Gabi",
5515391,
"aus-pam",
"Latn",
}
m["gbx"] = {
"Eastern Xwla Gbe",
18379975,
"alv-pph",
"Latn",
}
m["gby"] = {
"Gbari",
3915451,
"alv-ngb",
"Latn",
}
m["gcc"] = {
"Mali",
6743338,
"paa-bai",
"Latn",
}
m["gcd"] = {
"Ganggalida",
3913765,
"aus-tnk",
"Latn",
}
m["gce"] = {
"Galice",
20711,
"ath-pco",
"Latn",
}
m["gcf"] = {
"Antillean Creole",
3006280,
"crp",
"Latn",
ancestors = "fr",
sort_key = s["roa-oil-sortkey"],
}
m["gcl"] = {
"Grenadian Creole English",
4252500,
"crp",
"Latn",
ancestors = "en",
}
m["gcn"] = {
"Gaina",
11732195,
"ngf-gko",
"Latn",
}
m["gcr"] = {
"Guianese Creole",
1363072,
"crp",
"Latn",
ancestors = "fr",
sort_key = s["roa-oil-sortkey"],
}
m["gct"] = {
"Colonia Tovar German",
1138351,
"gmw-hgm",
"Latn",
ancestors = "gsw",
}
m["gdb"] = {
"Ollari",
33906,
"dra-pgd",
"Orya, Telu",
translit = {
Telu = "te-translit"
},
}
m["gdc"] = {
"Gugu Badhun",
10510360,
"aus-pam",
"Latn",
}
m["gdd"] = {
"Gedaged",
35292,
"poz-ocw",
"Latn",
}
m["gde"] = {
"Gude",
3441230,
"cdc-cbm",
"Latn",
}
m["gdf"] = {
"Guduf-Gava",
3441350,
"cdc-cbm",
"Latn",
}
m["gdg"] = { -- not to be confused with gad, gdk
"Ga'dang",
5515189,
"phi",
"Latn",
}
m["gdh"] = {
"Gadjerawang",
3913817,
"aus-jar",
"Latn",
}
m["gdi"] = {
"Gundi",
11137851,
"nic-nkb",
"Latn",
}
m["gdj"] = {
"Kurtjar",
5619931,
"aus-pmn",
"Latn",
}
m["gdk"] = { -- not to be confused with gad, gdg
"Gadang",
56256,
"cdc-est",
"Latn",
}
m["gdl"] = {
"Dirasha",
56809,
"cus-eas",
"Ethi",
}
m["gdm"] = {
"Laal",
33436,
"qfa-dis", -- Chad; unclassified, isolate or grouped with Adamawa or Chadic languages
"Latn",
}
m["gdn"] = {
"Umanakaina",
7881084,
"ngf-dag",
"Latn",
}
m["gdo"] = {
"Godoberi",
56515,
"cau-and",
"Cyrl",
display_text = {Cyrl = s["cau-Cyrl-displaytext"]},
strip_diacritics = {Cyrl = s["cau-Cyrl-stripdiacritics"]},
}
m["gdq"] = {
"Mehri",
13361,
"sem-sar",
"Arab, Latn",
}
m["gdr"] = {
"Wipi",
8026711,
"paa-etf",
"Latn",
}
m["gds"] = {
"Ghandruk Sign Language",
15971577,
"sgn",
}
m["gdt"] = {
"Kungardutyi",
6444517,
"aus-kar",
"Latn",
}
m["gdu"] = {
"Gudu",
3441172,
"cdc-cbm",
"Latn",
}
m["gdx"] = {
"Godwari",
3540922,
"raj",
"Deva",
}
m["gea"] = {
"Geruma",
3438789,
"cdc-wst",
"Latn",
}
m["geb"] = {
"Kire",
11129733,
"paa-rub",
"Latn",
}
m["gec"] = {
"Gboloo Grebo",
11019342,
"grb",
"Latn",
}
m["ged"] = {
"Gade",
3914459,
"alv-nup",
"Latn",
}
m["geg"] = {
"Gengle",
3438345,
"alv-mye",
"Latn",
ancestors = "kow",
}
m["geh"] = {
"Hutterisch",
33385,
"gmw-hgm",
"Latn",
ancestors = "bar",
}
m["gei"] = {
"Gebe",
3100032,
"poz-hce",
"Latn",
}
m["gej"] = {
"Gen",
33450,
"alv-gbe",
"Latn",
}
m["gek"] = {
"Gerka",
3441277,
"cdc-wst",
"Latn",
}
m["gel"] = {
"Fakkanci",
36627,
"nic-knn",
"Latn",
}
m["geq"] = {
"Geme",
3915851,
"znd",
"Latn",
}
m["ges"] = {
"Geser-Gorom",
5553579,
"poz-cma",
"Latn",
}
m["gev"] = {
"Viya",
7937974,
"bnt-tso",
"Latn",
}
m["gew"] = {
"Gera",
3438725,
"cdc-wst",
"Latn",
}
m["gex"] = {
"Garre",
56618,
"cus-som",
"Latn",
}
m["gey"] = {
"Enya",
5381452,
"bnt-mbe",
"Latn",
}
m["gez"] = {
"Ge'ez",
35667,
"sem-eth",
"Ethi",
translit = "Ethi-translit",
}
m["gfk"] = {
"Patpatar",
3368846,
"poz-ocw",
"Latn",
}
m["gft"] = {
"Gafat",
56910,
"sem-eth",
"Ethi, Latn",
}
m["gga"] = {
"Gao",
3095228,
"poz-ocw",
"Latn",
}
m["ggb"] = {
"Gbii",
3914390,
"kro-wkr",
"Latn",
}
m["ggd"] = {
"Gugadj",
5615186,
"aus-pmn",
"Latn",
}
m["gge"] = {
"Guragone",
5619801,
"aus-arn",
"Latn",
}
m["ggg"] = {
"Gurgula",
5620032,
"raj",
"Arab",
}
m["ggk"] = {
"Kungarakany",
6444516,
"aus-arn",
"Latn",
}
m["ggl"] = {
"Ganglau",
5521140,
"ngf-yag",
"Latn",
}
m["ggt"] = {
"Gitua",
3107865,
"poz-ocw",
"Latn",
}
m["ggu"] = {
"Gban",
3913317,
"dmn-nbe",
"Latn",
}
m["ggw"] = {
"Gogodala",
3512161,
"ngf-gsu",
"Latn",
}
m["gha"] = {
"Ghadames",
56747,
"ber",
"Latn", -- and other scripts?
}
m["ghc"] = {
"Classical Gaelic",
5128278,
"cel-gae",
"Latn, Latg",
ancestors = "mga",
}
m["ghe"] = {
"Southern Ghale",
12952453,
"sit-tam",
"Deva",
}
m["ghh"] = {
"Northern Ghale",
22662104,
"sit-tam",
"Deva",
}
m["ghk"] = {
"Geko Karen",
5530317,
"kar",
}
m["ghl"] = {
"Ghulfan",
16885737,
"nub-hil",
"Latn", -- and others?
}
m["ghn"] = {
"Ghanongga",
3104772,
"poz-ocw",
"Latn",
}
m["gho"] = {
"Ghomara",
35315,
"ber",
"Tfng, Latn",
translit = {Tfng = "Tfng-translit"},
}
m["ghr"] = {
"Ghera",
22808992,
"inc-hiw",
}
m["ghs"] = {
"Guhu-Samane",
11732219,
"ngf-gbi",
"Latn",
}
m["ght"] = {
"Kutang Ghale",
6448337,
"sit-tam",
"Tibt",
override_translit = true,
-- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["gia"] = {
"Kitja",
1284877,
"aus-jar",
"Latn",
}
m["gib"] = {
"Gibanawa",
12953530,
"crp",
"Latn",
ancestors = "ha",
}
m["gid"] = {
"Gidar",
35265,
"cdc-cbm",
"Latn",
}
m["gie"] = {
"Guébie",
63140714,
"kro-did",
"Latn",
}
m["gig"] = {
"Goaria",
33269,
"raj",
"Arab",
}
m["gih"] = {
"Githabul",
48987680,
"aus-pam",
"Latn",
}
m["gii"] = {
"Girirra",
5564288,
"cus-som",
}
m["gil"] = {
"Gilbertese",
30898,
"poz-mic",
"Latn",
}
m["gim"] = {
"Gimi (Papuan)",
11732209,
"ngf-fgi",
"Latn",
}
m["gin"] = {
"Hinukh",
33283,
"cau-wts",
"Cyrl",
translit = "gin-translit",
display_text = {Cyrl = s["cau-Cyrl-displaytext"]},
strip_diacritics = {Cyrl = s["cau-Cyrl-stripdiacritics"]},
}
m["gip"] = {
"Gimi (Austronesian)",
12952457,
"poz-ocw",
"Latn",
}
m["giq"] = {
"Green Gelao",
12953525,
"gio",
"Latn",
}
m["gir"] = {
"Red Gelao",
3100264,
"gio",
}
m["gis"] = {
"North Giziga",
3515084,
"cdc-cbm",
}
m["git"] = {
"Gitxsan",
3107862,
"nai-tsi",
"Latn",
}
m["giu"] = {
"Mulao",
11092831,
"gio",
}
m["giw"] = {
"White Gelao",
8843040,
"gio",
}
m["gix"] = {
"Gilima",
10977716,
"nic-nkm",
"Latn",
}
m["giy"] = {
"Giyug",
5565906,
}
m["giz"] = {
"South Giziga",
3502232,
"cdc-cbm",
}
m["gji"] = {
"Geji",
3914890,
"cdc-wst",
"Latn",
}
m["gjk"] = {
"Kachi Koli",
12953646,
"inc-wes",
}
m["gjm"] = {
"Gunditjmara",
6448731,
"aus-pam",
"Latn",
}
m["gjn"] = {
"Gonja",
35267,
"alv-gng",
"Latn",
}
m["gjr"] = {
"Gurindji Kriol",
5620091,
"qfa-mix",
"Latn",
ancestors = "gue, rop"
}
m["gju"] = {
"Gojri",
3241731,
"raj",
"Aran, Deva, Takr",
strip_diacritics = {
Aran = {
remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna .. c.smallv,
from = {"ڵ", "ݩ"},
to = {"ل", "ن"}
},
},
translit = {Aran = "ur-translit"},
}
m["gka"] = {
"Guya",
11732221,
"ngf-war",
"Latn",
}
m["gkd"] = {
"Magi",
55621742,
"ngf-ais",
"Latn",
}
m["gke"] = {
"Ndai",
6983667,
"alv-mbm",
}
m["gkn"] = {
"Gokana",
3075137,
"nic-ogo",
"Latn",
}
m["gko"] = {
"Kok-Nar",
6426526,
"aus-pmn",
"Latn",
}
m["gkp"] = {
"Guinea Kpelle",
11052867,
"dmn-msw",
"Latn, Kpel",
ancestors = "kpe",
}
m["glc"] = {
"Bon Gula",
289816,
"alv-bua",
}
m["gld"] = {
"Nanai",
13303,
"tuw-nan",
"Cyrl",
translit = "gld-translit",
strip_diacritics = {remove_diacritics = c.macron},
sort_key = {
from = {"ё", "ӈ"},
to = {"е" .. p[1], "н" .. p[1]}
},
}
m["glh"] = {
"Northwest Pashayi",
23713532,
"inc-pas",
"Arab",
}
m["glj"] = {
"Kulaal",
33360,
"alv-bua",
}
m["glk"] = {
"Gilaki",
33657,
"ira-csp",
"Arab",
}
m["glo"] = {
"Galambu",
2598797,
"cdc-wst",
"Latn",
}
m["glr"] = {
"Glaro-Twabo",
3915313,
"kro-wee",
}
m["glu"] = {
"Gula",
5617176,
"csu-bgr",
"Latn",
}
m["glw"] = {
"Glavda",
3441285,
"cdc-cbm",
"Latn",
}
m["gly"] = {
"Gule",
3120736,
"ssa-kom",
}
m["gma"] = {
"Gambera",
10502327,
"aus-wor",
"Latn",
}
m["gmb"] = {
"Gula'alaa",
3120733,
"poz-sls",
"Latn",
}
m["gmd"] = {
"Mághdì",
3914475,
"alv-bwj",
}
m["gmg"] = {
"Magiyi",
16926155,
"ngf-sog",
"Latn",
}
m["gmh"] = {
"Middle High German",
837985,
"gmw-hgm",
"Latn",
strip_diacritics = {
remove_diacritics = c.circ .. c.macron,
from = {"Ë", "ë", "[ƷȤ]", "[ʒȥ]"},
to = {"E", "e", "Z", "z"}
},
}
m["gml"] = {
"Middle Low German",
505674,
"gmw-lgm",
"Latn",
strip_diacritics = {remove_diacritics = c.circ .. c.macron .. c.diaer},
}
m["gmm"] = {
"Gbaya-Mbodomo",
6799713,
"gba-eas",
"Latn",
}
m["gmn"] = {
"Gimnime",
11016905,
"alv-dur",
"Latn",
}
m["gmr"] = {
"Mirning",
6873793,
"aus-pam",
"Latn",
}
m["gmu"] = {
"Gumalu",
5618027,
"ngf-gum",
"Latn",
}
m["gmv"] = {
"Gamo",
16116386,
"omv-nom",
"Latn, Ethi",
}
m["gmx"] = {
"Magoma",
16939552,
"bnt-bki",
}
m["gmy"] = {
"Mycenaean Greek",
668366,
"grk",
"Linb",
translit = "Linb-translit",
strip_diacritics = { remove_diacritics = "@" .. c.dotbelow },
display_text = { from = { "@", c.dotbelow }, to = { "", "" } },
}
m["gmz"] = {
"Mgbo",
6826835,
"alv-igb",
ancestors = "izi",
}
m["gna"] = {
"Kaansa",
56802,
"nic-gur",
}
m["gnb"] = {
"Gangte",
12952442,
"tbq-kuk",
}
m["gnc"] = {
"Guanche",
35762,
"ber",
}
m["gnd"] = {
"Zulgo-Gemzek",
56800,
"cdc-cbm",
"Latn",
}
m["gne"] = {
"Ganang",
63163361,
"nic-plc",
ancestors = "izr",
}
m["gng"] = {
"Ngangam",
35888,
"nic-grm",
"Latn",
}
m["gnh"] = {
"Lere",
3915319,
"nic-jer",
}
m["gni"] = {
"Gooniyandi",
2669219,
"aus-bub",
"Latn",
}
m["gnj"] = {
"Ngen of Djonkro",
63170838,
"dmn-nbe",
"Latn",
}
m["gnk"] = {
"ǁGana",
1975199,
"khi-kal",
"Latn",
}
m["gnl"] = {
"Gangulu",
4916329,
"aus-pam",
"Latn",
}
m["gnm"] = {
"Ginuman",
11732210,
"ngf-dag",
"Latn",
}
m["gnn"] = {
"Gumatj",
10510745,
"aus-yol",
"Latn",
}
m["gnq"] = {
"Gana",
5520523,
"poz-san",
"Latn",
}
m["gnr"] = {
"Gureng Gureng",
5619998,
"aus-pam",
"Latn",
}
m["gnt"] = {
"Guntai",
12952475,
"paa-ton",
"Latn",
}
m["gnu"] = {
"Gnau",
3915810,
"paa-trr",
"Latn",
}
m["gnw"] = {
"Western Bolivian Guarani",
3775037,
"gn",
"Latn",
}
m["gnz"] = {
"Ganzi",
11137942,
"nic-nkb",
"Latn",
}
m["goa"] = {
"Guro",
35251,
"dmn-mda",
"Latn",
}
m["gob"] = {
"Playero",
3027923,
"sai-guh",
}
m["goc"] = {
"Gorakor",
12952463,
"poz-ocw",
"Latn",
}
m["god"] = {
"Godié",
3914412,
"kro-bet",
}
m["goe"] = {
"Gongduk",
2669221,
"sit",
}
m["gof"] = {
"Gofa",
12631584,
"omv-nom",
"Latn, Ethi",
}
m["gog"] = {
"Gogo",
3272630,
"bnt-ruv",
"Latn",
}
m["goh"] = {
"Old High German",
35218,
"gmw-hgm",
"Latn, Runr",
strip_diacritics = {
remove_diacritics = c.circ .. c.macron .. c.diaer,
from = {"[ƷȤ]", "[ʒȥ]"},
to = {"Z", "z"}
},
translit = {
Runr = "Runr-translit",
},
}
m["goi"] = {
"Gobasi",
5575414,
"ngf-est",
"Latn",
}
m["goj"] = {
"Gowlan",
12953532,
"inc-sou",
}
-- gok is a spurious language, see [[w:Spurious languages]]
m["gol"] = {
"Gola",
35482,
"alv",
"Latn, Vaii",
}
m["gon"] = {
"Gondi",
1775361,
"dra-gon",
"Telu, Gonm, Gong, Deva, Orya",
translit = {
Telu = "te-translit",
Gong = "gon-Gong-translit",
Gonm = "gon-Gonm-translit",
},
}
m["goo"] = {
"Gone Dau",
3110470,
"poz-pcc",
"Latn",
}
m["gop"] = {
"Yeretuar",
8052565,
"poz-hce",
"Latn",
}
m["goq"] = {
"Gorap",
3110816,
"crp",
"Latn",
ancestors = "ms",
}
m["gor"] = {
"Gorontalo",
2501174,
"phi",
"Latn",
}
m["got"] = {
"Gothic",
35722,
"gme",
"Goth, Runr, Latn",
translit = {Goth = "Goth-translit"},
link_tr = true,
strip_diacritics = {Latn = {remove_diacritics = c.macron}},
}
m["gou"] = {
"Gavar",
3441180,
"cdc-cbm",
}
m["gov"] = {
"Goo",
16927208,
"dmn",
"Latn",
}
m["gow"] = {
"Gorwaa",
3437626,
"cus-sou",
"Latn",
}
m["gox"] = {
"Gobu",
7194986,
"bad-cnt",
}
m["goy"] = {
"Goundo",
317636,
"alv-kim",
}
m["goz"] = {
"Gozarkhani",
5590235,
"xme-ttc",
ancestors = "xme-ttc-eas",
}
m["gpa"] = {
"Gupa-Abawa",
3915352,
"alv-ngb",
"Latn",
}
m["gpn"] = {
"Taiap",
56237,
"qfa-dis", -- Papuan; isolate in Glottolog; relationship with Torricelli proposed by Usher
"Latn",
}
m["gqa"] = {
"Ga'anda",
56245,
"cdc-cbm",
"Latn",
}
m["gqi"] = {
"Guiqiong",
3120647,
"sit-qia",
}
m["gqn"] = { -- a variety of 'ter'
"Kinikinao",
53386731,
"awd",
"Latn",
}
m["gqr"] = {
"Gor",
759992,
"csu-sar",
"Latn",
}
m["gqu"] = {
"Qau",
17284874,
"gio",
}
m["gra"] = {
"Rajput Garasia",
21041529,
"inc-bhi",
"Deva, Gujr",
ancestors = "bhb",
}
m["grc"] = {
"Ancient Greek",
35497,
"grk",
"Polyt, Cprt",
translit = {
Cprt = "Cprt-translit",
},
override_translit = true,
-- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
standard_chars = {
Polyt = "ΑΆἈἉἊἋἌἍἎἏᾈᾉᾊᾋᾌᾍᾎᾏᾸᾹᾺᾼΒΓΔΕΈἘἙἚἛἜἝῈΖΗΉἨἩἪἫἬἭἮἯᾘᾙᾚᾛᾜᾝᾞᾟῊῌΘΙΊΪἸἹἺἻἼἽἾἿῘῙῚΚΛΜΝΞΟΌὈὉὊὋὌὍΠΡῬΡ̓ΣΤΥΎΫὙὛὝὟῨῩῪΦΧΨΩΏὨὩὪὫὬὭὮὯᾨᾩᾪᾫᾬᾭᾮᾯῸῺῼαάἀἁἂἃἄἅἆἇὰᾀᾁᾂᾃᾄᾅᾆᾇᾰᾱᾲᾳᾴᾶᾷβγδεέἐἑἒἓἔἕὲζηήἠἡἢἣἤἥἦἧὴᾐᾑᾒᾓᾔᾕᾖᾗῂῃῄῆῇθιίϊΐἰἱἲἳἴἵἶἷὶῐῑῒῖῗκλμνξοόὀὁὂὃὄὅὸπρῤῥςστυύϋΰὐὑὒὓὔὕὖὗὺῠῡῢῦῧφχψωώὠὡὢὣὤὥὦὧὼᾠᾡᾢᾣᾤᾥᾦᾧῲῳῴῶῷ·ͺ΄΅᾽᾿῀῁῍῎῏῝῞῟῭`´῾",
Cprt = "𐠀𐠁𐠂𐠃𐠄𐠅𐠈𐠊𐠋𐠌𐠍𐠎𐠏𐠐𐠑𐠒𐠓𐠔𐠕𐠖𐠗𐠘𐠙𐠚𐠛𐠜𐠝𐠞𐠟𐠠𐠡𐠢𐠣𐠤𐠥𐠦𐠧𐠨𐠩𐠪𐠫𐠬𐠭𐠮𐠯𐠰𐠱𐠲𐠳𐠴𐠵𐠷𐠸𐠼𐠿",
c.punc
},
}
m["grd"] = {
"Guruntum",
3441272,
"cdc-wst",
"Latn",
}
m["grg"] = {
"Madi",
6727664,
"ngf-gmo",
"Latn",
}
m["grh"] = {
"Gbiri-Niragu",
3913936,
"nic-kau",
"Latn",
}
m["gri"] = {
"Ghari",
3104782,
"poz-sls",
"Latn",
}
m["grj"] = {
"Southern Grebo",
3914444,
"grb",
"Latn",
}
m["grm"] = {
"Kota Marudu Talantang",
6433808,
"poz-san",
"Latn",
}
m["gro"] = {
"Groma",
56551,
"sit-tib",
}
m["grq"] = {
"Gorovu",
56355,
"paa-por",
"Latn",
}
m["grs"] = {
"Gresi",
5607612,
"paa-nim",
"Latn",
}
m["grt"] = {
"Garo",
36137,
"tbq-bdg",
"Latn, Beng, Brai",
}
m["gru"] = {
"Kistane",
13273,
"sem-eth",
"Latn, Ethi",
}
m["grv"] = {
"Central Grebo",
18385114,
"grb",
"Latn",
}
m["grw"] = {
"Gweda",
5623387,
"poz-ocw",
"Latn",
}
m["grx"] = {
"Guriaso",
12631954,
"qfa-unc", -- no consensus; may be Kwomtari per Baron (1983) and Usher (2020), but no connections accepted by
-- Glottolog.
"Latn",
}
m["gry"] = {
"Barclayville Grebo",
11157342,
"grb",
"Latn",
}
m["grz"] = {
"Guramalum",
3120935,
"poz-ocw",
"Latn",
}
m["gse"] = {
"Ghanaian Sign Language",
35289,
"sgn-asl",
"Latn", -- when documented
}
m["gsg"] = {
"German Sign Language",
33282,
"sgn-gsl",
"Sgnw",
}
m["gsl"] = {
"Gusilay",
35439,
"alv-jol",
"Latn",
}
m["gsm"] = {
"Guatemalan Sign Language",
2886781,
"sgn",
"Zxxx", -- unwrittten
}
m["gsn"] = {
"Gusan",
11732224,
"ngf-era",
"Latn",
}
m["gso"] = {
"Southwest Gbaya",
4919322,
"gba-sou",
"Latn",
}
m["gsp"] = {
"Wasembo",
7971402,
"ngf-mad", -- placed in under Rai Coast by Glottolog (under Greater Yaganon) and Pawley-Hammarström
"Latn",
}
m["gss"] = {
"Greek Sign Language",
3565084,
"sgn",
}
m["gsw"] = {
"Alemannic German",
131339,
"gmw-hgm",
"Latn",
wikimedia_codes = "als",
ancestors = "gmh",
}
m["gta"] = {
"Guató",
3027940,
"qfa-dis", -- isolate or Macro-Jê
"Latn",
}
m["gtu"] = {
"Aghu Tharrnggala",
16825981,
"aus-pmn",
"Latn",
}
m["gua"] = {
"Shiki",
3913946,
"nic-jrn",
"Latn",
}
m["gub"] = {
"Guajajára",
7699720,
"tup-gua",
"Latn",
}
m["guc"] = {
"Wayuu",
891085,
"awd-taa",
"Latn",
}
m["gud"] = {
"Yocoboué Dida",
21074781,
"kro-did",
"Latn",
}
m["gue"] = {
"Gurindji",
10511016,
"aus-pam",
"Latn",
}
m["guf"] = {
"Gupapuyngu",
10511004,
"aus-yol",
"Latn",
}
m["gug"] = {
"Paraguayan Guarani",
17478066,
"gn",
"Latn",
wikimedia_codes = "gn",
ancestors = "gn-cls",
}
m["guh"] = {
"Guahibo",
2669193,
"sai-guh",
"Latn",
}
m["gui"] = {
"Eastern Bolivian Guarani",
2963912,
"gn",
"Latn",
}
m["guk"] = {
"Gumuz",
2396970,
"ssa",
"Latn, Ethi",
}
m["gul"] = {
"Gullah",
33395,
"crp",
"Latn",
ancestors = "en",
}
m["gum"] = {
"Guambiano",
2744745,
"sai-bar",
"Latn",
}
m["gun"] = {
"Mbya Guarani",
3915584,
"gn",
"Latn",
}
m["guo"] = {
"Guayabero",
2980375,
"sai-guh",
"Latn",
}
m["gup"] = {
"Gunwinggu",
1406574,
"aus-gun",
"Latn",
}
m["guq"] = {
"Aché",
383701,
"tup",
"Latn",
}
m["gur"] = {
"Farefare",
35331,
"nic-mre",
"Latn",
}
m["gus"] = {
"Guinean Sign Language",
15983937,
"sgn-asl", -- sic, not sgn-fsl
"Latn", -- when documented
}
m["gut"] = {
"Maléku Jaíka",
3915782,
"cba",
"Latn",
}
m["guu"] = {
"Yanomamö",
8048928,
"sai-ynm",
"Latn",
}
m["guv"] = {
"Gey",
11137816,
"alv-sav",
"Latn",
}
m["guw"] = {
"Gun",
3111668,
"alv-gbe",
"Latn",
strip_diacritics = {remove_diacritics = c.grave .. c.acute .. c.macron},
}
m["gux"] = {
"Gourmanchéma",
35474,
"nic-grm",
"Latn",
}
m["guz"] = {
"Gusii",
33603,
"bnt-lok",
"Latn",
}
m["gva"] = {
"Kaskihá",
3033534,
"sai-mas",
"Latn",
}
m["gvc"] = {
"Guanano",
3566001,
"sai-tuc",
"Latn",
}
m["gve"] = {
"Duwet",
5317647,
"poz-ocw",
"Latn",
}
m["gvf"] = {
"Golin",
3110291,
"ngf-sim",
"Latn",
}
m["gvj"] = {
"Guajá",
3915506,
"tup",
"Latn",
}
m["gvl"] = {
"Gulay",
641737,
"csu-sar",
"Latn",
}
m["gvm"] = {
"Gurmana",
3913363,
"nic-shi",
"Latn",
}
m["gvn"] = {
"Kuku-Yalanji",
5621973,
"aus-pam",
"Latn",
}
m["gvo"] = {
"Gavião do Jiparaná",
5528335,
"tup",
"Latn",
}
m["gvp"] = {
"Pará Gavião",
3365443,
"sai-nje",
"Latn",
}
m["gvr"] = {
"Gurung",
2392342,
"sit-tam",
"Gukh, Deva",
}
m["gvs"] = {
"Gumawana",
5618041,
"poz-ocw",
"Latn",
}
m["gvy"] = {
"Guyani",
10511230,
"aus-pam",
"Latn",
}
m["gwa"] = {
"Mbato",
3914941,
"alv-ptn",
"Latn",
}
m["gwb"] = {
"Gwa",
5623219,
"nic-jrn",
"Latn",
}
m["gwc"] = {
"Kalami",
1675961,
"inc-koh",
"Arab",
strip_diacritics = {
Arab = {
-- character "ۂ" code U+06C2 to "ه" and "هٔ" (U+0647 + U+0654) to "ه"; hamzatu l-waṣli to a regular alif
from = {"هٔ", "ۂ", "ٱ"},
to = {"ہ", "ہ", "ا"},
remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna .. c.superalef .. u(0x065e)
},
},
}
m["gwd"] = {
"Gawwada",
3032135,
"cus-eas",
"Latn, Ethi",
}
m["gwe"] = {
"Gweno",
3358211,
"bnt-chg",
"Latn",
}
m["gwf"] = {
"Gowro",
3812403,
"inc-koh",
"Arab",
}
m["gwg"] = {
"Moo",
6907057,
"alv-bwj",
"Latn",
}
m["gwi"] = {
"Gwich'in",
21057,
"ath-nor",
"Latn",
}
m["gwj"] = {
"Gcwi",
12631978,
"khi-kal",
"Latn",
}
m["gwm"] = {
"Awngthim",
4830109,
"aus-pmn",
"Latn",
}
m["gwn"] = {
"Gwandara",
56521,
"cdc-wst",
"Latn",
}
m["gwr"] = {
"Gwere",
5623559,
"bnt-nyg",
"Latn",
}
m["gwt"] = {
"Gawar-Bati",
33894,
"inc-kun",
"Arab",
}
m["gwu"] = {
"Guwamu",
10511225,
"aus-pam",
"Latn",
}
m["gww"] = {
"Kwini",
10551249,
"aus-wor",
"Latn",
}
m["gwx"] = {
"Gua",
35422,
"alv-gng",
"Latn",
}
m["gxx"] = {
"Wè Southern",
19921582,
"kro-wee",
"Latn",
}
m["gya"] = {
"Northwest Gbaya",
36594,
"gba-wes",
"Latn",
}
m["gyb"] = {
"Garus",
5524492,
"ngf-han",
"Latn",
}
m["gyd"] = {
"Kayardild",
3913770,
"aus-tnk",
"Latn",
}
m["gye"] = {
"Gyem",
5624046,
"nic-jer",
"Latn",
}
m["gyf"] = {
"Gungabula",
10510783,
"aus-pam",
"Latn",
}
m["gyg"] = {
"Gbayi",
11137618,
"nic-ngd",
"Latn",
}
m["gyi"] = {
"Gyele",
35434,
"bnt-mnj",
"Latn",
}
m["gyl"] = {
"Gayil",
5528771,
"omv-aro",
"Latn",
}
m["gym"] = {
"Ngäbere",
3915581,
"cba",
"Latn",
}
m["gyn"] = {
"Guyanese Creole English",
3305477,
"crp",
"Latn",
ancestors = "en",
}
m["gyo"] = {
"Gyalsumdo",
53575940,
"sit-kyk",
}
m["gyr"] = {
"Guarayu",
3118779,
"tup-gua",
"Latn",
}
m["gyy"] = {
"Gunya",
10511001,
"aus-pam",
"Latn",
}
m["gza"] = {
"Ganza",
5521556,
"omv-mao",
"Latn",
}
m["gzn"] = {
"Gane",
3095108,
"poz-hce",
"Latn",
}
return require("Module:languages").finalizeData(m, "language")
0xlrgxv3ycxd1n2crqdbwwqsjj68ura
Module:etymon/tracking
828
8135
55024
2026-09-30T19:19:13Z
Deadend0914
7211
Gesceop tramet þe hafaþ '--[=[ Documentation: [[WT:Tracking#Etymon]]. ]=] local export = {} local M = require("Module:module loader").init({ require = { track = "Module:debug/track", }, }) local DEPTH_RANGES = { { min = 50, label = "extremely-deep" }, { min = 20, label = "20+" }, { min = 10, max = 19, label = "10-19" }, { min = 5, max = 9, label = "5-9" }, { min = 3, max = 4, label = "3-4" }, { max = 2, label = "1-2" }, } local NODE_RANGES = { { min = 100, label = "extre...'
55024
Scribunto
text/plain
--[=[
Documentation: [[WT:Tracking#Etymon]].
]=]
local export = {}
local M = require("Module:module loader").init({
require = {
track = "Module:debug/track",
},
})
local DEPTH_RANGES = {
{ min = 50, label = "extremely-deep" },
{ min = 20, label = "20+" },
{ min = 10, max = 19, label = "10-19" },
{ min = 5, max = 9, label = "5-9" },
{ min = 3, max = 4, label = "3-4" },
{ max = 2, label = "1-2" },
}
local NODE_RANGES = {
{ min = 100, label = "extremely-large" },
{ min = 50, label = "50+" },
{ min = 20, max = 49, label = "20-49" },
{ min = 10, max = 19, label = "10-19" },
{ min = 5, max = 9, label = "5-9" },
{ max = 4, label = "1-4" },
}
local LANGUAGE_RANGES = {
{ min = 10, label = "10+" },
{ min = 5, max = 9, label = "5-9" },
{ min = 3, max = 4, label = "3-4" },
{ exact = 2, label = "2" },
{ exact = 1, label = "1" },
}
local TERM_PAGE_NORMALIZERS = {
{
pattern = "^Reconstruction:[^/]+/(.+)$",
normalize = function(term)
if term:sub(1, 1) ~= "*" then
return "*" .. term
end
return term
end,
},
{
pattern = "^Appendix:[^/]+/(.+)$",
normalize = function(term)
return term
end,
},
}
local function normalize_term_page(term_page)
local page = tostring(term_page)
for _, rule in ipairs(TERM_PAGE_NORMALIZERS) do
local term = page:match(rule.pattern)
if term then
return rule.normalize(term)
end
end
return page
end
local function sanitize_term_page(term_page)
return normalize_term_page(term_page):gsub(":", "-"):gsub("/", "-"):gsub(" ", "_")
end
local function sanitize_track_segment(value)
return tostring(value):gsub(":", "-"):gsub("/", "-"):gsub(" ", "_")
end
local function track_path(page_lang_code, path)
M.track(path)
if page_lang_code then
M.track("etymon/lang/" .. page_lang_code .. "/" .. path:match("^etymon/(.+)$"))
end
end
local function term_path(term_lang_code, term_page, ...)
local safe_term = sanitize_term_page(term_page)
local parts = { "etymon", "term", term_lang_code, safe_term }
for i = 1, select("#", ...) do
local segment = select(i, ...)
if segment then
table.insert(parts, segment)
end
end
return table.concat(parts, "/")
end
local function term_id_path(term_lang_code, term_page, id_value, suffix)
return term_path(term_lang_code, term_page, "id", sanitize_track_segment(id_value), suffix)
end
local function idless_term_path(term_lang_code, term_page, outcome)
return term_path(term_lang_code, term_page, outcome)
end
local function mismatched_term_path(term_lang_code, term_page, id_value)
return term_id_path(term_lang_code, term_page, id_value, "mismatched")
end
local function record_term_id(id_stats, term_lang_code, term_page, id_value, is_override)
if not term_page or term_page == "" or not id_value or id_value == "" then
return
end
id_stats.term_ids[term_lang_code] = id_stats.term_ids[term_lang_code] or {}
id_stats.term_ids[term_lang_code][term_page] = id_stats.term_ids[term_lang_code][term_page] or {}
local entry = id_stats.term_ids[term_lang_code][term_page][id_value]
if not entry then
entry = { count = 0, override = false }
id_stats.term_ids[term_lang_code][term_page][id_value] = entry
end
entry.count = entry.count + 1
if is_override then
entry.override = true
end
end
local function record_idless_term(id_stats, term_lang_code, term_page)
if not term_page or term_page == "" then
return
end
id_stats.idless_terms[term_lang_code] = id_stats.idless_terms[term_lang_code] or {}
local entry = id_stats.idless_terms[term_lang_code][term_page]
if not entry then
entry = { count = 0, outcomes = {} }
id_stats.idless_terms[term_lang_code][term_page] = entry
end
entry.count = entry.count + 1
end
local function track_ranges(base_key, value, ranges, lang_code)
M.track("etymon/" .. base_key .. "/" .. value)
if lang_code then
M.track("etymon/lang/" .. lang_code .. "/" .. base_key .. "/" .. value)
end
for _, range in ipairs(ranges) do
local matches = false
if range.min and range.max then
matches = value >= range.min and value <= range.max
elseif range.min then
matches = value >= range.min
elseif range.max then
matches = value <= range.max
elseif range.exact then
matches = value == range.exact
end
if matches then
M.track("etymon/" .. base_key .. "/" .. range.label)
if lang_code then
M.track("etymon/lang/" .. lang_code .. "/" .. base_key .. "/" .. range.label)
end
break
end
end
end
function export.track_term(rest)
if rest == "" then
M.track("etymon/term/empty")
elseif rest == "?" then
M.track("etymon/term/question-mark")
elseif rest == "-" then
M.track("etymon/term/hyphen")
end
end
function export.track_title_pagename_mismatch(lang)
local lang_code = lang:getCode()
M.track("etymon/title/pagename-mismatch-after-strip-diacritics")
M.track("etymon/lang/" .. lang_code .. "/title/pagename-mismatch-after-strip-diacritics")
end
function export.record_keyword_usage(keyword_stats, keyword, target_lang, source_lang, is_toplevel)
if not is_toplevel then
return
end
if not keyword_stats[keyword] then
keyword_stats[keyword] = {
count = 0,
target_langs = {},
source_langs = {},
}
end
local keyword_data = keyword_stats[keyword]
keyword_data.count = keyword_data.count + 1
local target_code = target_lang:getCode()
keyword_data.target_langs[target_code] = (keyword_data.target_langs[target_code] or 0) + 1
if source_lang then
local source_code = source_lang:getCode()
keyword_data.source_langs[source_code] = (keyword_data.source_langs[source_code] or 0) + 1
end
end
function export.track_tree_metrics(opts)
local max_depth = opts.max_depth_reached
if not max_depth or max_depth <= 0 then
return
end
local lang_code = opts.lang:getCode()
local total_nodes = opts.total_nodes
local language_count = opts.language_count
track_ranges("depth", max_depth, DEPTH_RANGES, lang_code)
track_ranges("nodes", total_nodes, NODE_RANGES, lang_code)
local unique_languages = 0
for _ in pairs(language_count) do
unique_languages = unique_languages + 1
end
track_ranges("unique-languages", unique_languages, LANGUAGE_RANGES, lang_code)
if total_nodes == max_depth + 1 then
track_ranges("linear-depth", max_depth, DEPTH_RANGES, lang_code)
end
end
function export.track_keywords(keyword_stats, target_lang)
local target_lang_code = target_lang:getCode()
for keyword, keyword_data in pairs(keyword_stats) do
M.track("etymon/keyword/" .. keyword)
M.track("etymon/keyword/" .. keyword .. "/target/" .. target_lang_code)
for source_code in pairs(keyword_data.source_langs) do
M.track("etymon/keyword/" .. keyword .. "/source/" .. source_code)
M.track("etymon/keyword/" .. keyword .. "/target/" .. target_lang_code .. "/source/" .. source_code)
end
end
end
function export.record_term_id_usage(id_stats, etymon_data, term_page)
local term_lang_code = etymon_data.lang:getCode()
if etymon_data.id and etymon_data.id ~= "" then
record_term_id(id_stats, term_lang_code, term_page, etymon_data.id, etymon_data.override)
else
record_idless_term(id_stats, term_lang_code, term_page)
end
end
function export.record_mismatched_id_usage(id_stats, term_lang, term_page, id_value)
if not term_page or term_page == "" or not id_value or id_value == "" then
return
end
local term_lang_code = term_lang:getCode()
id_stats.mismatched_ids[term_lang_code] = id_stats.mismatched_ids[term_lang_code] or {}
id_stats.mismatched_ids[term_lang_code][term_page] = id_stats.mismatched_ids[term_lang_code][term_page] or {}
id_stats.mismatched_ids[term_lang_code][term_page][id_value] =
(id_stats.mismatched_ids[term_lang_code][term_page][id_value] or 0) + 1
end
function export.record_idless_resolution(id_stats, term_lang, term_page, outcome)
if not term_page or term_page == "" then
return
end
local term_lang_code = term_lang:getCode()
id_stats.idless_terms[term_lang_code] = id_stats.idless_terms[term_lang_code] or {}
local entry = id_stats.idless_terms[term_lang_code][term_page]
if not entry then
entry = { count = 0, outcomes = {} }
id_stats.idless_terms[term_lang_code][term_page] = entry
end
entry.outcomes[outcome] = (entry.outcomes[outcome] or 0) + 1
end
function export.track_text_stop_lang_missing(page_lang, stop_code)
if not stop_code or stop_code == "" then
return
end
local page_lang_code = page_lang:getCode()
M.track("etymon/text/stop-lang/missing/" .. stop_code)
M.track("etymon/lang/" .. page_lang_code .. "/text/stop-lang/missing/" .. stop_code)
end
function export.track_page_id(page_lang, id)
local lang_code = page_lang:getCode()
if id and id ~= "" then
M.track("etymon/page-id/set")
M.track("etymon/lang/" .. lang_code .. "/page-id/set")
else
M.track("etymon/page-id/unset")
M.track("etymon/lang/" .. lang_code .. "/page-id/unset")
end
end
function export.track_ids(id_stats, page_lang)
local page_lang_code = page_lang:getCode()
for term_lang_code, terms in pairs(id_stats.term_ids or {}) do
for term_page, ids in pairs(terms) do
for id_value, entry in pairs(ids) do
if entry.count > 0 then
track_path(page_lang_code, term_id_path(term_lang_code, term_page, id_value))
if entry.override then
track_path(page_lang_code, term_id_path(term_lang_code, term_page, id_value, "override"))
end
end
end
end
end
for term_lang_code, terms in pairs(id_stats.idless_terms or {}) do
for term_page, entry in pairs(terms) do
if entry.count > 0 then
track_path(page_lang_code, idless_term_path(term_lang_code, term_page))
end
for outcome, count in pairs(entry.outcomes or {}) do
if count > 0 then
track_path(page_lang_code, idless_term_path(term_lang_code, term_page, outcome))
end
end
end
end
for term_lang_code, terms in pairs(id_stats.mismatched_ids or {}) do
for term_page, ids in pairs(terms) do
for id_value, count in pairs(ids) do
if count > 0 then
track_path(page_lang_code, mismatched_term_path(term_lang_code, term_page, id_value))
end
end
end
end
end
function export.new_id_stats()
return {
term_ids = {},
idless_terms = {},
mismatched_ids = {},
}
end
return export
1a6uqo8jj8m8p5gt11ki7rx17qdrf11
Module:etymon/data
828
8136
55025
2026-09-30T19:19:40Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} export.STATUS = { OK = "ok", INLINE = "inline", MISSING = "missing", REDLINK = "redlink", AMBIGUOUS = "ambiguous", } export.TRANSITIVE = { ALWAYS = "always", -- always recurse into children NEVER = "never", -- never recurse into children CROSS_LANG = "cross_lang", -- only recurse when source lang differs from targe...'
55025
Scribunto
text/plain
local export = {}
export.STATUS = {
OK = "ok",
INLINE = "inline",
MISSING = "missing",
REDLINK = "redlink",
AMBIGUOUS = "ambiguous",
}
export.TRANSITIVE = {
ALWAYS = "always", -- always recurse into children
NEVER = "never", -- never recurse into children
CROSS_LANG = "cross_lang", -- only recurse when source lang differs from target lang (but pos chain continues)
CROSS_LANG_NO_INTERNAL_SOURCE = "cross_lang_no_internal_source", -- like CROSS_LANG, but source breaks for internal derivations in the same language context
}
-- Deep merge tables (nested tables are merged recursively, later values override earlier)
local function deep_merge(...)
local result = {}
for _, t in ipairs({ ... }) do
for k, v in pairs(t) do
if type(v) == "table" and type(result[k]) == "table" then
result[k] = deep_merge(result[k], v)
else
result[k] = v
end
end
end
return result
end
local function make_glossary_link(term, display_text)
if not term then return display_text end
return "[[Appendix:Glossary#" .. term:gsub(" ", "_") .. "|" .. display_text .. "]]"
end
-- Extract base word and connector from text like "Borrowed from" or "calque of"
local function split_glossary_text(text)
for _, pattern in ipairs({ "^(.-)(%s+[Oo][Ff])$", "^(.-)(%s+[Ff][Rr][Oo][Mm])$" }) do
local base, rest = text:match(pattern)
if base then return base, rest end
end
return text, ""
end
local TRANSITIVE = export.TRANSITIVE
local function create_keyword(opts)
local entry = {
is_group = opts.is_group or false,
abbrev = opts.abbrev,
glossary = opts.glossary,
transitive = opts.transitive or TRANSITIVE.ALWAYS, -- default "always"
inherited_chain = opts.inherited_chain or false,
affix_categories = opts.affix_categories or false,
borrowing_type = opts.borrowing_type,
specialized_borrowing = opts.specialized_borrowing,
toplevel_category = opts.toplevel_category,
no_child_categories = opts.no_child_categories or false,
source_category_type = opts.source_category_type,
invisible = (opts.invisible == true and "all") or opts.invisible or false,
pos_override = opts.pos_override,
new_sentence = opts.new_sentence or false,
separate_clause = opts.separate_clause or false,
default_conj = opts.default_conj,
min_etymons = opts.min_etymons,
max_etymons = opts.max_etymons,
term_rules = opts.term_rules,
aliases = opts.aliases,
}
-- Only set text/phrase when visible in text (invisible ~= "all" and ~= "text")
local inv = entry.invisible
if inv ~= "all" and inv ~= "text" then
entry.phrase = opts.phrase
if opts.text then
if opts.glossary then
local base_word, rest = split_glossary_text(opts.text)
entry.text = make_glossary_link(opts.glossary, base_word) .. rest
else
entry.text = opts.text
end
end
end
return entry
end
-- Shared defaults for keyword groups
local DEFAULTS = {
-- Keywords that pass through inheritance chain
inheritance = {
transitive = TRANSITIVE.ALWAYS,
inherited_chain = true,
},
-- Standard transitive derivation
transitive = {
transitive = TRANSITIVE.ALWAYS,
},
-- Standard for internal derivations: transitive across languages, but not within them
internal_derivation = {
transitive = TRANSITIVE.CROSS_LANG,
},
-- Borrowing keywords
borrowing = {
transitive = TRANSITIVE.ALWAYS,
},
-- Affix group keywords (compound words, blends, etc.)
affix_group = {
is_group = true,
min_etymons = 2,
transitive = TRANSITIVE.CROSS_LANG,
affix_categories = true,
},
-- Calque-like keywords (calque, partial calque, semantic loan)
calque_like = {
transitive = TRANSITIVE.NEVER,
no_child_categories = true,
new_sentence = true,
},
-- Non-transitive influence
influence_like = {
transitive = TRANSITIVE.NEVER,
no_child_categories = true,
},
}
export.keywords = {
--
-- Inheritance keywords
--
["from"] = create_keyword(deep_merge(DEFAULTS.inheritance, {
text = "From", phrase = "from",
term_rules = { entry_lang = true },
})),
["inherited"] = create_keyword(deep_merge(DEFAULTS.inheritance, {
text = "Inherited from",
phrase = "from",
glossary = "inherited",
aliases = { "inh" },
term_rules = {
family = "disallowed",
family_suffix = "; use a specific language.",
ancestor_check = true,
},
})),
--
-- Basic derivation keywords
--
["uder"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "From",
phrase = "from",
toplevel_category = "undefined derivations",
})),
["derived"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Derived from",
phrase = "from",
abbrev = "der.",
glossary = "derived terms",
aliases = { "der" },
})),
--
-- Affix/compound group keywords
--
["affix"] = create_keyword(deep_merge(DEFAULTS.affix_group, {
text = "From",
phrase = "from",
min_etymons = 1,
aliases = { "af" },
})),
["blend"] = create_keyword(deep_merge(DEFAULTS.affix_group, {
text = "Blend of",
phrase = "a blend of",
abbrev = "blend",
glossary = "blend",
toplevel_category = "blends",
})),
["univerbation"] = create_keyword(deep_merge(DEFAULTS.affix_group, {
text = "Univerbation of",
phrase = "univerbation of",
abbrev = "univ.",
glossary = "univerbation",
toplevel_category = "univerbations",
min_etymons = 1,
aliases = { "univ" },
})),
["vrd-af"] = create_keyword(deep_merge(DEFAULTS.affix_group, {
text = "Vṛddhi derivative of",
phrase = "a vṛddhi derivative of",
abbrev = "vṛd.",
glossary = "vṛddhi derivative",
toplevel_category = "vrddhi derivatives",
})),
["sa-af"] = create_keyword(deep_merge(DEFAULTS.affix_group, {
text = "[[Sanskritic]] formation from",
phrase = "a [[Sanskritic]] formation of",
toplevel_category = "Sanskritic formations",
})),
--
-- Borrowing keywords
--
["bor"] = create_keyword(deep_merge(DEFAULTS.borrowing, {
text = "Borrowed from",
phrase = "borrowed from",
abbrev = "bor.",
glossary = "loanword",
borrowing_type = "borrowed",
aliases = { "borrowed" },
})),
["lbor"] = create_keyword(deep_merge(DEFAULTS.borrowing, {
text = "Learned borrowing from",
phrase = "a learned borrowing from",
abbrev = "lbor.",
glossary = "learned borrowing",
specialized_borrowing = "learned",
})),
["obor"] = create_keyword(deep_merge(DEFAULTS.borrowing, {
text = "Orthographic borrowing from",
phrase = "an orthographic borrowing from",
abbrev = "obor.",
glossary = "orthographic borrowing",
specialized_borrowing = "orthographic",
})),
["slbor"] = create_keyword(deep_merge(DEFAULTS.borrowing, {
text = "Semi-learned borrowing from",
phrase = "a semi-learned borrowing from",
abbrev = "slbor.",
glossary = "semi-learned borrowing",
specialized_borrowing = "semi-learned",
aliases = { "slb" },
})),
["ubor"] = create_keyword(deep_merge(DEFAULTS.borrowing, {
text = "Unadapted borrowing from",
phrase = "an unadapted borrowing from",
abbrev = "ubor.",
glossary = "unadapted borrowing",
specialized_borrowing = "unadapted",
})),
--
-- Calque-like keywords (non-transitive, start new sentence)
--
["calque"] = create_keyword(deep_merge(DEFAULTS.calque_like, {
text = "Calque of",
phrase = "a calque of",
abbrev = "calq.",
glossary = "calque",
specialized_borrowing = "calque",
aliases = { "cal", "clq" },
})),
["partial calque"] = create_keyword(deep_merge(DEFAULTS.calque_like, {
text = "Partial calque of",
phrase = "a partial calque of",
abbrev = "pcalq.",
glossary = "partial calque",
specialized_borrowing = "partial-calque",
aliases = { "pcal", "pclq" },
})),
["semantic loan"] = create_keyword(deep_merge(DEFAULTS.calque_like, {
text = "Semantic loan from",
phrase = "a semantic loan from",
abbrev = "sl.",
glossary = "semantic loan",
specialized_borrowing = "semantic-loan",
aliases = { "sl" },
})),
["psm"] = create_keyword(deep_merge(DEFAULTS.calque_like, {
text = "Phono-semantic matching of",
phrase = "a phono-semantic matching of",
abbrev = "psm.",
glossary = "phono-semantic matching",
specialized_borrowing = "phono-semantic-matching",
aliases = { "phono-semantic matching" },
})),
--
-- Influence keywords (non-transitive, separate clause)
--
["influence"] = create_keyword(deep_merge(DEFAULTS.influence_like, {
text = "Influenced by",
phrase = "influenced by",
abbrev = "influ.",
glossary = "contamination",
separate_clause = true,
})),
--
-- Morphological derivation keywords
--
["clipping"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Clipping of",
phrase = "clipping of",
abbrev = "clip.",
glossary = "clipping",
toplevel_category = "clippings",
aliases = { "clip" },
})),
["ellipsis"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Ellipsis of",
phrase = "ellipsis of",
abbrev = "ellip.",
glossary = "ellipsis",
toplevel_category = "ellipses",
aliases = { "ellip" },
})),
["back-formation"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Back-formation from",
phrase = "a back-formation from",
abbrev = "bf.",
glossary = "back-formation",
toplevel_category = "back-formations",
aliases = { "bf" },
})),
["nominalization"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Nominalization of",
phrase = "a nominalization of",
abbrev = "nom.",
glossary = "nominalization",
toplevel_category = "nominalizations",
aliases = { "nom" },
})),
["transliteration"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Transliteration of",
phrase = "borrowed from",
abbrev = "translit.",
glossary = "transliteration",
aliases = { "translit" },
})),
["vrd"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Vṛddhi derivative of",
phrase = "a vṛddhi derivative of",
abbrev = "vṛd.",
glossary = "vṛddhi derivative",
toplevel_category = "vrddhi derivatives",
})),
["apheretic"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Apheretic form of",
phrase = "an apheretic form of",
abbrev = "aph.",
glossary = "apheresis",
aliases = { "apheresis", "aphetic" },
})),
["denominal"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Denominal verb from",
phrase = "denominal verb from",
abbrev = "denom.",
glossary = "denominal",
toplevel_category = "denominal verbs",
aliases = { "denom" },
})),
["deverbal"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Deverbal from",
phrase = "deverbal from",
abbrev = "deverb.",
glossary = "deverbal",
toplevel_category = "deverbals",
})),
["reduplication"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Reduplication of",
phrase = "reduplication of",
abbrev = "redup.",
glossary = "reduplication",
toplevel_category = "reduplications",
aliases = { "rdp", "redup" },
})),
["abbreviation"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Abbreviation of",
phrase = "abbreviation of",
abbrev = "abbr.",
glossary = "abbreviation",
aliases = { "abbr", "abbrev" },
})),
["syllabic abbreviation"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Syllabic abbreviation of",
phrase = "syllabic abbreviation of",
abbrev = "syl. abbr.",
glossary = "syllabic abbreviation",
aliases = { "sylabbr", "sylabbrev" },
})),
["acronym"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Acronym of",
phrase = "acronym of",
abbrev = "acronym",
glossary = "acronym",
aliases = { "acro" },
})),
["initialism"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Initialism of",
phrase = "initialism of",
abbrev = "init.",
glossary = "initialism",
aliases = { "init" },
})),
["metathesis"] = create_keyword(deep_merge(DEFAULTS.transitive, {
text = "Metathesis of",
phrase = "metathesis of",
abbrev = "meta.",
glossary = "metathesis",
toplevel_category = "words derived through metathesis",
aliases = { "meta" },
})),
--
-- Invisible keywords (no text output)
--
["root"] = create_keyword {
transitive = TRANSITIVE.ALWAYS,
invisible = "all",
pos_override = "root",
},
["afeq"] = create_keyword(deep_merge(DEFAULTS.affix_group, {
text = "From",
phrase = "from",
transitive = TRANSITIVE.NEVER,
min_etymons = 1,
invisible = "all",
})),
}
-- Template-parameter supplements.
export.supplements = {
doublet = {
text = "Doublet of",
phrase = "doublet of",
glossary = "doublet",
toplevel_category = "doublets",
default_conj = "and",
term_rules = {
entry_lang = true,
require_term = true,
disallow = { "suppress", "unknown", "family" },
},
},
}
local aliases_to_register = {}
local canonical_aliases = {}
-- Map every keyword (canonical or alias) to its canonical form for consistent checks and tracking.
export.keyword_canonical = {}
for name, keyword_data in pairs(export.keywords) do
export.keyword_canonical[name] = name
if keyword_data.aliases then
canonical_aliases[name] = keyword_data.aliases
for _, alias in ipairs(keyword_data.aliases) do
if export.keywords[alias] then
error("Alias '" ..
alias .. "' defined in keyword '" .. name .. "' collides with existing keyword '" .. alias .. "'.")
end
if aliases_to_register[alias] then
error("Alias '" ..
alias .. "' defined in keyword '" .. name .. "' is already claimed by another keyword.")
end
aliases_to_register[alias] = keyword_data
export.keyword_canonical[alias] = name
end
keyword_data.aliases = nil
end
end
for alias, data in pairs(aliases_to_register) do
export.keywords[alias] = data
end
--
-- Language exception presets
--
local EXCEPTION_PRESETS = {
-- Fully disallowed: no tree, no text, no categories
disallowed = {
disallow = { tree = true, text = true },
suppress_categories = true,
},
-- Suppress transliteration only
no_translit = {
suppress_tr = true,
},
-- Suppress all categories only
no_categories = {
suppress_categories = true,
},
}
--[=[
Available exception options:
disallow = { Related options for disallowing output:
tree Disallow etymology trees for this language
text Disallow etymology text generation for this language
ref Reference link shown when tree/text is disallowed
}
suppress_tr Suppress transliteration in links
suppress_categories Suppress all category generation
normalize_to Normalize language code to a different code
normalize_from_families Apply normalization to languages in these families
normalize_exclude_families Exclude these families from normalization
keyword_overrides Per-keyword categorisation overrides (e.g. { ["af"] = { transitive = TRANSITIVE.NEVER } })
]=]
local function create_exception(preset, overrides)
local base = preset and EXCEPTION_PRESETS[preset] or {}
return deep_merge(base, overrides or {})
end
export.config = {
lang_exceptions = {
["zh"] = create_exception("disallowed", {
disallow = { ref = "[[Wiktionary:Beer parlour/2025/May#Template:etymon for Chinese]]" },
suppress_tr = true,
normalize_to = "zh",
normalize_from_families = { "zhx" },
normalize_exclude_families = { "qfa-cnt" },
}),
},
}
-- Supported codes for the nominalization <g:code> modifier (subset of common gender/number-style codes)
-- Short names of the <aftype:> modifier, as [[Module:affix]] names affix types.
export.aftype_aliases = {
["pre"] = "prefix",
["suf"] = "suffix",
["in"] = "infix",
["inter"] = "interfix",
["circum"] = "circumfix",
["naf"] = "non-affix",
["root"] = "non-affix",
}
export.nominalization_g_codes = {
["m"] = "masculine",
["f"] = "feminine",
["n"] = "neuter",
["c"] = "common",
["gneut"] = "gender-neutral",
["s"] = "singular",
["p"] = "plural",
["d"] = "dual",
["pauc"] = "paucal",
["mf"] = "masculine or feminine",
["fm"] = "masculine or feminine",
["mfn"] = "masculine, feminine or neuter",
["mnf"] = "masculine, feminine or neuter",
["fmn"] = "masculine, feminine or neuter",
["fnm"] = "masculine, feminine or neuter",
["nmf"] = "masculine, feminine or neuter",
["nfm"] = "masculine, feminine or neuter",
}
--
-- Propagate keyword overrides to aliases
--
if export.config.lang_exceptions then
for lang_code, exception in pairs(export.config.lang_exceptions) do
if exception.keyword_overrides then
for canonical, aliases in pairs(canonical_aliases) do
if exception.keyword_overrides[canonical] then
local override_data = exception.keyword_overrides[canonical]
for _, alias in ipairs(aliases) do
if not exception.keyword_overrides[alias] then
exception.keyword_overrides[alias] = override_data
end
end
end
end
end
end
end
return export
5eomy2e0qlujstp3t9s9r5rlf8dlnhc
Module:etymon/descendants
828
8137
55026
2026-09-30T19:20:04Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local M = require("Module:module loader").init({ require = { template_parser = "Module:template parser", links = "Module:links", languages = "Module:languages", }, }) local TRACKABLE_KEYWORDS = { inherited = true, bor = true, lbor = true, slbor = true, derived = true, uder = true, } local DESCENDANT_TEMPLATES = { ["desc"] = true, ["descendant"] = true, ["desctree"] = true, ["descendants tree"] = true, } local SKIPPED_TERMS...'
55026
Scribunto
text/plain
local export = {}
local M = require("Module:module loader").init({
require = {
template_parser = "Module:template parser",
links = "Module:links",
languages = "Module:languages",
},
})
local TRACKABLE_KEYWORDS = {
inherited = true,
bor = true,
lbor = true,
slbor = true,
derived = true,
uder = true,
}
local DESCENDANT_TEMPLATES = {
["desc"] = true,
["descendant"] = true,
["desctree"] = true,
["descendants tree"] = true,
}
local SKIPPED_TERMS = {
[""] = true,
["-"] = true,
}
local CHECK_MISSING_HEADER = { missing_header = true, missing_entry = false }
local STRIPPED_REF_TEMPLATES = {
ref = true,
refn = true,
}
local ENABLE_DESCENDANTS_TRACKING = false
local function _resolve_explicit_cache_id(explicit_id, parent_etymon)
if parent_etymon then
return parent_etymon.id or "*"
end
return explicit_id
end
local function _resolve_single_cache_id(id_data)
return (type(id_data) == "table" and id_data.id) or id_data or "*"
end
local function _resolve_cache_id(lookup)
if not lookup then
return nil
end
if lookup.id then
return lookup.id
end
if lookup.explicit_id then
return _resolve_explicit_cache_id(lookup.explicit_id, lookup.parent_etymon)
end
if lookup.id_data ~= nil then
return _resolve_single_cache_id(lookup.id_data)
end
return nil
end
local function _get_cached_check(cache_table, is_toplevel, base_key, cache_id)
if not is_toplevel or not cache_table or not base_key or not cache_id then
return nil
end
return cache_table[base_key .. ":" .. cache_id]
end
local function _store_checks(cache_table, lang_page_key, checks_by_id, redirected_from)
if not cache_table or not lang_page_key or not checks_by_id then
return
end
for id, check in pairs(checks_by_id) do
cache_table[lang_page_key .. ":" .. id] = check
end
if redirected_from then
for id, check in pairs(checks_by_id) do
cache_table[redirected_from .. ":" .. id] = check
end
end
end
local function _strip_refs(value)
if type(value) ~= "string" or value == "" then
return value
end
local get_node_class = M.template_parser.class_else_type
local function trim_local(text)
return (type(text) == "string" and text:match("^%s*(.-)%s*$")) or ""
end
local function lower_local(text)
return text and mw.ustring.lower(text) or text
end
local function template_name_unexpanded(template_node)
return lower_local(trim_local(tostring(template_node[1]) or ""))
end
local function stringify_without_ref_templates(node)
if type(node) ~= "table" then
return node == nil and "" or tostring(node)
end
local node_class = get_node_class(node)
if node_class == "template" and STRIPPED_REF_TEMPLATES[template_name_unexpanded(node)] then
return ""
end
if node_class == "template" then
return tostring(node)
end
if node_class == "heading" then
return tostring(node)
end
if node_class and node_class ~= "wikitext" then
return tostring(node)
end
local pieces = {}
for i = 1, #node do
pieces[i] = stringify_without_ref_templates(node[i])
end
return table.concat(pieces)
end
local parsed = M.template_parser.parse(value)
local cleaned = stringify_without_ref_templates(parsed)
local changed = true
while changed do
local next_cleaned = mw.ustring.gsub(cleaned, "<%s*[Rr][Ee][Ff][^>]-/>", "")
next_cleaned = mw.ustring.gsub(next_cleaned, "<%s*[Rr][Ee][Ff][^>]*>.-<%s*/%s*[Rr][Ee][Ff]%s*>", "")
next_cleaned = mw.ustring.gsub(next_cleaned, "{{%s*[Rr][Ee][Ff]%s*}}", "")
next_cleaned = mw.ustring.gsub(next_cleaned, "{{%s*[Rr][Ee][Ff]%s*|[^{}]-}}", "")
changed = next_cleaned ~= cleaned
cleaned = next_cleaned
end
return cleaned
end
local function _trim(value)
if type(value) ~= "string" then
return nil
end
return value:match("^%s*(.-)%s*$")
end
local function _lower(value)
return value and mw.ustring.lower(value) or value
end
local function _is_index_in_range(index, start_index, end_index)
return index and index >= start_index and index <= end_index
end
local function _build_parse_index(lang_section)
local parsed = M.template_parser.parse(lang_section)
local headings = {}
local templates = {}
for heading in parsed:iterate_nodes("heading") do
local heading_name = heading:get_name()
table.insert(headings, {
index = heading.index or 1,
level = heading.level or 0,
name = heading_name and _lower(_trim(heading_name) or "") or nil,
length = #tostring(heading),
})
end
for template in parsed:iterate_nodes("template") do
local template_name = template:get_name()
local template_args = template:get_arguments()
table.insert(templates, {
index = template.index or 1,
name = template_name and _lower(_trim(template_name) or "") or "",
args = template_args or {},
})
end
return {
headings = headings,
templates = templates,
}
end
local function _build_regions(lang_section, headings)
local content_length = #lang_section
local etymology_headings = {}
for _, heading in ipairs(headings) do
if heading.level == 3 and heading.name and mw.ustring.match(heading.name, "^etymology") then
table.insert(etymology_headings, heading)
end
end
if #etymology_headings == 0 then
return {
{ start_index = 1, end_index = content_length }
}
end
local regions = {}
for i = 1, #etymology_headings do
local heading = etymology_headings[i]
local next_heading = etymology_headings[i + 1]
local start_index = heading.index
local end_index = next_heading and (next_heading.index - 1) or content_length
if i == 1 and heading.index > 1 then
local preamble = lang_section:sub(1, heading.index - 1)
if preamble:match("%S") then
start_index = 1
end
end
table.insert(regions, {
start_index = start_index,
end_index = end_index,
})
end
return regions
end
local function _count_region_etymons(region, templates, etymon_lang_code)
local count = 0
for _, template in ipairs(templates) do
if _is_index_in_range(template.index, region.start_index, region.end_index)
and template.name == "etymon"
and template.args[1] == etymon_lang_code then
count = count + 1
end
end
return count
end
local function _get_descendants_sections(region, headings)
local sections = {}
for i = 1, #headings do
local heading = headings[i]
if _is_index_in_range(heading.index, region.start_index, region.end_index)
and heading.name == "descendants" then
local body_start = heading.index + heading.length
local body_end = region.end_index
for j = i + 1, #headings do
local next_heading = headings[j]
if next_heading.index > region.end_index then
break
end
if next_heading.level <= heading.level then
body_end = next_heading.index - 1
break
end
end
table.insert(sections, {
start_index = body_start,
end_index = body_end,
})
end
end
return sections
end
local function _template_lang_matches_entry(template_lang_code, entry_full_code, cache)
local normalized_code = _trim(template_lang_code)
if not normalized_code or normalized_code == "" then
return false
end
local cached = cache[normalized_code]
if cached ~= nil then
return cached
end
local template_lang = M.languages.getByCode(normalized_code, nil, true)
local is_match = template_lang and template_lang:getFullCode() == entry_full_code or false
cache[normalized_code] = is_match
return is_match
end
local function _template_lists_target(template_args, first_term_index, target_page_lower, entry_lang)
for arg_index = first_term_index, #template_args do
local template_term = _trim(template_args[arg_index])
if template_term and not SKIPPED_TERMS[template_term] then
if _lower(template_term) == target_page_lower then
return true
end
local template_page = M.links.get_link_page(template_term, entry_lang)
if _lower(template_page) == target_page_lower then
return true
end
end
end
return false
end
local function _is_target_listed_in_descendants(descendants_sections, templates, entry_title, entry_lang)
if #descendants_sections == 0 then
return false
end
local target_page = M.links.get_link_page(entry_title, entry_lang)
local target_page_lower = _lower(target_page)
local entry_full_code = entry_lang:getFullCode()
local lang_match_cache = {}
for _, section in ipairs(descendants_sections) do
for _, template in ipairs(templates) do
if _is_index_in_range(template.index, section.start_index, section.end_index)
and DESCENDANT_TEMPLATES[template.name] then
local lang_arg_index = nil
for arg_index = 1, #template.args do
if _template_lang_matches_entry(template.args[arg_index], entry_full_code, lang_match_cache) then
lang_arg_index = arg_index
break
end
end
if lang_arg_index and _template_lists_target(template.args, lang_arg_index + 1, target_page_lower, entry_lang) then
return true
end
end
end
end
return false
end
local function _region_has_descendant_template(region, templates)
for _, template in ipairs(templates) do
if _is_index_in_range(template.index, region.start_index, region.end_index)
and DESCENDANT_TEMPLATES[template.name] then
return true
end
end
return false
end
local function _get_region_check(region, parsed_index, entry_title, entry_lang)
local descendants_sections = _get_descendants_sections(region, parsed_index.headings)
if #descendants_sections == 0 then
if _region_has_descendant_template(region, parsed_index.templates) then
local fallback_sections = { {
start_index = region.start_index,
end_index = region.end_index,
} }
return {
missing_header = false,
missing_entry = not _is_target_listed_in_descendants(fallback_sections, parsed_index.templates, entry_title, entry_lang),
}
end
return CHECK_MISSING_HEADER
end
return {
missing_header = false,
missing_entry = not _is_target_listed_in_descendants(descendants_sections, parsed_index.templates, entry_title, entry_lang),
}
end
local function _build_checks_by_id(lang_section, etymon_lang_code, found_templates_for_lang, entry_title, entry_lang)
local checks_by_id = {}
local parsed_index = _build_parse_index(lang_section)
local regions = _build_regions(lang_section, parsed_index.headings)
local template_list_index = 1
local mapping_failed = false
for _, region in ipairs(regions) do
local region_etymon_count = _count_region_etymons(region, parsed_index.templates, etymon_lang_code)
local region_check = nil
if region_etymon_count > 0 then
region_check = _get_region_check(region, parsed_index, entry_title, entry_lang)
end
for _ = 1, region_etymon_count do
local found_template_args = found_templates_for_lang[template_list_index]
if not found_template_args then
mapping_failed = true
break
end
checks_by_id[found_template_args.id or "*"] = region_check
template_list_index = template_list_index + 1
end
if mapping_failed then
break
end
end
if mapping_failed or template_list_index ~= (#found_templates_for_lang + 1) then
local global_region = { start_index = 1, end_index = #lang_section }
local fallback_check = _get_region_check(global_region, parsed_index, entry_title, entry_lang)
checks_by_id = {}
for _, template_args in ipairs(found_templates_for_lang) do
checks_by_id[template_args.id or "*"] = fallback_check
end
end
return checks_by_id
end
local function _compute_checks_for_page(opts)
opts = opts or {}
local found_templates_for_lang = opts.found_templates_for_lang or {}
if #found_templates_for_lang == 0 then
return {}
end
local entry_title = opts.entry_title
local entry_lang = opts.entry_lang
local etymon_lang_code = opts.etymon_lang_code
if not entry_title or not entry_lang or not etymon_lang_code then
return {}
end
local sanitized_lang_section = _strip_refs(opts.lang_section or "")
return _build_checks_by_id(
sanitized_lang_section,
etymon_lang_code,
found_templates_for_lang,
entry_title,
entry_lang
)
end
function export.cache_page_checks(opts)
if not ENABLE_DESCENDANTS_TRACKING then
return {}
end
opts = opts or {}
local cache_table = opts.cached_descendants_checks
local lang_page_key = opts.lang_page_key
if not cache_table or not lang_page_key then
return {}
end
local checks_by_id = _compute_checks_for_page(opts)
_store_checks(cache_table, lang_page_key, checks_by_id, opts.redirected_from)
return checks_by_id
end
function export.get_lookup_check(opts)
if not ENABLE_DESCENDANTS_TRACKING then
return nil
end
opts = opts or {}
return _get_cached_check(
opts.cached_descendants_checks,
opts.is_toplevel,
opts.base_key,
_resolve_cache_id(opts.lookup)
)
end
function export.get_term_sync_flags(keyword, term_status, descendants_check)
if not ENABLE_DESCENDANTS_TRACKING then
return false, false
end
local should_track = term_status == "ok"
and TRACKABLE_KEYWORDS[keyword] == true
and descendants_check ~= nil
if not should_track then
return false, false
end
return descendants_check.missing_header or false, descendants_check.missing_entry or false
end
return export
njoa75ldlbxvifpj024ykipfx44kfcd
Module:etymon/data/text allowed
828
8138
55027
2026-09-30T19:20:31Z
Deadend0914
7211
Gesceop tramet þe hafaþ '--[=[ Languages and families that may use the {{etymon}} `text=` parameter (language-community consensus). ]=] return { -- Mode: "off" = disabled, "warn" = warn only, "error" = enforce. default_mode = "warn", langs = { ["ak"] = true, ["amf"] = true, ["bg"] = true, ["bnt-sab-pro"] = true, ["cs"] = true, ["en"] = true, ["eo"] = true, ["es"] = true, ["ext"] = true, ["fa"] = true, ["gmw-msc"] = true, ["hsb"] = true, ["iir-pro"] = t...'
55027
Scribunto
text/plain
--[=[
Languages and families that may use the {{etymon}} `text=` parameter (language-community consensus).
]=]
return {
-- Mode: "off" = disabled, "warn" = warn only, "error" = enforce.
default_mode = "warn",
langs = {
["ak"] = true,
["amf"] = true,
["bg"] = true,
["bnt-sab-pro"] = true,
["cs"] = true,
["en"] = true,
["eo"] = true,
["es"] = true,
["ext"] = true,
["fa"] = true,
["gmw-msc"] = true,
["hsb"] = true,
["iir-pro"] = true,
["jbo"] = true,
["jdt"] = true,
["la"] = true,
["mul"] = true,
["ota"] = true,
["pap"] = true,
["ps"] = true,
["ro"] = true,
["sco"] = true,
["sk"] = true,
["sw"] = true,
["tg"] = true,
["tl"] = true,
["tr"] = true,
["uk"] = true,
["uz"] = true,
["sl"] = true,
["rsk"] = true,
["zlw-ocs"] = true,
["zlw-osk"] = true,
["zle-ono"] = true,
["zle-ort"] = true,
["sla-pro"] = true,
["eu"] = true,
-- Austronesian
["map-pro"] = true,
["map-ata-pro"] = true,
["poz-pro"] = true,
["poz-btk-pro"] = true,
["poz-cet-pro"] = true,
["pqe-pro"] = true,
["poz-hce-pro"] = true,
["poz-oce-pro"] = true,
["poz-pol-pro"] = true,
["poz-pnp-pro"] = true,
["poz-pep-pro"] = true,
["poz-mic-pro"] = true,
["poz-lgx-pro"] = true,
["poz-msa-pro"] = true,
["poz-mcm-pro"] = true,
["cmc-pro"] = true,
["poz-mly-pro"] = true,
["poz-swa-pro"] = true,
["btk-pro"] = true,
["phi-pro"] = true,
["phi-kal-pro"] = true,
["poz-ssw-pro"] = true,
["dru-pro"] = true,
["crp-slb"] = true,
},
families = {
["ber"] = true, -- Berber
["dra"] = true, -- Dravidian
["inc"] = true, -- Indo-Aryan
["iir-nur"] = true, -- Nuristani
["mun"] = true, -- Munda
["roa-gap"] = true, -- Galician-Portuguese
["sem-ara"] = true, -- Aramaic
["sem-arb"] = true, -- Arabic
["tup"] = true, -- Tupian
["bnt-sab"] = true, -- Lechitic
["zlw-lch"] = true, -- Lechitic
},
}
d2t0jck7ixvesghw7yzk9gdr2q8prgn
Module:fun/trycall
828
8139
55028
2026-09-30T19:20:56Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local fun_is_callable_module = "Module:fun/isCallable" local error = error local format = string.format local pcall = pcall local type = type local function is_callable(...) is_callable = require(fun_is_callable_module) return is_callable(...) end local function catch_values(obj, success, ...) if success then return success, ... -- Error message will only take this exact form if `obj` is not callable, -- because it will contain a traceback if it was thr...'
55028
Scribunto
text/plain
local fun_is_callable_module = "Module:fun/isCallable"
local error = error
local format = string.format
local pcall = pcall
local type = type
local function is_callable(...)
is_callable = require(fun_is_callable_module)
return is_callable(...)
end
local function catch_values(obj, success, ...)
if success then
return success, ...
-- Error message will only take this exact form if `obj` is not callable,
-- because it will contain a traceback if it was thrown further up the
-- stack.
elseif (...) == format("attempt to call a %s value", type(obj)) then
return false
end
return error(...)
end
--[==[
A special form of {pcall()}, which returns {true} plus the result value(s) if {obj} is callable, or {false} if it isn't. Errors that occur within the called function are not protected.]==]
return function(obj, ...)
local callable = is_callable(obj, true)
if callable then
return true, obj(...)
elseif callable == false then
return false
end
-- If `callable` is nil, there's a protected metatable, so there's no way to
-- check without doing a protected call.
return catch_values(obj, pcall(obj, ...))
end
b1dd95sq861pept04po2rb74sr1t9y1
Module:etymon/text
828
8140
55029
2026-09-30T19:21:25Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local loader = require("Module:module loader") local M = loader.init({ require = { en_utilities = "Module:en-utilities", references = "Module:references", senseno = "Module:senseno", }, loadData = { data = "Module:etymon/data", }, }) function export.render(opts) opts = opts or {} local data_tree = opts.data_tree local format_term_func = opts.format_term_func local max_depth = opts.max_depth local stop_at_blue_link = opts.stop...'
55029
Scribunto
text/plain
local export = {}
local loader = require("Module:module loader")
local M = loader.init({
require = {
en_utilities = "Module:en-utilities",
references = "Module:references",
senseno = "Module:senseno",
},
loadData = {
data = "Module:etymon/data",
},
})
function export.render(opts)
opts = opts or {}
local data_tree = opts.data_tree
local format_term_func = opts.format_term_func
local max_depth = opts.max_depth
local stop_at_blue_link = opts.stop_at_blue_link
local curr_page = opts.curr_page
local nodot = opts.nodot and opts.nodot ~= "" and opts.nodot ~= "0"
local function explicit_dot_override()
if opts.dot == nil or opts.dot == false then
return nil
end
local d = mw.text.trim(tostring(opts.dot))
if d == "" or d == "." then
return nil
end
return d
end
local dot_override = explicit_dot_override()
local function find_deepest_last_part(tree)
if not tree or not tree.container_parts or #tree.container_parts == 0 then
return nil
end
local last_part = tree.container_parts[#tree.container_parts]
if last_part.continuation then
return find_deepest_last_part(last_part.continuation)
end
return last_part
end
local function apply_final_punctuation_override(tree, punct)
if not tree or punct == nil then
return
end
local last_part = find_deepest_last_part(tree)
if last_part then
last_part.punctuation = punct
end
end
local function apply_closing_punctuation_override(tree, is_last_segment)
if not is_last_segment then
return
end
local punct
if nodot then
punct = ""
elseif dot_override ~= nil then
punct = dot_override
else
return
end
apply_final_punctuation_override(tree, punct)
end
local function has_supplements()
return data_tree.supplements and #data_tree.supplements > 0
end
local stop_at_lang = opts.stop_at_lang
local stop_at_lang_or_bluelink = opts.stop_at_lang_or_bluelink
local lang_matches_stop_code = opts.lang_matches_stop_code
local stop_lang_reached = false
local function term_matches_stop_code(term_lang, stop_code)
if lang_matches_stop_code then
return lang_matches_stop_code(term_lang, stop_code)
end
return term_lang and term_lang:getCode() == stop_code
end
local children = data_tree.children
local function has_text_supplements()
if not data_tree.supplements then
return false
end
for _, supplement in ipairs(data_tree.supplements) do
if supplement.type == "doublet" and supplement.terms and #supplement.terms > 0 then
return true
end
if supplement.type == "etydate" and supplement.etydate_text and supplement.etydate_text ~= "" then
return true
end
end
return false
end
if (not children or #children == 0) and not has_text_supplements() then
if stop_at_lang then
return "", { stop_lang_reached = false }
end
return ""
end
local entry_lang = data_tree.lang
local function lowercase_glossary_link_display(wikitext)
return wikitext:gsub("(%[%[Appendix:Glossary#[^|]+|)([^%]])([^%]]*)%]%]", function(prefix, first, rest)
return prefix .. mw.ustring.lower(first) .. rest .. "]]"
end)
end
local function format_sl_senseid_intro(senseid, keyword_text, keyword_phrase, capitalize_senseno, is_uncertain)
local senseids = mw.text.split(senseid, "!!", true)
local senseno_parts = {}
for i, id in ipairs(senseids) do
id = mw.text.trim(id)
if id ~= "" then
table.insert(senseno_parts, M.senseno.link_text(entry_lang:getCode(), { id }, {
title = curr_page,
uc = (i == 1 and capitalize_senseno) or nil,
}))
end
end
if #senseno_parts == 0 then
return keyword_text, keyword_phrase
end
local senseno_text = mw.text.listToText(senseno_parts)
local glossary_link = (keyword_text or ""):gsub(" from$", "")
local glossary_lower = lowercase_glossary_link_display(glossary_link)
if #senseno_parts > 1 then
local plural_link = glossary_lower:gsub("|semantic loan%]%]", "|semantic loans]]")
if is_uncertain then
return senseno_text .. " are possibly " .. plural_link .. " from",
senseno_text .. " are possibly semantic loans from"
end
return senseno_text .. " are " .. plural_link .. " from",
senseno_text .. " are semantic loans from"
end
if is_uncertain then
return senseno_text .. " is possibly a " .. glossary_lower .. " from",
senseno_text .. " is possibly a semantic loan from"
end
return senseno_text .. " is a " .. glossary_lower .. " from",
senseno_text .. " is a semantic loan from"
end
local function get_term_refs(term, defined_here)
if defined_here and term.refs and next(term.refs) then
return M.references.format_references(term.refs)
end
return ""
end
-- Build a text part for a single term
local function build_term_part(term, current_lang, defined_here)
local text = ""
local new_lang = current_lang
local lang_changed = term.lang:getCanonicalName() ~= current_lang:getCanonicalName()
-- Use centralized format_term (handles suppress_term, unknown_term, and regular terms)
local term_text = format_term_func(term)
if lang_changed then
new_lang = term.lang
if term_text then
text = term.lang:makeWikipediaLink() .. " " .. term_text
elseif term.is_family then
text = M.en_utilities.add_indefinite_article(term.lang:makeWikipediaLink() .. " language", false)
else
-- suppress_term with language change: show only language
text = term.lang:makeWikipediaLink()
end
else
text = term_text or ""
end
return {
type = "term",
text = text,
refs = get_term_refs(term, defined_here),
lang = new_lang,
is_uncertain = term.is_uncertain or false,
}
end
-- Build text parts for a container
local function build_container_part(container, node, depth, allow_continuation, fallback_to_bluelink)
local keyword_info = container.keyword_info
local keyword_modifiers = container.keyword_modifiers or {}
local terms = container.terms or {}
if not keyword_info or #terms == 0 then
return nil
end
-- Skip building text part when invisible in text ("all", "text", or true)
local inv = keyword_info.invisible
if inv == "all" or inv == true or inv == "text" then
return nil
end
local is_group = keyword_info.is_group
local keyword_uncertain = keyword_modifiers.unc or false
-- Determine text and phrase (allowing for overrides)
local intro_text = keyword_info.text
local phrase = keyword_info.phrase
local new_sentence = keyword_info.new_sentence or false
if keyword_modifiers.text then
-- User-provided override: assumed to be lowercase
phrase = keyword_modifiers.text
-- Auto-capitalize for intro text (e.g., "derived from" -> "Derived from")
intro_text = mw.ustring.upper(phrase:sub(1, 1)) .. phrase:sub(2)
end
local defined_here = (node.page_of or curr_page) == curr_page
-- Get keyword references
local keyword_refs = ""
if defined_here and keyword_modifiers.refs and next(keyword_modifiers.refs) then
keyword_refs = M.references.format_references(keyword_modifiers.refs)
end
-- Build term parts
local term_parts = {}
local current_lang = node.lang
for _, term in ipairs(terms) do
local term_part = build_term_part(term, current_lang, defined_here)
if term_part.text ~= "" then
table.insert(term_parts, term_part)
current_lang = term_part.lang
end
end
-- Check uncertainty distribution
local uncertain_count = 0
for _, term_part in ipairs(term_parts) do
if term_part.is_uncertain then
uncertain_count = uncertain_count + 1
end
end
-- If keyword itself is uncertain, treat all terms as uncertain
local all_uncertain = keyword_uncertain or (uncertain_count == #term_parts and #term_parts > 0)
if is_group and uncertain_count > 0 then
all_uncertain = true
end
local has_mixed_uncertainty = not all_uncertain and uncertain_count > 0
-- Check if there are more steps (only if continuation is allowed)
local has_more_steps = false
local next_node = nil
local first_term = terms[1]
-- Check if we should stop at this language
local reached_stop_lang = false
if stop_at_lang then
for _, term in ipairs(terms) do
if term.lang and term_matches_stop_code(term.lang, stop_at_lang) then
reached_stop_lang = true
stop_lang_reached = true
break
end
end
elseif stop_at_lang_or_bluelink then
-- Check if we should stop at this language, or at the first bluelink if it's a redlink
for _, term in ipairs(terms) do
if term.lang and term_matches_stop_code(term.lang, stop_at_lang_or_bluelink) then
if first_term.status == M.data.STATUS.OK then
reached_stop_lang = true
else
fallback_to_bluelink = true
end
break
end
end
if fallback_to_bluelink and first_term.status == M.data.STATUS.OK then
reached_stop_lang = true
end
end
if allow_continuation and not is_group and #terms == 1 and not reached_stop_lang then
local first_term_children = first_term.children
if first_term_children and #first_term_children > 0 and (not max_depth or depth < max_depth) then
local next_container = first_term_children[1]
local next_keyword_info = next_container and next_container.keyword_info
if not (next_keyword_info and next_keyword_info.invisible) then
if stop_at_blue_link then
if first_term.status ~= M.data.STATUS.OK then
has_more_steps = true
next_node = first_term
end
else
has_more_steps = true
next_node = first_term
end
end
end
end
return {
type = "container",
intro_text = intro_text,
phrase = phrase,
senseid = keyword_modifiers.senseid,
sl_keyword_text = keyword_modifiers.senseid and keyword_info.text or nil,
is_uncertain = all_uncertain,
has_mixed_uncertainty = has_mixed_uncertainty,
term_parts = term_parts,
is_group = is_group,
has_more_steps = has_more_steps,
next_node = next_node,
new_sentence = new_sentence,
separate_clause = keyword_info.separate_clause or false,
conj = keyword_modifiers.conj or keyword_info.default_conj, -- custom conjunction: "and", "or", "and/or", etc.
lit = keyword_modifiers.lit,
keyword_refs = keyword_refs,
fallback_to_bluelink = fallback_to_bluelink,
}
end
-- Build the full tree of text parts
local function build_text_tree(node, depth, allow_continuation, fallback_to_bluelink)
local containers = node.children
if not containers or #containers == 0 then
return nil
end
local container_parts = {}
-- Count containers that get a text part (invisible in text = "all", "text", or true)
local visible_container_count = 0
for _, container in ipairs(containers) do
local keyword_info = container.keyword_info
local inv = keyword_info and keyword_info.invisible
if not (inv == "all" or inv == true or inv == "text") then
visible_container_count = visible_container_count + 1
end
end
-- If there are multiple visible containers at this level, don't allow continuation for any
local has_multiple_containers = visible_container_count > 1
local should_allow_continuation = allow_continuation and not has_multiple_containers
for _, container in ipairs(containers) do
local part = build_container_part(container, node, depth, should_allow_continuation, fallback_to_bluelink)
if part then
-- Recursively build children if there are more steps
if part.has_more_steps and part.next_node then
part.continuation = build_text_tree(part.next_node, depth + 1, true, part.fallback_to_bluelink)
end
table.insert(container_parts, part)
end
end
if #container_parts == 0 then
return nil
end
return {
type = "tree",
container_parts = container_parts,
depth = depth,
}
end
-- Check if tree has mixed joining types
local function container_join_kind(part)
if part.type == "etydate" then
return nil
end
if part.new_sentence or part.separate_clause then
return "supplement"
end
return "or_join"
end
local function check_complexity(tree)
if not tree then return nil end
local parts = tree.container_parts
if #parts <= 1 then
-- Single container
if parts[1] and parts[1].continuation then
return check_complexity(parts[1].continuation)
end
return nil
end
-- Or-join containers must precede any supplemental (calque-like / influence) containers.
local seen_supplement = false
for _, part in ipairs(parts) do
local kind = container_join_kind(part)
if kind == "supplement" then
seen_supplement = true
elseif kind == "or_join" and seen_supplement then
error(
"Cannot generate etymology text: a main derivation step cannot follow a calque, semantic loan, or influence clause in the same list.")
end
end
for _, part in ipairs(parts) do
if part.continuation then
check_complexity(part.continuation)
end
end
return nil
end
-- Analyze tree and assign punctuation
local function analyze_punctuation(tree, is_toplevel)
if not tree then return end
local parts = tree.container_parts
local num_parts = #parts
for i, part in ipairs(parts) do
local is_first = (i == 1)
local is_last = (i == num_parts)
local next_part = parts[i + 1]
-- Analyze term punctuation within container
if part.term_parts then
-- Terms use Oxford comma style: "A, B, or C"
-- Custom conjunction can be specified via conj modifier (e.g., "and/or", "and")
local num_terms = #part.term_parts
local term_conj = part.conj or "or" -- default to "or"
for j, term_part in ipairs(part.term_parts) do
local is_last_term = (j == num_terms)
if part.is_group then
-- Group: terms joined with " + "
term_part.joiner = is_last_term and "" or " + "
elseif num_terms > 1 then
-- Multiple terms not in a group: Oxford comma style
if is_last_term then
term_part.joiner = ""
elseif j == num_terms - 1 then
-- Second to last term
if num_terms == 2 then
term_part.joiner = " " .. term_conj .. " "
else
term_part.joiner = ", " .. term_conj .. " "
end
else
term_part.joiner = ", "
end
else
-- Single term
term_part.joiner = ""
end
end
end
-- Determine container punctuation based on what comes next
if part.continuation then
-- Has continuation
part.punctuation = ","
-- Recursively analyze continuation
analyze_punctuation(part.continuation, false)
elseif is_last then
-- Last container at this level (may still continue in part.continuation)
part.punctuation = "."
elseif next_part and next_part.new_sentence then
-- Next container starts a new sentence
part.punctuation = "."
elseif next_part and next_part.separate_clause then
-- Next container is a separate clause
part.punctuation = ","
else
-- Not last, next is joined with "or"
-- Containers use repeated "or" style: "A, or B, or C"
part.punctuation = ","
end
-- Determine joiner to next part
-- Containers use repeated "or" style: ", or" between each
-- Custom conjunction can be specified via conj modifier
local container_conj = part.conj or "or" -- default to "or"
if not is_last then
if next_part and next_part.new_sentence then
-- New sentence
part.joiner = " "
elseif next_part and next_part.separate_clause then
-- Separate clause
part.joiner = " "
else
-- Same sentence: use custom conjunction or default "or"
part.joiner = " " .. container_conj .. " "
end
else
part.joiner = ""
end
-- Determine intro formatting
-- Capitalize if first at top level, OR if this container starts a new sentence
if (is_first and is_toplevel) or part.new_sentence then
part.intro_capitalized = true
part.use_full_intro = true
else
part.intro_capitalized = false
part.use_full_intro = false
end
end
end
-- Assemble text from analyzed tree
local function assemble_text(tree)
if not tree then return "" end
local result = ""
for i, part in ipairs(tree.container_parts) do
if part.type == "etydate" then
result = result .. part.etydate_text
if part.punctuation and part.punctuation ~= "" then
result = result .. part.punctuation
end
if part.etydate_refs and next(part.etydate_refs) then
result = result .. M.references.format_references(part.etydate_refs)
end
if part.joiner and part.joiner ~= "" then
result = result .. part.joiner
end
else
-- Build intro
local intro_text = part.intro_text
local phrase = part.phrase
if part.senseid then
intro_text, phrase = format_sl_senseid_intro(
part.senseid,
part.sl_keyword_text,
part.phrase,
part.intro_capitalized,
part.is_uncertain
)
end
local intro
if part.use_full_intro then
if part.is_uncertain and not part.senseid then
intro = "Possibly " .. phrase
else
intro = intro_text
end
else
if part.is_uncertain and not part.senseid then
intro = "possibly " .. phrase
else
intro = phrase
end
end
result = result .. intro
-- Build terms
if #part.term_parts > 0 then
result = result .. " "
for j, term_part in ipairs(part.term_parts) do
-- Add "possibly" prefix for uncertain terms when there's mixed uncertainty
if part.has_mixed_uncertainty and term_part.is_uncertain then
result = result .. "possibly "
end
result = result .. term_part.text
-- Add joiner between terms
if term_part.joiner ~= "" then
-- Check if joiner contains comma (punctuation)
local comma_pos = term_part.joiner:find(",")
if comma_pos then
-- Add up to and including comma
result = result .. term_part.joiner:sub(1, comma_pos)
-- Add refs after comma
if term_part.refs ~= "" then
result = result .. term_part.refs
end
-- Add rest of joiner
result = result .. term_part.joiner:sub(comma_pos + 1)
else
-- No comma, add refs before joiner
if term_part.refs ~= "" then
result = result .. term_part.refs
end
result = result .. term_part.joiner
end
end
end
-- For the last term, add punctuation then refs
local last_term = part.term_parts[#part.term_parts]
if last_term and last_term.joiner == "" then
if part.punctuation ~= "" then
-- If we have literal text, punctuation goes AFTER it
if part.lit then
-- Add refs first (attached to term)
if last_term.refs ~= "" then
result = result .. last_term.refs
end
-- Add keyword refs
if part.keyword_refs and part.keyword_refs ~= "" then
result = result .. part.keyword_refs
end
-- Add literal text
result = result .. ", literally “" .. part.lit .. "”"
-- Add punctuation
result = result .. part.punctuation
else
-- Normal behavior: punctuation then refs
result = result .. part.punctuation
if last_term.refs ~= "" then
result = result .. last_term.refs
end
-- Add keyword refs after term refs
if part.keyword_refs and part.keyword_refs ~= "" then
result = result .. part.keyword_refs
end
end
else
-- No punctuation
if last_term.refs ~= "" then
result = result .. last_term.refs
end
-- Add keyword refs
if part.keyword_refs and part.keyword_refs ~= "" then
result = result .. part.keyword_refs
end
-- Add literal text if present (even without punctuation)
if part.lit then
result = result .. ", literally “" .. part.lit .. "”"
end
end
end
else
-- No terms, just add punctuation and keyword refs
if part.punctuation ~= "" then
result = result .. part.punctuation
end
-- Add keyword refs even when there are no terms
if part.keyword_refs and part.keyword_refs ~= "" then
result = result .. part.keyword_refs
end
end
-- Add continuation
if part.continuation then
result = result .. " " .. assemble_text(part.continuation)
end
-- Add joiner to next container
if part.joiner ~= "" then
result = result .. part.joiner
end
end
end
return result
end
local text_tree = build_text_tree(data_tree, 1, true, false)
-- Supplements (doublets, etydate, …) are rendered outside the main derivation text tree.
local function assemble_supplements()
if not data_tree.supplements then
return ""
end
local chunks = {}
local pending_trees = {}
local function flush_pending_trees()
local num = #pending_trees
for i, supplement_tree in ipairs(pending_trees) do
analyze_punctuation(supplement_tree, true)
apply_closing_punctuation_override(supplement_tree, i == num)
local chunk = assemble_text(supplement_tree)
if chunk ~= "" then
table.insert(chunks, chunk)
end
end
pending_trees = {}
end
for _, supplement in ipairs(data_tree.supplements) do
local supplement_tree
if supplement.type == "doublet" and supplement.config
and supplement.terms and #supplement.terms > 0 then
local config = supplement.config
local term_parts = {}
for _, term in ipairs(supplement.terms) do
local term_part = build_term_part(term, entry_lang, true)
if term_part.text ~= "" then
table.insert(term_parts, term_part)
end
end
if #term_parts == 0 then
supplement_tree = nil
else
supplement_tree = {
type = "tree",
container_parts = {
{
type = "doublet",
intro_text = config.text,
phrase = config.phrase,
term_parts = term_parts,
conj = config.default_conj or "and",
new_sentence = true,
},
},
depth = 1,
}
end
elseif supplement.type == "etydate" and supplement.etydate_text and supplement.etydate_text ~= "" then
supplement_tree = {
type = "tree",
container_parts = {
{
type = "etydate",
etydate_text = supplement.etydate_text,
etydate_refs = supplement.etydate_refs,
new_sentence = true,
},
},
depth = 1,
}
end
if supplement_tree then
table.insert(pending_trees, supplement_tree)
end
end
flush_pending_trees()
return table.concat(chunks, " ")
end
if not text_tree then
local supplement_text = assemble_supplements()
if supplement_text == "" then
if stop_at_lang then
return "", { stop_lang_reached = false }
end
return ""
end
if stop_at_lang then
return supplement_text, { stop_lang_reached = false }
end
return supplement_text
end
local rendered = ""
if text_tree then
check_complexity(text_tree)
analyze_punctuation(text_tree, true)
apply_closing_punctuation_override(text_tree, not has_supplements())
rendered = assemble_text(text_tree)
end
local supplement_text = assemble_supplements()
if supplement_text ~= "" then
if rendered ~= "" then
rendered = rendered .. " " .. supplement_text
else
rendered = supplement_text
end
end
if stop_at_lang then
return rendered, { stop_lang_reached = stop_lang_reached }
end
return rendered
end
return export
55fpa9rl4jqvgxk9ggp38excqfa50nk
Module:etymon/categories
828
8141
55030
2026-09-30T19:21:49Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local M = require("Module:module loader").init({ require = { etymology = "Module:etymology", affix = "Module:affix", etymology_specialized = "Module:etymology/specialized", utilities = "Module:utilities", roots = "Module:roots", }, loadData = { data = "Module:etymon/data", }, }) -- Evaluate whether a keyword is transitive for a given term local function is_transitive(transitive_mode, page_lang, term_lang) if transitive_mode ==...'
55030
Scribunto
text/plain
local export = {}
local M = require("Module:module loader").init({
require = {
etymology = "Module:etymology",
affix = "Module:affix",
etymology_specialized = "Module:etymology/specialized",
utilities = "Module:utilities",
roots = "Module:roots",
},
loadData = {
data = "Module:etymon/data",
},
})
-- Evaluate whether a keyword is transitive for a given term
local function is_transitive(transitive_mode, page_lang, term_lang)
if transitive_mode == M.data.TRANSITIVE.ALWAYS then
return true
elseif transitive_mode == M.data.TRANSITIVE.NEVER then
return false
elseif transitive_mode == M.data.TRANSITIVE.CROSS_LANG then
return page_lang:getCode() ~= term_lang:getCode()
elseif transitive_mode == M.data.TRANSITIVE.CROSS_LANG_NO_INTERNAL_SOURCE then
return page_lang:getCode() ~= term_lang:getCode()
end
error("Unknown transitive mode: " .. tostring(transitive_mode))
end
-- Get keyword config with language-specific overrides
local function get_keyword_config(keyword, lang_exc)
local base_config = M.data.keywords[keyword]
if not base_config then
return nil -- Invalid keyword
end
local overrides = lang_exc and lang_exc.keyword_overrides and lang_exc.keyword_overrides[keyword]
if not overrides then
return base_config
end
-- Merge overrides into base config
local merged = {}
for k, v in pairs(base_config) do
merged[k] = v
end
for k, v in pairs(overrides) do
merged[k] = v
end
return merged
end
function export.get_cat_name(source)
local _, cat_name = M.etymology.get_display_and_cat_name(source, true)
return cat_name
end
-- Normalize affix type aliases
local function add_category(categories, cat_name, sort_key, sort_base)
if categories[cat_name] == nil then
categories[cat_name] = {
sort_key = sort_key,
sort_base = sort_base,
}
return
end
local existing = categories[cat_name]
if existing.sort_key == nil and sort_key ~= nil then
existing.sort_key = sort_key
end
if existing.sort_base == nil and sort_base ~= nil then
existing.sort_base = sort_base
end
end
-- Collect affix categories from top-level group containers
local function collect_affix_categories(node, page_lang, available_etymon_ids, senseid_parent_etymon, lang_exc)
local parts = {}
local part_index = 1
for _, container in ipairs(node.children or {}) do
local config = container.keyword_info
if config and config.affix_categories then
for _, term in ipairs(container.terms or {}) do
if not term.unknown_term then
local part_data = {
term = term.title,
tr = term.tr,
ts = term.ts,
alt = term.alt,
itemno = part_index,
orig_index = part_index
}
-- Determine affix type: explicit aftype > pos=root > auto-detect
local aftype = term.aftype
if aftype then
aftype = M.data.aftype_aliases[aftype] or aftype
part_data.type = aftype
elseif term.args and term.args.pos and term.args.pos == "root" then
part_data.type = "non-affix"
end
if term.lang:getCode() ~= page_lang:getCode() then
part_data.lang = term.lang
end
local target_ids = available_etymon_ids[term.target_key]
local has_multiple_ids = target_ids and #target_ids > 1
local id_exists_in_disambiguation = false
local matched_id = nil
-- Count available senseids for the target page
local senseid_count = 0
local target_prefix = term.target_key .. ":"
if senseid_parent_etymon then
for key, _ in pairs(senseid_parent_etymon) do
if key:sub(1, #target_prefix) == target_prefix then
senseid_count = senseid_count + 1
end
end
end
local has_multiple_senseids = senseid_count > 1
if term.id then
-- Check if user provided a valid senseid
local senseid_key = term.target_key .. ":" .. term.id
if senseid_parent_etymon and senseid_parent_etymon[senseid_key] then
if has_multiple_senseids then
-- Ambiguous senseid: use senseid
matched_id = term.id
id_exists_in_disambiguation = true
elseif has_multiple_ids then
-- Unique senseid but ambiguous etymon: use etymon ID
matched_id = term.etymon_id or term.id
id_exists_in_disambiguation = true
end
else
-- Check if user provided a valid etymon ID
if has_multiple_ids and target_ids then
for _, id_data in ipairs(target_ids) do
local stored_id = type(id_data) == "table" and id_data.id or id_data
if stored_id == term.id then
-- Ambiguous etymon: use etymon ID
id_exists_in_disambiguation = true
matched_id = term.id
break
end
end
end
-- Fallback: check resolved etymon_id (e.g. from previous steps)
if not id_exists_in_disambiguation and has_multiple_ids and term.etymon_id and target_ids then
for _, id_data in ipairs(target_ids) do
local stored_id = type(id_data) == "table" and id_data.id or id_data
if stored_id == term.etymon_id then
id_exists_in_disambiguation = true
matched_id = term.etymon_id
break
end
end
end
end
end
-- Use the matched ID if found
if term.override or id_exists_in_disambiguation then
part_data.id = matched_id or term.id
end
table.insert(parts, part_data)
part_index = part_index + 1
end
end
end
end
if #parts == 0 then return {} end
local affix_data = {
lang = page_lang,
parts = parts,
pos = "term",
sort_key = nil,
}
if #parts == 1 then
affix_data.allow_no_affixes_or_compounds = true
end
local affix_categories = M.affix.get_affix_categories_only(affix_data)
local result = {}
for _, cat in ipairs(affix_categories) do
if type(cat) == "table" then
table.insert(result, { cat = cat.cat, sort_key = cat.sort_key, sort_base = cat.sort_base })
else
table.insert(result, { cat = cat })
end
end
return result
end
local function lang_is_source(page_lang, source)
return page_lang:getCode() == source:getCode() or page_lang:hasParent(source)
end
local function is_borrowing_keyword_config(config)
return config and (config.borrowing_type or config.specialized_borrowing)
end
local function add_reborrow_category(categories, page_lang)
local lang_name = page_lang:getFullName()
add_category(categories, lang_name .. " terms borrowed back into " .. lang_name)
end
local function borrow_returns_to_page_lang(page_lang, source, in_foreign_branch)
if not in_foreign_branch then
return false
end
if source:getFullCode() == page_lang:getFullCode() then
return true
end
return page_lang:hasParent(source)
end
local function node_borrows_from_lang(node, page_lang, visited, in_foreign_branch)
visited = visited or {}
if not node or visited[node] then
return false
end
visited[node] = true
if node.is_duplicate then
if node.duplicate_of then
return node_borrows_from_lang(node.duplicate_of, page_lang, visited, in_foreign_branch)
end
return false
end
local node_is_foreign = in_foreign_branch
or (node.lang and node.lang:getFullCode() ~= page_lang:getFullCode())
for _, container in ipairs(node.children or {}) do
if is_borrowing_keyword_config(container.keyword_info) then
for _, child_term in ipairs(container.terms or {}) do
if borrow_returns_to_page_lang(page_lang, child_term.lang, node_is_foreign) then
return true
end
end
end
for _, child_term in ipairs(container.terms or {}) do
if child_term.status == M.data.STATUS.OK or child_term.status == M.data.STATUS.INLINE then
if node_borrows_from_lang(child_term, page_lang, visited, node_is_foreign) then
return true
end
end
end
end
return false
end
local function should_add_reborrow_category(page_lang, term)
if page_lang:getCode() == term.lang:getCode() then
return false
end
if term.status ~= M.data.STATUS.OK and term.status ~= M.data.STATUS.INLINE then
return false
end
return node_borrows_from_lang(term, page_lang, {}, false)
end
-- Add borrowing-related categories (top-level only)
local function collect_borrowing_categories(categories, page_lang, term, config, check_reborrow_path)
if check_reborrow_path and should_add_reborrow_category(page_lang, term) then
add_reborrow_category(categories, page_lang)
end
if config.borrowing_type == "borrowed" and not lang_is_source(page_lang, term.lang) then
local temp_categories = {}
M.etymology.insert_borrowed_cat(temp_categories, page_lang, term.lang)
for _, cat in ipairs(temp_categories) do
add_category(categories, cat)
end
end
if config.specialized_borrowing and not lang_is_source(page_lang, term.lang) then
local result = M.etymology_specialized.specialized_borrowing {
bortype = config.specialized_borrowing,
lang = page_lang,
sources = { term.lang },
terms = { { lang = term.lang, term = "-" } },
notext = true,
nocat = false,
}
for cat_name in result:gmatch("%[%[Category:([^%]]+)%]%]") do
add_category(categories, cat_name)
end
end
end
-- Add source-based derivation categories (top-level only)
local function collect_source_derivation_categories(categories, page_lang, term, config)
if not config.source_category_type then
return
end
local temp_categories = {}
M.etymology.insert_source_cat_get_display {
lang = page_lang,
source = term.lang,
categories = temp_categories,
borrowing_type = config.source_category_type,
nocat = false,
}
for _, cat in ipairs(temp_categories) do
add_category(categories, cat)
end
end
-- Add source language categories
local function collect_source_categories(categories, page_lang, term, chain, get_norm_lang_func)
if page_lang:getCode() == get_norm_lang_func(term.lang):getCode() then
return
end
local temp_categories = {}
M.etymology.insert_source_cat_get_display {
lang = page_lang,
source = term.lang,
categories = temp_categories,
nocat = false,
}
for _, cat in ipairs(temp_categories) do
add_category(categories, cat)
end
if chain.inherited then
temp_categories = {}
M.etymology.insert_source_cat_get_display {
lang = page_lang,
source = term.lang,
categories = temp_categories,
borrowing_type = "terms inherited",
nocat = false,
}
for _, cat in ipairs(temp_categories) do
add_category(categories, cat)
end
end
end
-- Add root/word categories
local function collect_pos_categories(categories, page_lang, root_title, term, available_etymon_ids, chain,
get_norm_lang_func, lang_exc, keyword)
local pos_types = { root = "root", word = "word" }
-- Determine pos: from term's postype, keyword's pos_override, or args.pos
local pos
local config = get_keyword_config(keyword, lang_exc)
if term.postype then
-- Term-level postype modifier takes highest priority
pos = term.postype
elseif config and config.pos_override then
pos = config.pos_override
elseif type(term.args) == "table" and term.args.pos then
pos = term.args.pos
end
local pos_type = pos_types[pos]
if not pos_type or term.unknown_term then
return
end
-- Skip root/word categories for descendants of affix groups
-- if pos_type then
-- return
-- end
local same_language = get_norm_lang_func(page_lang):getFullCode() == get_norm_lang_func(term.lang):getFullCode()
-- Skip self-references
if same_language and root_title == term.title then
return
end
local entry_name
if pos_type == "root" then
entry_name = term.title
M.roots.assert_root(term.lang, entry_name)
else
entry_name = term.lang:makeEntryName(term.title)
end
local lang_name = page_lang:getCanonicalName()
local cat_name
if chain.passed_through then
local etymon_lang_name = export.get_cat_name(term.lang)
cat_name = lang_name .. " terms derived from the " .. etymon_lang_name .. " " .. pos_type .. " " .. entry_name
else
cat_name = lang_name .. " terms belonging to the " .. pos_type .. " " .. entry_name
end
-- Add ID disambiguation if needed (for roots/words: use etymon_id if resolved via senseid, otherwise use id)
local target_ids = available_etymon_ids[term.target_key]
local effective_id = term.etymon_id or term.id -- etymon_id if senseid, otherwise id is already an etymon id
if target_ids and effective_id then
local same_pos_count = 0
for _, id_data in ipairs(target_ids) do
if type(id_data) == "table" and id_data.pos == pos then
same_pos_count = same_pos_count + 1
end
end
if same_pos_count > 1 then
cat_name = cat_name .. " (" .. effective_id .. ")"
end
end
add_category(categories, cat_name)
end
-- Compute chain state for a term based on parent chain and keyword config
-- Hyphen patterns for affix detection (regular hyphen + script-specific)
local AFFIX_HYPHEN_PATTERN = "[%-%־ـ᠊]" -- regular hyphen, Hebrew maqqef, Arabic tatweel, Mongolian hyphen
-- Check if a term is an actual affix (not a non-affix member of an affix group)
local function is_actual_affix(term)
-- Check explicit aftype modifier
if term.aftype then
local normalized = M.data.aftype_aliases[term.aftype] or term.aftype
return normalized ~= "non-affix"
end
-- Check if pos=root (treated as non-affix)
if term.args and term.args.pos and term.args.pos == "root" then
return false
end
-- Auto-detect by hyphen: prefix ends with -, suffix starts with -, etc.
if term.title then
local title = term.title
-- Strip leading * for reconstructed terms before checking hyphens
title = title:gsub("^%*", "")
-- Check for hyphens at start or end (handles script-specific hyphens too)
if title:match("^" .. AFFIX_HYPHEN_PATTERN) or title:match(AFFIX_HYPHEN_PATTERN .. "$") then
return true
end
end
-- Default: not an affix
return false
end
local function compute_category_chain(parent_chain, config, page_lang, term_lang, get_norm_lang_func, parent_term_lang, term)
-- Track if we're inside an actual affix (for suppressing root categories on descendants)
-- Only set if the term is an actual affix (prefix, suffix, etc.), not a non-affix member
local inside_affix = parent_chain.inside_affix
if config.affix_categories and term and is_actual_affix(term) then
inside_affix = true
end
-- If no_child_categories is set, disable everything
if config.no_child_categories then
return {
passed_through = parent_chain.passed_through or page_lang:getCode() ~= get_norm_lang_func(term_lang):getCode(),
inherited = false,
source = false,
pos = false,
recurse = false,
inside_affix = inside_affix,
}
end
local term_is_transitive = is_transitive(config.transitive, page_lang, term_lang)
local new_source = parent_chain.source and term_is_transitive
-- For CROSS_LANG_NO_INTERNAL_SOURCE: track internal derivation language context
-- Check if this term is internal relative to parent term's language (if parent_term_lang provided)
-- or relative to page language (if no parent_term_lang)
local internal_lang = parent_chain.internal_lang
local is_internal_in_context = false
if config.transitive == M.data.TRANSITIVE.CROSS_LANG_NO_INTERNAL_SOURCE then
local check_lang = parent_term_lang or page_lang
local term_lang_code = get_norm_lang_func(term_lang):getCode()
local check_lang_code = get_norm_lang_func(check_lang):getCode()
if internal_lang then
-- Already in an internal derivation context: check if this term is also internal
is_internal_in_context = term_lang_code == internal_lang
else
-- Check if this term is internal relative to parent term (or page if no parent)
is_internal_in_context = term_lang_code == check_lang_code
end
end
-- Source chain behavior for CROSS_LANG_NO_INTERNAL_SOURCE
if config.transitive == M.data.TRANSITIVE.CROSS_LANG_NO_INTERNAL_SOURCE then
if is_internal_in_context then
-- Internal derivation
new_source = false
internal_lang = get_norm_lang_func(term_lang):getCode()
else
-- Cross-language
new_source = parent_chain.source and term_is_transitive
internal_lang = nil
end
end
local new_pos = parent_chain.pos
return {
passed_through = parent_chain.passed_through or page_lang:getCode() ~= get_norm_lang_func(term_lang):getCode(),
inherited = parent_chain.inherited and config.inherited_chain,
source = new_source,
pos = new_pos,
internal_lang = internal_lang,
recurse = new_source or new_pos,
inside_affix = inside_affix,
}
end
function export.render(opts)
opts = opts or {}
local data_tree = opts.data_tree
local page_lang = opts.page_lang
local available_etymon_ids = opts.available_etymon_ids
local senseid_parent_etymon = opts.senseid_parent_etymon
local get_norm_lang_func = opts.get_norm_lang_func
local lang_exc = opts.lang_exc
local categories = {}
local seen = {}
local lang_name = page_lang:getCanonicalName()
local root_title = data_tree.title
-- Collect the tree recursively
local function collect(node, parent_chain, is_toplevel)
-- Avoid processing same node twice
if not node.unknown_term and node.title then
local key = node.lang:getFullCode() .. ":" .. (node.title or "") .. ":" .. (node.id or "")
if seen[key] then return end
seen[key] = true
end
-- Collect affix categories at top level only
if is_toplevel then
local affix_cats = collect_affix_categories(node, page_lang, available_etymon_ids, senseid_parent_etymon, lang_exc)
for _, cat in ipairs(affix_cats) do
add_category(categories, lang_name .. " " .. cat.cat, cat.sort_key, cat.sort_base)
end
if node.supplements then
for _, supplement in ipairs(node.supplements) do
local config = supplement.config
if config and config.toplevel_category then
add_category(categories, lang_name .. " " .. config.toplevel_category)
end
end
end
end
-- Process each container
for _, container in ipairs(node.children or {}) do
local keyword = container.keyword
local config = get_keyword_config(keyword, lang_exc)
-- Skip invalid keywords
if config then
-- Process each term in the container
for _, term in ipairs(container.terms or {}) do
local term_chain = compute_category_chain(parent_chain, config, page_lang, term.lang, get_norm_lang_func, node.lang, term)
local no_child_categories = config.no_child_categories == true
local term_is_transitive = is_transitive(config.transitive, page_lang, term.lang)
-- Top-level only processing
if is_toplevel then
-- Missing/ambiguous etymon tracking
if not term.unknown_term and (term.status == M.data.STATUS.MISSING or term.status == M.data.STATUS.REDLINK) then
add_category(categories, lang_name .. " entries referencing missing etymons")
end
if not term.unknown_term and term.status == M.data.STATUS.AMBIGUOUS then
add_category(categories, lang_name .. " entries referencing ambiguous etymons")
end
if term.missing_descendants_header then
add_category(categories, lang_name .. " entries referencing etymons without Descendants sections")
end
if term.missing_descendants_entry then
add_category(categories, lang_name .. " entries referencing etymons without this term in Descendants sections")
end
-- Top-level category (e.g., "undefined derivations")
if config.toplevel_category then
add_category(categories, lang_name .. " " .. config.toplevel_category)
end
-- Borrowing categories (bor, lbor, slbor, ubor, obor)
if config.borrowing_type or config.specialized_borrowing then
collect_borrowing_categories(categories, page_lang, term, config, true)
end
-- Borrowing categories from <bor>, <lbor>, or <slbor> modifiers on affix-group terms
local kw_config = M.data.keywords[keyword]
if kw_config and kw_config.affix_categories then
if term.bor then
local bor_config = { borrowing_type = "borrowed" }
collect_borrowing_categories(categories, page_lang, term, bor_config, true)
elseif term.lbor then
local bor_config = { specialized_borrowing = "learned" }
collect_borrowing_categories(categories, page_lang, term, bor_config, true)
elseif term.slbor then
local bor_config = { specialized_borrowing = "semi-learned" }
collect_borrowing_categories(categories, page_lang, term, bor_config, true)
end
end
-- Source-based derivation categories (sl, calque, pcal)
if config.source_category_type then
collect_source_derivation_categories(categories, page_lang, term, config)
end
-- Skip all child categorisation if no_child_categories is set
if not no_child_categories then
-- Source categories only if transitive
if term_is_transitive then
collect_source_categories(categories, page_lang, term, term_chain, get_norm_lang_func)
end
-- Pos categories always (unless no_child_categories)
collect_pos_categories(categories, page_lang, root_title, term, available_etymon_ids, term_chain,
get_norm_lang_func, lang_exc, keyword)
end
else
-- Below top level, respect the parent chain
if parent_chain.source then
collect_source_categories(categories, page_lang, term, term_chain, get_norm_lang_func)
end
if parent_chain.pos then
collect_pos_categories(categories, page_lang, root_title, term, available_etymon_ids, term_chain,
get_norm_lang_func, lang_exc, keyword)
end
end
-- Recurse into term's children if needed and status allows
if term_chain.recurse and (term.status == M.data.STATUS.OK or term.status == M.data.STATUS.INLINE) then
collect(term, term_chain, false)
end
end
end
end
end
-- Initial chain state
local initial_chain = {
passed_through = false,
inherited = true,
source = true,
pos = true,
internal_lang = nil,
recurse = true,
inside_affix = false,
}
collect(data_tree, initial_chain, true)
local cat_list = {}
for cat_name, sort_data in pairs(categories) do
if sort_data.sort_key ~= nil or sort_data.sort_base ~= nil then
table.insert(cat_list, {
name = cat_name,
sort_key = sort_data.sort_key,
sort_base = sort_data.sort_base,
})
else
table.insert(cat_list, cat_name)
end
end
return cat_list
end
function export.build(opts)
opts = opts or {}
local categories = {}
if not opts.suppress_categories and not opts.nocat then
categories = export.render({
data_tree = opts.data_tree,
page_lang = opts.page_lang,
available_etymon_ids = opts.available_etymon_ids,
senseid_parent_etymon = opts.senseid_parent_etymon,
get_norm_lang_func = opts.get_norm_lang_func,
lang_exc = opts.lang_exc,
})
end
local page_lang = opts.page_lang
if not page_lang then
return categories
end
local lang_name = page_lang:getCanonicalName()
table.insert(categories, "Pages with etymon")
table.insert(categories, lang_name .. " entries with etymon")
if opts.tree then
table.insert(categories, "Pages with etymology trees")
table.insert(categories, lang_name .. " entries with etymology trees")
end
if opts.text then
table.insert(categories, lang_name .. " entries with etymology texts")
end
if opts.exnihilo then
table.insert(categories, lang_name .. " terms coined ex nihilo")
end
if opts.toplevel_has_inline_etymology then
table.insert(categories, "Pages with inline etymon for redlinks")
end
if opts.toplevel_redundant_etymology then
table.insert(categories, "Pages with redundant inline etymon")
end
if opts.toplevel_idless_etymon then
table.insert(categories, "Pages using etymon with no ID")
end
if opts.has_mismatched_id then
table.insert(categories, lang_name .. " entries referencing etymons with mismatched IDs")
end
if opts.linked_page_multiple_etymons_idless then
table.insert(categories,
lang_name .. " entries referencing pages with multiple etymons missing IDs")
end
if opts.linked_page_partial_etymology_sections then
table.insert(categories,
lang_name .. " entries referencing pages with etymology sections missing etymons")
end
if opts.text_stop_lang_missing then
table.insert(categories, "Pages with etymology text stop language not in chain")
table.insert(categories, lang_name .. " entries with etymology text stop language not in chain")
end
return categories
end
function export.format(entries, lang)
if type(entries) ~= "table" or #entries == 0 then
return ""
end
local parts = {}
for _, category in ipairs(entries) do
if type(category) == "table" and type(category.name) == "string" then
table.insert(parts, M.utilities.format_categories({ category.name }, lang, category.sort_key, category.sort_base))
elseif type(category) == "string" then
table.insert(parts, M.utilities.format_categories({ category }, lang))
end
end
return table.concat(parts)
end
return export
l7p4kvhjzzkv6mvr0d3ud5xek882jvl
Bysen:goh-noun
10
8142
55032
2026-09-30T19:31:21Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:checkparams|error}}<!-- Validate template parameters -->{{head|goh|noun|head={{{head|}}}|sort={{{sort|}}}<!-- -->|g={{#switch:{{{1}}}|m|f|n|m-p|f-p|n-p={{{1}}}|?}}<!-- -->|g2={{#switch:{{{g2|}}}|m|f|n|m-p|f-p|n-p={{{g2}}}}}<!-- -->|{{#if:{{{2|}}}|plural}}<!-- -->|{{{2|}}}<!-- -->|{{#if:{{{pl2|}}}|or}}<!-- -->|{{{pl2|}}}<!-- -->}}<!-- --><noinclude>{{documentation}}</noinclude>'
55032
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{head|goh|noun|head={{{head|}}}|sort={{{sort|}}}<!--
-->|g={{#switch:{{{1}}}|m|f|n|m-p|f-p|n-p={{{1}}}|?}}<!--
-->|g2={{#switch:{{{g2|}}}|m|f|n|m-p|f-p|n-p={{{g2}}}}}<!--
-->|{{#if:{{{2|}}}|plural}}<!--
-->|{{{2|}}}<!--
-->|{{#if:{{{pl2|}}}|or}}<!--
-->|{{{pl2|}}}<!--
-->}}<!--
--><noinclude>{{documentation}}</noinclude>
fwmnmkv91lwtdilofa7xf5hwrog7q28
Bysen:der
10
8143
55036
2026-09-30T19:36:10Z
Deadend0914
7211
Redirected page to [[Bysen:derived]]
55036
wikitext
text/x-wiki
#REDIRECT [[Bysen:derived]]
7b14fhut10xy4i0i72jaf9kdlidizni
Bysen:derived
10
8144
55037
2026-09-30T19:36:25Z
Deadend0914
7211
Gesceop tramet þe hafaþ '<includeonly>{{#invoke:etymology/templates|derived}}</includeonly><!-- --><noinclude>{{der|und|und|term}}{{documentation}}</noinclude>'
55037
wikitext
text/x-wiki
<includeonly>{{#invoke:etymology/templates|derived}}</includeonly><!--
--><noinclude>{{der|und|und|term}}{{documentation}}</noinclude>
qy162q531c9y9k498mdiw259aiw0jl1
drink
0
8145
55041
2026-09-30T19:41:19Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|en}}== ==Rihtstefn=== * IPA: /ˈdɹɪŋk/, ===Wordstǣr=== {{inh+|en|enm|drink}}, of {{inh|en|ang|drynċ}}, of {{inh|en|gem-pro|*drunkiz}} ===Nama=== # drync'
55041
wikitext
text/x-wiki
=={{sprǣc|en}}==
==Rihtstefn===
* IPA: /ˈdɹɪŋk/,
===Wordstǣr===
{{inh+|en|enm|drink}}, of {{inh|en|ang|drynċ}}, of {{inh|en|gem-pro|*drunkiz}}
===Nama===
# drync
gp0s0o3s3g66xpobfd3k9quo6j56mbo
55042
55041
2026-09-30T19:41:27Z
Deadend0914
7211
/* Wordstǣr */
55042
wikitext
text/x-wiki
=={{sprǣc|en}}==
==Rihtstefn===
* IPA: /ˈdɹɪŋk/,
===Wordstǣr===
{{inh+|en|enm|drink}}, of {{inh|en|ang|drync}}, of {{inh|en|gem-pro|*drunkiz}}
===Nama===
# drync
dcv7q0qqdll1lgcdmamtcu7z0s8skz6
55043
55042
2026-09-30T19:41:53Z
Deadend0914
7211
/* Nama */
55043
wikitext
text/x-wiki
=={{sprǣc|en}}==
==Rihtstefn===
* IPA: /ˈdɹɪŋk/,
===Wordstǣr===
{{inh+|en|enm|drink}}, of {{inh|en|ang|drync}}, of {{inh|en|gem-pro|*drunkiz}}
===Nama===
{{en-noun|~}}
# drync
pli3db4zayfekb3ke29hbn0jzl31by1
55044
55043
2026-09-30T19:42:03Z
Deadend0914
7211
/* Nama */
55044
wikitext
text/x-wiki
=={{sprǣc|en}}==
==Rihtstefn===
* IPA: /ˈdɹɪŋk/,
===Wordstǣr===
{{inh+|en|enm|drink}}, of {{inh|en|ang|drync}}, of {{inh|en|gem-pro|*drunkiz}}
===Nama===
{{en-noun|~}}
# [[drync]]
cbse2g621xfzb6krfb0ju9jgoig4s14
undergo
0
8146
55045
2026-09-30T19:46:57Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|en}}== ===Rihstefn=== * IPA: /ˌʌndɚˈɡoʊ/ ===Wordstǣr=== Of {{inh|en|enm|undergon}}, of {{inh|en|ang|undergān}} ===Word=== {{en-verb|~es|~ing|underwent|~ne}} # undergān'
55045
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihstefn===
* IPA: /ˌʌndɚˈɡoʊ/
===Wordstǣr===
Of {{inh|en|enm|undergon}}, of {{inh|en|ang|undergān}}
===Word===
{{en-verb|~es|~ing|underwent|~ne}}
# undergān
265x8vxywv4rfw0w1r9lcbnzhrgp8wu
55048
55045
2026-09-30T19:54:27Z
Deadend0914
7211
/* Word */
55048
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihstefn===
* IPA: /ˌʌndɚˈɡoʊ/
===Wordstǣr===
Of {{inh|en|enm|undergon}}, of {{inh|en|ang|undergān}}
===Word===
{{en-verb|~es|~ing|underwent|~ne}}
# [[undergān]]
tnkkrcbb1jqwgzovyjmkknzp8x2ggp0
Bysen:en-verb
10
8147
55046
2026-09-30T19:47:19Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:en-headword|show|verbs}}<!-- --><noinclude>{{documentation}}</noinclude>'
55046
wikitext
text/x-wiki
{{#invoke:en-headword|show|verbs}}<!--
--><noinclude>{{documentation}}</noinclude>
61usq2ftl97ga0ky6nna0er3yyqtbp9
Bysen:en-head
10
8148
55049
2026-09-30T19:55:00Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:en-headword|show}}<!-- --><noinclude>{{documentation}}</noinclude>'
55049
wikitext
text/x-wiki
{{#invoke:en-headword|show}}<!--
--><noinclude>{{documentation}}</noinclude>
b61rollxgc9fbrkcs5t760xnqz1hw58
Module:qualifier
828
8149
55054
2026-09-30T22:06:09Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local concat = table.concat --[==[ Wrap text in one or more CSS classes. `classes` should be a string; separate multiple classes with a space. ]==] function export.wrap_css(text, classes) return ("<span class=\"%s\">%s</span>"):format(classes, text) end --[==[ Wrap text in one or more qualifier CSS classes. `suffix` is the suffix describing the type of content, e.g. `brac` for parens, `content` for content, `comma` for commas. CSS classes <c...'
55054
Scribunto
text/plain
local export = {}
local concat = table.concat
--[==[
Wrap text in one or more CSS classes. `classes` should be a string; separate multiple classes with a space.
]==]
function export.wrap_css(text, classes)
return ("<span class=\"%s\">%s</span>"):format(classes, text)
end
--[==[
Wrap text in one or more qualifier CSS classes. `suffix` is the suffix describing the type of content, e.g. `brac`
for parens, `content` for content, `comma` for commas. CSS classes <code>ib-<var>suffix</var></code> and
i<code>qualifier-<var>suffix</var></code> are added.
]==]
function export.wrap_qualifier_css(text, suffix)
local css_classes = ("ib-%s qualifier-%s"):format(suffix, suffix)
return export.wrap_css(text, css_classes)
end
--[==[
Format one or more qualifiers. `data` is an object with the following fields:
* `qualifiers`: A single qualifier or a list or qualifiers.
* `open`: Override the open paren displayed before the qualifiers. If `false` or an empty string, no paren is displayed.
* `close`: Override the close paren displayed before the qualifiers. If `false` or an empty string, no paren is
displayed.
* `opencontent`: Content to display before the qualifiers, after the open paren.
* `closecontent`: Content to display after the qualifiers, before the close paren.
* `no_ib_content`: Suppress wrapping the content with classes `ib-content` and `qualifier-content`. Parens and commas
will still be wrapped in CSS.
* `raw`: Suppress all CSS wrapping.
]==]
function export.format_qualifiers(data)
local qualifiers, open, close = data.qualifiers, data.open, data.close
if type(qualifiers) ~= "table" then
qualifiers = {qualifiers}
end
if not qualifiers[1] then
return ""
end
local parts = {}
local function ins(text)
table.insert(parts, text)
end
local function wrap_qualifier_css(text, suffix)
if data.raw then
return text
end
return export.wrap_qualifier_css(text, suffix)
end
if open ~= false and open ~= ""then
ins(wrap_qualifier_css(open or "(", "brac"))
end
if data.opencontent then
ins(data.opencontent)
end
local content = concat(qualifiers, wrap_qualifier_css(",", "comma") .. " ")
if not data.no_ib_content then
content = wrap_qualifier_css(content, "content")
end
ins(content)
if data.closecontent then
ins(data.closecontent)
end
if close ~= false and close ~= "" then
ins(wrap_qualifier_css(close or ")", "brac"))
end
return concat(parts)
end
--[==[
An older interface onto `format_qualifiers`. Eventually code should be converted to use the new entry point.
]==]
function export.format_qualifier(qualifiers, open, close, opencontent, closecontent, no_ib_content)
return export.format_qualifiers {
qualifiers = qualifiers,
open = open,
close = close,
opencontent = opencontent,
closecontent = closecontent,
no_ib_content = no_ib_content,
}
end
local function format_qualifiers_with_clarification(qualifiers, clarification, openquote, closequote)
local opencontent = export.wrap_css(clarification, "qualifier-clarification") ..
export.wrap_css(openquote or "“", "qualifier-clarification qualifier-quote")
local closecontent = export.wrap_css(closequote or "”", "qualifier-clarification qualifier-quote")
return export.format_qualifiers {
qualifiers = qualifiers,
open = "(",
close = ")",
opencontent = opencontent,
closecontent = closecontent,
}
end
--[==[
Internal implementation of {{tl|sense}}.
]==]
function export.sense(qualifiers)
return export.format_qualifiers {
qualifiers = qualifiers
}.. export.wrap_css(":", "ib-colon sense-qualifier-colon")
end
--[==[
Internal implementation of {{tl|antsense}}.
]==]
function export.antsense(qualifiers)
return format_qualifiers_with_clarification(qualifiers, "antonym(s) of ") ..
export.wrap_css(":", "ib-colon sense-qualifier-colon")
end
return export
tc3tigewud11x9n9900h1tvmhjpvcah
Module:labels/data/lang/en
828
8150
55055
2026-09-30T22:06:45Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local labels = {} ------------------------------------ North America ------------------------------------ labels["North America"] = { aliases = {"North American"}, display = "[[Canada]], [[American English|US]]", regional_categories = {"Canadian", "American"}, } labels["caricature Black"] = { display = "caricatures of Black speech", def = "any of various caricatures of Black speech by non-Black writers", aliases = {"caricature black", "a caricature of Bl...'
55055
Scribunto
text/plain
local labels = {}
------------------------------------ North America ------------------------------------
labels["North America"] = {
aliases = {"North American"},
display = "[[Canada]], [[American English|US]]",
regional_categories = {"Canadian", "American"},
}
labels["caricature Black"] = {
display = "caricatures of Black speech",
def = "any of various caricatures of Black speech by non-Black writers",
aliases = {"caricature black", "a caricature of Black speech", "caricatures of Black speech"},
plain_categories = "Caricatures of Black English",
form_of_display = "a caricature of Black"
-- for use in T:pronunciation spelling of. use in T:altform and T:altspell is not intended and looks bad.
}
------------- Canada -------------
labels["Canada"] = {
aliases = {"CA", "Canadian", "CanE", "Canadian English"},
Wikipedia = "Canadian English",
regional_categories = "Canadian",
parent = "rawposcat:North American English,Commonwealth",
}
labels["Acadia"] = {
region = "colonial [[Acadia]]",
aliases = {"Acadian"},
Wikipedia = true,
regional_categories = "Acadian",
parent = "Canada",
}
labels["Alberta"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canadian Prairies",
}
labels["Atlantic Canada"] = {
Wikipedia = "Atlantic Canadian English",
regional_categories = "Atlantic Canadian",
parent = "Canada",
}
labels["British Columbia"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canada",
}
labels["Canadian Prairies"] = {
the = true,
Wikipedia = true,
regional_categories = true,
parent = "Canada",
}
labels["Labrador"] = {
Wikipedia = true,
regional_categories = true,
parent = "Atlantic Canada",
}
labels["Manitoba"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canadian Prairies",
}
labels["Multicultural Toronto English"] = {
def = "the {{w|multiethnolect|multi-ethnic dialect}} of {{catlink|Canadian English}} used in the {{w|Greater Toronto Area}}, particularly among young non-white working-class speakers",
aliases = {"MTE"},
display = "MTE",
Wikipedia = true,
plain_categories = true,
parent = "Ontario",
}
labels["New Brunswick"] = {
Wikipedia = "Atlantic Canadian English",
regional_categories = true,
parent = "Atlantic Canada",
}
labels["Newfoundland"] = {
Wikipedia = "Newfoundland English",
regional_categories = true,
parent = "Atlantic Canada",
}
labels["Northwest Territories"] = {
region = "the [[Northwest Territories]] of [[Canada]]",
Wikipedia = true,
regional_categories = true,
parent = "Canada",
}
labels["Northwestern Ontario"] = {
aliases = {"northwestern Ontario", "Northwest Ontario", "northwest Ontario"},
Wikipedia = true,
regional_categories = true,
parent = "Ontario",
}
labels["Nova Scotia"] = {
Wikipedia = "Atlantic Canadian English",
regional_categories = true,
parent = "Atlantic Canada",
}
labels["Nunavut"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canada",
}
labels["Ontario"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canada",
}
labels["Prince Edward Island"] = {
Wikipedia = true,
regional_categories = true,
parent = "Atlantic Canada",
}
labels["Quebec"] = {
aliases = {"Québec"},
Wikipedia = "Quebec English",
regional_categories = true,
parent = "Canada",
}
labels["Saskatchewan"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canadian Prairies",
}
labels["Yukon"] = {
Wikipedia = true,
regional_categories = true,
parent = "Canada",
}
------------- US -------------
labels["US"] = {
region = "the [[United States]]",
aliases = {"U.S.", "United States", "United States of America", "USA", "US English", "U.S. English", "America", "American", "American English"},
Wikipedia = "American English",
regional_categories = "American",
parent = "rawposcat:North American English",
}
labels["African-American Vernacular"] = {
def = "the variety of [[English]] spoken, especially in urban communities, by most working-class and some middle-class [[African-American]]s",
addl = "It is a [[sociolect]] with significantly different grammatical characteristics from {{w|Standard English}}, especially in {{w|tense-aspect}} and [[negation]] constructions. Many African-American communities maintain [[diglossia]] between African-American Vernacular English (AAVE) and Standard English.",
aliases = {"AAVE", "African American Vernacular", "African American Vernacular English", "African-American Vernacular English", "BVE"},
Wikipedia = "African-American Vernacular English",
regional_categories = true,
parent = "African-American",
}
labels["African-American"] = {
prep = "by",
region = "[[African-American]]s in the [[United States]]",
aliases = {"AA", "African-American English", "African American", "African American English", "AAE"},
Wikipedia = "African-American English",
regional_categories = true,
parent = "US",
}
labels["Alabama"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
labels["Alaska"] = {
Wikipedia = true,
regional_categories = true,
parent = "Northwestern US",
}
labels["Appalachia"] = {
aliases = {"Appalachian"},
Wikipedia = "Appalachian English",
regional_categories = "Appalachian",
parent = "US",
}
labels["Arizona"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southwestern US",
}
labels["Arkansas"] = {
Wikipedia = true,
regional_categories = "Arkansan",
parent = "South Midland US",
}
labels["Baltimore"] = {
Wikipedia = "Baltimore accent",
regional_categories = true,
parent = "Maryland",
}
labels["Boston"] = {
Wikipedia = "Boston accent",
regional_categories = true,
parent = "Massachusetts",
}
labels["Cajun"] = {
prep = "by",
region = "[[Cajun]]s in {{w|Acadiana|Southern Louisiana}}",
Wikipedia = "Cajun English",
regional_categories = true,
parent = "Louisiana",
}
labels["California"] = {
Wikipedia = "California English",
regional_categories = true,
parent = "Western US",
}
labels["Chicago"] = {
Wikipedia = {"Inland Northern American English", true},
regional_categories = true,
parent = "Illinois",
}
labels["Cincinnati"] = {
Wikipedia = "Midland American English#Cincinnati",
regional_categories = true,
parent = "Ohio,Kentucky",
}
labels["Colorado"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southwestern US",
}
labels["Connecticut"] = {
Wikipedia = true,
regional_categories = true,
parent = "New England",
}
labels["District of Columbia"] = {
region = "Washington, D.C.",
aliases = {"DC", "Washington, DC"},
Wikipedia = true,
regional_categories = "DC",
parent = "Mid-Atlantic US",
}
labels["Eastern New England"] = {
Wikipedia = "Eastern New England English",
regional_categories = true,
parent = "New England",
}
labels["Florida"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
labels["Georgia (US)"] = {
region = "the state of [[Georgia]] in the [[United States]]",
display = "Georgia",
Wikipedia = "Georgia (U.S. state)",
regional_categories = true,
parent = "Southern US",
}
labels["Hawaii"] = {
Wikipedia = true,
regional_categories = "Hawaiian",
parent = "Western US",
}
labels["Illinois"] = {
Wikipedia = true,
regional_categories = true,
parent = "Midland US,Northern US",
}
labels["Indiana"] = {
Wikipedia = true,
regional_categories = true,
parent = "Midland US,Northern US",
}
labels["Kentucky"] = {
Wikipedia = true,
regional_categories = true,
parent = "South Midland US",
}
labels["Louisiana"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
labels["Maine"] = {
Wikipedia = "Maine accent",
regional_categories = true,
parent = "New England",
}
labels["Maryland"] = {
Wikipedia = true,
regional_categories = true,
parent = "Mid-Atlantic US",
}
labels["Massachusetts"] = {
Wikipedia = true,
regional_categories = true,
parent = "New England",
}
labels["Memphis"] = {
regional_categories = true,
parent = "Southern US",
}
labels["Michigan"] = {
Wikipedia = true,
regional_categories = true,
parent = "Upper Midwestern US",
}
labels["Mid-Atlantic US"] = {
region = "the [[Mid-Atlantic]] United States",
Wikipedia = "Mid-Atlantic American English",
regional_categories = true,
parent = "US",
}
labels["Midland US"] = {
region = "the American [[Midland]]",
Wikipedia = "Midland American English",
regional_categories = true,
parent = "Midwestern US",
}
labels["Midwestern US"] = {
region = "the [[Midwest]] of the [[United States]]",
aliases = {"Midwest US"},
Wikipedia = "Midwestern American English",
regional_categories = true,
parent = "US",
}
labels["Mississippi"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
labels["Missouri"] = {
Wikipedia = true,
regional_categories = true,
parent = "Midland US",
}
labels["New England"] = {
Wikipedia = "New England English",
regional_categories = true,
parent = "Northeastern US",
}
labels["New Jersey"] = {
Wikipedia = "New Jersey English",
regional_categories = true,
parent = "Northeastern US",
}
labels["New Mexico"] = {
Wikipedia = "Western American English#New Mexico",
regional_categories = true,
parent = "Southwestern US",
}
labels["New Orleans"] = {
Wikipedia = "New Orleans English",
regional_categories = true,
parent = "Louisiana",
}
labels["New York City"] = {
aliases = {"NYC"},
Wikipedia = "New York City English",
regional_categories = true,
parent = "New York",
}
labels["New York"] = {
region = "the [[United States]] state of [[New York]]",
aliases = {"NY"},
Wikipedia = "New York English (disambiguation)",
regional_categories = true,
parent = "Northeastern US",
}
labels["North Carolina"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
-- can be split off if enough entries in it arise; group with Midland US for now
labels["North Midland US"] = {
aliases = {"Northern Midland US"},
Wikipedia = "Midland American English",
regional_categories = "Midland US",
}
labels["Northeastern US"] = {
region = "the {{w|Northeastern United States}}",
aliases = {"Northeast US"},
Wikipedia = {"Northern American English#Northeastern American English"},
regional_categories = true,
parent = "Northern US",
}
-- can be split off if enough entries in it arise; group with California for now
labels["Northern California"] = {
Wikipedia = "California English",
regional_categories = "California",
}
labels["Northwestern US"] = {
region = "the {{w|Northwestern United States}}",
aliases = {"Northwest US", "Pacific Northwest"},
Wikipedia = {"Pacific Northwest English", "Northwestern United States"},
regional_categories = true,
parent = "Western US",
}
labels["Ohio"] = {
Wikipedia = true,
regional_categories = true,
parent = "Midland US,Northern US",
}
labels["Oklahoma"] = {
Wikipedia = true,
regional_categories = true,
parent = "South Midland US",
}
labels["Pennsylvania Dutch English"] = {
prep = "by",
region = "{{w|Pennsylvania Dutch}} people in south-central [[Pennsylvania]]",
Wikipedia = true,
plain_categories = true,
parent = "Pennsylvania",
}
labels["Pennsylvania"] = {
Wikipedia = true,
regional_categories = true,
parent = "Northeastern US",
}
-- can be split off if enough entries in it arise; group with Pennsylvania for now
labels["Philadelphia"] = {
Wikipedia = "Philadelphia English",
regional_categories = true,
parent = "Pennsylvania,Mid-Atlantic US",
}
-- can be split off if enough entries in it arise; group with Western Pennsylvania for now
labels["Pittsburgh"] = {
Wikipedia = "Western Pennsylvania English",
regional_categories = "Western Pennsylvania",
parent = "Pennsylvania",
}
labels["Pittsburghese"] = {
Wikipedia = "Western Pennsylvania English",
regional_categories = "Western Pennsylvania",
parent = "Pennsylvania",
}
labels["Rhode Island"] = {
Wikipedia = true,
regional_categories = true,
parent = "New England",
}
labels["South Carolina"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
-- can be split off if enough entries in it arise; group with Midland US for now
labels["South Midland US"] = {
aliases = {"Southern Midland US"},
Wikipedia = "Midland American English",
regional_categories = "Midland US",
}
-- can be split off if enough entries in it arise; group with California for now
labels["Southern California"] = {
Wikipedia = "California English",
regional_categories = "California",
}
labels["Southern US"] = {
region = "the {{w|Southern United States}}",
aliases = {"Southern American English", "southern US", "US South"},
Wikipedia = "Southern American English",
regional_categories = true,
parent = "US",
}
labels["Southwestern US"] = {
region = "the {{w|Southwestern United States}}",
aliases = {"southwestern US", "Southwest US", "southwest US"},
Wikipedia = {"Western American English", "Southwestern United States"},
regional_categories = true,
parent = "Western US",
}
labels["St. Louis"] = {
Wikipedia = "St. Louis dialect",
regional_categories = true,
parent = "Missouri,Illinois",
}
labels["St. Vincent"] = {
Wikipedia = "Saint Vincent (Saint Vincent and the Grenadines)",
regional_categories = "Saint Vincentian",
parent = "Caribbean",
}
labels["Texas"] = {
Wikipedia = "Texan English",
regional_categories = true,
parent = "Southern US,Southwestern US",
}
labels["Upper Midwestern US"] = {
region = "the {{w|Upper Midwest}} of the [[United States]]",
aliases = {"Upper Midwest US"},
Wikipedia = {"North-Central American English", "Upper Midwest"},
regional_categories = true,
parent = "Midwestern US,Northern US",
}
labels["Vermont"] = {
Wikipedia = {"New England English", "Vermont"},
regional_categories = true,
parent = "New England",
}
labels["Virginia"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern US",
}
labels["Western Pennsylvania"] = {
aliases = {"Western Pennsylvania English"},
Wikipedia = "Western Pennsylvania English",
regional_categories = true,
parent = "Pennsylvania",
}
labels["Western US"] = {
region = "the {{w|Western United States}}",
aliases = {"western US"},
Wikipedia = {"Western American English", "Western United States"},
regional_categories = true,
parent = "US",
}
labels["Wisconsin"] = {
Wikipedia = true,
regional_categories = true,
parent = "Upper Midwestern US",
}
------------------------------------ Australia and New Zealand ------------------------------------
labels["Australian Aboriginal"] = {
prep = "by",
region = "[[Aboriginal]] people in [[Australia]]",
aliases = {"Australian aboriginal", "Australian Aboriginal English", "Australian aboriginal English", "Aboriginal Australian", "aboriginal Australian", "Aboriginal Australian English", "aboriginal Australian English"},
Wikipedia = "Australian Aboriginal English",
regional_categories = true,
parent = "Australia",
}
labels["Australia"] = {
aliases = {"Australian", "AU", "AuE", "Aus", "AusE"},
Wikipedia = "Australian English",
accent_Wikipedia = "Australian English phonology",
regional_categories = "Australian",
parent = "Oceania,Commonwealth",
}
labels["Canberra"] = {
region = "the [[Australian Capital Territory]] ([[Canberra]])",
Wikipedia = {"Variation in Australian English#Regional variation", true},
regional_categories = true,
parent = "Australia",
}
labels["New South Wales"] = {
aliases = {"NSW"},
Wikipedia = {"Variation in Australian English#Regional variation", true},
regional_categories = true,
parent = "Australia",
}
labels["New Zealand"] = {
aliases = {"NZ", "NZE"},
Wikipedia = "New Zealand English",
accent_Wikipedia = "New Zealand English phonology",
regional_categories = true,
parent = "Oceania,Commonwealth",
}
labels["Northern Territory"] = {
aliases = {"NT"},
Wikipedia = {"Variation in Australian English#Regional variation", true},
regional_categories = true,
parent = "Australia",
}
labels["Northern US"] = {
region = "the {{w|Northern United States}}",
aliases = {"Northern American English", "northern US", "US North"},
Wikipedia = "Northern American English",
regional_categories = true,
parent = "US",
}
labels["Queensland"] = {
Wikipedia = {"Variation in Australian English#Regional variation", true},
regional_categories = true,
parent = "Australia",
}
labels["South Australia"] = {
Wikipedia = "South Australian English",
regional_categories = "South Australian",
parent = "Australia",
}
labels["Tasmania"] = {
Wikipedia = {"Variation in Australian English#Regional variation", true},
regional_categories = "Tasmanian",
parent = "Australia",
}
labels["Victoria"] = {
Wikipedia = {"Variation in Australian English#Regional variation", "Victoria (state)"},
regional_categories = true,
parent = "Australia",
}
labels["Western Australia"] = {
Wikipedia = "Western Australian English",
regional_categories = "Western Australian",
parent = "Australia",
}
------------------------------------ Ireland ------------------------------------
labels["Cork"] = {
Wikipedia = {"South-West Irish English", "Cork (city)"},
regional_categories = "Munster",
}
labels["Dublin"] = {
Wikipedia = {"Dublin English", "Dublin", "DE"},
regional_categories = true,
parent = "Ireland",
}
labels["Ireland"] = {
aliases = {"Irish", "IE"},
Wikipedia = "Hiberno-English",
regional_categories = "Irish",
parent = "Europe",
}
labels["Munster"] = {
Wikipedia = {"South-West Irish English", "Munster"},
regional_categories = true,
parent = "Ireland",
}
------------------------------------ United Kingdom ------------------------------------
labels["UK"] = {
addl = "Not to be confused with [[:Category:British English forms|British spellings]], a spelling system used in some English-speaking countries of the world.",
aliases = {"United Kingdom", "British", "Britain", "Great Britain"},
Wikipedia = "British English",
regional_categories = "British",
parent = "Europe,Commonwealth",
country = "the United Kingdom",
}
labels["Antrim"] = {
Wikipedia = "County Antrim",
regional_categories = "Northern Irish",
}
labels["Bedfordshire"] = {
Wikipedia = "Bedfordshire dialect",
regional_categories = true,
parent = "Southern England",
}
labels["Berkshire"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern England",
}
labels["Birmingham"] = {
Wikipedia = "Brummie dialect",
regional_categories = true,
parent = "West Midlands",
}
labels["Bristol"] = {
region = "[[Bristol]], [[England]]",
aliases = {"Bristolian"},
Wikipedia = "Bristolian dialect",
regional_categories = "Bristolian",
parent = "West Country",
}
labels["Caithness"] = {
Wikipedia = true,
regional_categories = true,
parent = "Scotland",
}
labels["Cambridge University"] = {
prep = "at",
region = "{{w|Cambridge University}} in [[Cambridge]]",
aliases = {"University of Cambridge", "Cantab"},
Wikipedia = "University of Cambridge",
regional_categories = true,
parent = "East Anglia",
othercat = "en:Universities",
}
labels["Channel Islands"] = {
the = true,
Wikipedia = "Channel Island English",
regional_categories = true,
parent = "Europe,Commonwealth",
}
labels["Cockney"] = {
prep = "by",
region = "working-class [[Londoner]]s, especially in the [[East End]]",
Wikipedia = "Cockney#Speech",
regional_categories = true,
parent = "London",
}
labels["Cornwall"] = {
aliases = {"Cornish"},
Wikipedia = "Cornish dialect",
regional_categories = "Cornish",
parent = "West Country",
}
labels["Cumbria"] = {
aliases = {"Cumbrian"},
Wikipedia = "Cumbrian dialect",
regional_categories = "Cumbrian",
parent = "Northern England",
}
labels["Derbyshire"] = {
region = "[[Derbyshire]], which is geographically in the East Midlands but whose dialect is sometimes classified as West Midlands",
Wikipedia = "Derbyshire dialect",
regional_categories = true,
parent = "East Midlands,West Midlands",
}
labels["Devon"] = {
aliases = {"Devonshire"},
Wikipedia = {"West Country English", true},
regional_categories = "Devonian",
parent = "West Country",
}
labels["Dorset"] = {
Wikipedia = "Dorset dialect",
regional_categories = true,
parent = "West Country",
}
labels["Dundee"] = {
Wikipedia = true,
regional_categories = true,
parent = "Scotland",
}
labels["Durham University"] = {
prep = "at",
region = "{{w|Durham University}} in [[Durham]]",
Wikipedia = true,
regional_categories = true,
parent = "Durham",
othercat = "en:Universities",
}
labels["Durham"] = {
Wikipedia = "County Durham",
regional_categories = true,
parent = "Northumbria",
}
labels["East Anglia"] = {
Wikipedia = "East Anglian English",
regional_categories = "East Anglian",
parent = "England",
}
labels["East Midlands"] = {
region = "the [[East Midlands]] of [[England]]",
Wikipedia = "East Midlands English",
regional_categories = true,
parent = "Midlands",
}
labels["England"] = {
aliases = {"English"},
Wikipedia = "English language in England",
regional_categories = "English",
parent = "British",
}
labels["England and Wales"] = {
aliases = {"E&W"},
Wikipedia = true,
regional_categories = {"English", "Welsh"},
}
labels["Essex"] = {
Wikipedia = "Essex dialect",
regional_categories = true,
parent = "Southern England",
}
labels["Exmoor"] = {
Wikipedia = true,
regional_categories = {"Devonian", "Somerset"},
}
labels["Geordie"] = {
region = "Tyneside",
aliases = {"Geordie English", "Tyneside"},
Wikipedia = true,
plain_categories = true,
parent = "Northumbria",
}
labels["Gloucestershire"] = {
Wikipedia = {"West Country English", true},
regional_categories = true,
parent = "West Country",
}
labels["Guernsey"] = {
prep = "on",
Wikipedia = "Channel Island English#Guernsey English",
regional_categories = true,
parent = "Channel Islands",
}
labels["Hartlepool"] = {
Wikipedia = "Smoggie",
regional_categories = "Teesside",
}
labels["Herefordshire"] = {
Wikipedia = true,
regional_categories = true,
parent = "West Country",
}
labels["Isle of Man"] = {
prep = "on",
the = true,
aliases = {"Manx"},
Wikipedia = "Manx English",
regional_categories = "Manx",
parent = "British",
}
labels["Isle of Wight"] = {
prep = "on",
the = true,
Wikipedia = true,
regional_categories = true,
parent = "Southern England",
}
labels["Jersey"] = {
prep = "on",
Wikipedia = "Channel Island English#Jersey English",
regional_categories = true,
parent = "Channel Islands",
}
labels["Kent"] = {
aliases = {"Kentish"},
Wikipedia = "Kentish dialect",
regional_categories = "Kentish",
parent = "Southern England",
}
labels["Lancashire"] = {
Wikipedia = "Lancashire dialect",
regional_categories = true,
parent = "Northern England",
}
labels["Lewis"] = {
prep = "on",
region = "the [[Isle of Lewis]]",
aliases = {"Isle of Lewis"},
Wikipedia = "Isle of Lewis",
regional_categories = true,
parent = "Scotland",
}
labels["Lincolnshire"] = {
Wikipedia = "Lincolnshire dialect",
regional_categories = true,
parent = "East Midlands",
}
labels["Liverpool"] = {
region = "the {{w|Liverpool City Region}}, comprising the [[metropolitan county]] of [[Merseyside]] and the [[Cheshire]] [[unitary authority]] of [[Halton]]",
aliases = {"Scouse"},
Wikipedia = "Scouse",
regional_categories = "Liverpudlian",
parent = "Northern England",
}
labels["London"] = {
Wikipedia = "Estuary English",
regional_categories = true,
parent = "Southern England",
}
labels["Manchester"] = {
aliases = {"Mancunian"},
Wikipedia = "Manchester dialect",
regional_categories = "Mancunian",
parent = "Northern England",
}
labels["Mid-Ulster"] = {
region = "central [[Ulster]]",
aliases = {"Mid-Ulster English"},
Wikipedia = "Mid-Ulster English",
regional_categories = true,
parent = "Ulster,Northern Ireland",
}
labels["Midlands"] = {
region = "the [[Midlands]] of [[England]]",
aliases = {"English Midlands", "South Midlands"},
Wikipedia = "Midlands English",
regional_categories = true,
parent = "England",
}
labels["Multicultural London English"] = {
prep = "by",
region = "young, working-class people in multicultural parts of [[London]]",
aliases = {"MLE"},
display = "MLE",
Wikipedia = true,
plain_categories = true,
parent = "London",
}
labels["Norfolk"] = {
Wikipedia = "Norfolk dialect",
regional_categories = true,
parent = "East Anglia",
}
labels["North Wales"] = {
Wikipedia = {"Welsh English", true},
regional_categories = true,
parent = "Wales",
}
labels["Northern England"] = {
aliases = {"northern England", "North England", "north England"},
Wikipedia = "English language in Northern England",
regional_categories = true,
parent = "England",
}
labels["Northern Ireland"] = {
aliases = {"Northern Irish", "NI"},
Wikipedia = "Ulster English",
regional_categories = "Northern Irish",
parent = "Ulster",
}
labels["Northern Isles"] = {
display = "[[w:Orkney|Orkney]], [[w:Shetland|Shetland]]",
regional_categories = {"Orkney", "Shetland"},
}
labels["Northumberland"] = {
Wikipedia = {"Northumberland"},
regional_categories = true,
parent = "Northumbria",
}
labels["Northumbria"] = {
aliases = {"Northumbrian", "Northeast England", "North-East England", "North East England"},
Wikipedia = {"Northumbrian dialect", "Northumbria (modern)"},
regional_categories = "Northumbrian",
parent = "Northern England",
}
labels["Nottinghamshire"] = {
Wikipedia = "Nottinghamshire dialect",
regional_categories = true,
parent = "East Midlands",
}
labels["Orkney"] = {
prep = "on",
aliases = {"Orcadian"},
Wikipedia = {true, "Highland English"},
regional_categories = true,
parent = "Scotland",
}
labels["Oxbridge"] = {
Wikipedia = true,
regional_categories = {"Cambridge University", "Oxford University"},
}
labels["Oxford City"] = {
region = "the city of [[Oxford]] in [[England]]",
Wikipedia = true,
regional_categories = "Oxford",
parent = "Oxfordshire",
}
labels["Oxford University"] = {
prep = "at",
aliases = {"University of Oxford", "Oxon"},
Wikipedia = "University of Oxford",
regional_categories = true,
parent = "Oxford City",
othercat = "en:Universities",
}
labels["Oxfordshire"] = {
Wikipedia = true,
regional_categories = true,
parent = "Southern England",
}
labels["Pitmatic"] = {
Wikipedia = true,
regional_categories = true,
parent = "Northumbria",
}
labels["Potteries"] = {
region = "Stoke-on-Trent",
Wikipedia = "Potteries dialect",
regional_categories = true,
parent = "West Midlands",
}
labels["Scotland"] = {
aliases = {"Scottish", "Scottish English", "ScE"},
Wikipedia = "Scottish English",
regional_categories = "Scottish",
parent = "British",
}
labels["Shetland"] = {
region = "the [[Shetland Islands]]",
aliases = {"Shetland Islands", "Shetlands"},
Wikipedia = {true, "Highland English"},
regional_categories = true,
parent = "Scotland",
}
labels["Shropshire"] = {
Wikipedia = true,
regional_categories = true,
parent = "West Midlands",
}
labels["Somerset"] = {
Wikipedia = {"West Country English", true},
regional_categories = true,
parent = "West Country",
}
-- eventually maybe break this out into its own category
labels["South Midlands"] = {
Wikipedia = {"Midlands English", "South Midlands"},
regional_categories = "Midlands",
}
labels["South Wales"] = {
Wikipedia = {"Welsh English", true},
regional_categories = true,
parent = "Wales",
}
labels["Southern England"] = {
aliases = {"southern England", "South England", "south England", "Southern English"},
Wikipedia = "English in southern England",
regional_categories = true,
parent = "England",
}
labels["Suffolk"] = {
Wikipedia = "Suffolk dialect",
regional_categories = true,
parent = "East Anglia",
}
labels["Sussex"] = {
Wikipedia = "Sussex dialect",
regional_categories = true,
parent = "Southern England",
}
labels["Teesside"] = {
Wikipedia = "Smoggie",
regional_categories = true,
parent = "Northumbria",
}
-- Tyneside: see Geordie
labels["Ulster"] = {
Wikipedia = "Ulster English",
regional_categories = true,
parent = "Ireland",
}
labels["Wales"] = {
aliases = {"Welsh"},
Wikipedia = "Welsh English",
regional_categories = "Welsh",
parent = "British",
}
labels["Wearside"] = {
Wikipedia = {"Mackem", true},
regional_categories = true,
parent = "Northumbria",
}
labels["West Country"] = {
the = true,
aliases = {"West England", "west England"},
Wikipedia = "West Country English",
regional_categories = true,
parent = "England",
}
-- can be split off if enough entries in it arise; group with Cumbria for now
labels["West Cumbria"] = {
Wikipedia = "Cumbrian dialect",
regional_categories = "Cumbrian",
}
labels["West Midlands"] = {
region = "the [[West Midlands]] of [[England]]",
Wikipedia = "West Midlands English",
regional_categories = true,
parent = "Midlands",
}
labels["Wiltshire"] = {
Wikipedia = {"West Country English", true},
regional_categories = true,
parent = "West Country",
}
labels["Yorkshire"] = {
Wikipedia = "Yorkshire dialect",
regional_categories = true,
parent = "Northern England",
}
-------------------------------- South Asia --------------------------------
labels["South Asia"] = {
aliases = {"Indic", "South Asian", "SA"},
Wikipedia = "South Asian English",
regional_categories = "South Asian",
parent = "Asia",
}
labels["Afghanistan"] = {
Wikipedia = true,
regional_categories = "Afghan",
parent = "South Asia",
}
labels["Bangladesh"] = {
Wikipedia = "Bangladeshi English",
regional_categories = "Bangladeshi",
parent = "South Asia,Commonwealth",
}
labels["Nepal"] = {
Wikipedia = "Nepalese English",
regional_categories = "Nepali",
parent = "South Asia,Commonwealth",
}
labels["Sri Lanka"] = {
aliases = {"Sri Lankan"},
Wikipedia = "Sri Lankan English",
regional_categories = "Sri Lankan",
parent = "South Asia,Commonwealth",
}
labels["Pakistan"] = {
aliases = {"Pakistani"},
Wikipedia = "Pakistani English",
regional_categories = "Pakistani",
parent = "South Asia,Commonwealth",
}
labels["British Pakistani"] = {
prep = "by",
region = "{{w|British Pakistanis}}, i.e. [[British]] citizens of [[Pakistani]] origin",
Wikipedia = "British Pakistanis",
regional_categories = true,
parent = "South Asia,British",
}
labels["British India"] = {
fulldef = "Anglo-Indian terms or senses in English as used formerly by Britishers in {{w|British India}}",
Wikipedia = "Indian English", -- The WP articles are strictly divided by geography, not historical period
regional_categories = true,
parent = "South Asia,British",
othercat = "English terms with historical senses",
}
labels["India"] = {
aliases = {"Indian", "Indian English", "InE"},
Wikipedia = "Indian English",
accent_Wikipedia = "Indian English#Phonology",
regional_categories = "Indian",
parent = "South Asia,Commonwealth",
}
labels["North India"] = {
Wikipedia = true,
regional_categories = "North Indian",
parent = "India",
}
labels["South India"] = {
aliases = {"South Indian"},
Wikipedia = true,
regional_categories = "South Indian",
parent = "India",
}
labels["West Bengal"] = {
Wikipedia = true,
accent_Wikipedia = "Regional differences and dialects in Indian English#Bengali English",
regional_categories = true,
parent = "India",
}
labels["Hinglish"] = {
def = "[[Hinglish]], an English-based [[creole]] incorporating many [[Hindi]] words; used informally in [[India]]",
Wikipedia = true,
plain_categories = true,
parent = "North India",
}
labels["Tamil Nadu"] = {
aliases = {"TN", "Tamilnadu"},
accent_Wikipedia = "Regional differences and dialects in Indian English#Southern Indian English",
parent="South India"
}
labels["Kerala"] = {
accent_Wikipedia = "Regional differences and dialects in Indian English#Malayali",
aliases = { "Malayalam", "Mallu" },
parent = "South India"
}
------------------------------------ World ------------------------------------
labels["Africa"] = {
aliases = {"African"},
Wikipedia = "African English",
regional_categories = "African",
parent = true,
}
labels["Antarctica"] = {
Wikipedia = "Antarctic English",
regional_categories = "Antarctic",
parent = true,
}
labels["Asia"] = {
Wikipedia = "Asian English",
regional_categories = "Asian",
parent = true,
}
labels["Bahamas"] = {
the = true,
Wikipedia = "Bahamian English",
regional_categories = "Bahaman",
parent = "Caribbean",
}
labels["Barbados"] = {
Wikipedia = "English in Barbados",
regional_categories = "Barbadian",
parent = "Caribbean",
}
labels["Belize"] = {
Wikipedia = "Belizean English",
regional_categories = "Belizean",
parent = "Caribbean,Central America",
}
labels["Benglish"] = {
def = "[[Benglish]], an English-based [[creole]] incorporating many [[Bengali]] words; used informally in [[Bangladesh]] and [[West Bengal]]",
aliases = {"Banglish"},
Wikipedia = true,
plain_categories = true,
parent = "Bangladesh,West Bengal",
}
labels["Bermuda"] = {
Wikipedia = "Bermudian English",
regional_categories = "Bermudian",
parent = "Caribbean,rawposcat:North American English,British",
}
labels["Botswana"] = {
Wikipedia = "Botswana English",
regional_categories = "Botswanan",
parent = "Africa",
}
labels["Brunei"] = {
Wikipedia = "Brunei English",
regional_categories = "Bruneian",
parent = "Southeast Asia",
}
labels["Cameroon"] = {
aliases = {"CM", "Cameroonian", "Cameroonian English", "en-CM"},
Wikipedia = "Cameroonian English",
regional_categories = "Cameroonian",
parent = "Africa",
}
labels["Caribbean"] = {
the = true,
aliases = {"West Indies"},
Wikipedia = "Caribbean English",
regional_categories = true,
parent = true,
}
labels["Cebu"] = {
Wikipedia = true,
regional_categories = true,
parent = "Philippines",
}
labels["Central America"] = {
Wikipedia = true,
regional_categories = "Central American",
parent = "rawposcat:North American English",
}
labels["Ceylon"] = {
Wikipedia = true,
regional_categories = "Sri Lankan",
}
labels["China"] = {
Wikipedia = true,
regional_categories = "Chinese",
parent = "East Asia",
}
labels["Chinese Filipino"] = {
verb = "used",
prep = "by",
region = "Chinese Filipinos",
aliases = {"Chinese-Filipino"}, -- Sociolect subset to Philippine English
-- may also see "Hokaglish" in Wikipedia, although Hokaglish is the codeswitching form with a Hokkien or Tagalog base, just like Philippine English to "Conyo" (English-based) and "Taglish" (Tagalog-based), whereas this is the English variant itself subset to Philippine English.
Wikipedia = "Chinese Filipino#Language",
regional_categories = true,
parent = "Philippines",
}
labels["Chinglish"] = {
def = "[[English]] that has been influenced by [[Chinese]]",
Wikipedia = true,
plain_categories = true,
parent = "China",
}
labels["Commonwealth"] = {
region = "the [[Commonwealth of Nations]]",
Wikipedia = "English in the Commonwealth of Nations",
regional_categories = true,
parent = true,
}
labels["Cuba"] = {
Wikipedia = true,
regional_categories = "Cuban",
parent = "Caribbean",
}
labels["East Africa"] = {
Wikipedia = true,
regional_categories = "East African",
parent = "Africa",
}
labels["East Asia"] = {
Wikipedia = true,
regional_categories = "East Asian",
parent = "Asia",
}
labels["Egypt"] = {
Wikipedia = true,
regional_categories = "Egyptian",
parent = "Africa,Middle East",
}
labels["Europe"] = {
aliases = {"European"},
Wikipedia = "English language in Europe",
regional_categories = "European",
parent = true,
}
labels["Fiji"] = {
Wikipedia = "Fijian English",
regional_categories = "Fijian",
parent = "Oceania,Commonwealth",
}
labels["Ghana"] = {
Wikipedia = "Ghanaian English",
regional_categories = "Ghanaian",
parent = "West Africa",
}
labels["Guyana"] = {
Wikipedia = {"Guyanese English", "Guyana"},
regional_categories = "Guyanese",
parent = "Caribbean,South America",
}
labels["Hong Kong"] = {
aliases = {"HK"},
Wikipedia = "Hong Kong English",
regional_categories = true,
parent = "China",
}
labels["Hungary"] = {
Wikipedia = true,
regional_categories = "Hungarian",
parent = "Europe",
}
labels["Indonesia"] = {
Wikipedia = "Indonesian English",
regional_categories = "Indonesian",
parent = "Southeast Asia",
}
labels["Israel"] = {
Wikipedia = "Israeli English",
regional_categories = "Israeli",
parent = "Middle East",
}
labels["Japan"] = {
Wikipedia = true,
regional_categories = "Japanese",
parent = "East Asia",
}
labels["Jamaica"] = {
aliases = {"Jamaican English", "Jamaican"},
Wikipedia = "Jamaican English",
regional_categories = "Jamaican",
parent = "Caribbean,Commonwealth",
}
labels["Kenya"] = {
Wikipedia = "Kenyan English",
regional_categories = "Kenyan",
parent = "East Africa",
}
labels["Liberia"] = {
Wikipedia = "Liberian English",
regional_categories = "Liberian",
parent = "West Africa",
}
labels["Libya"] = {
Wikipedia = true,
regional_categories = "Libyan",
parent = "Africa",
}
labels["Macau"] = {
Wikipedia = true,
regional_categories = "Macanese",
parent = "China",
}
labels["Mainland China"] = {
aliases = {"Mainland", "mainland", "mainland China"},
Wikipedia = true,
regional_categories = true,
parent = "China",
}
labels["Malaysia"] = {
aliases = {"Malaysian"},
Wikipedia = "Malaysian English",
regional_categories = "Malaysian",
parent = "Southeast Asia,Commonwealth",
}
labels["Malta"] = {
Wikipedia = "Maltese English",
regional_categories = "Maltese",
parent = "Europe",
}
labels["Manglish"] = {
def = "[[Manglish]], an English-based [[creole]] incorporating [[Malay]], [[Chinese]], and [[Tamil]] words; used informally in [[Malaysia]]",
Wikipedia = true,
plain_categories = true,
parent = "Malaysia",
}
labels["Mexico"] = {
Wikipedia = {"Mexican English", "Mexico"},
regional_categories = "Mexican",
parent = "rawposcat:North American English",
}
labels["Middle East"] = {
the = true,
Wikipedia = true,
regional_categories = "Middle Eastern",
parent = true,
}
labels["Myanmar"] = {
aliases = {"Burma"},
Wikipedia = "Myanmar English",
regional_categories = true,
parent = "Southeast Asia",
}
labels["Namibia"] = {
Wikipedia = "Namibian English",
regional_categories = "Namibian",
parent = "Africa",
}
labels["Natal"] = {
Wikipedia = "KwaZulu-Natal",
regional_categories = true,
parent = "South Africa",
}
labels["Nigeria"] = {
aliases = {"Nigerian"},
Wikipedia = "Nigerian English",
regional_categories = "Nigerian",
parent = "West Africa",
}
labels["Oceania"] = {
Wikipedia = "Oceanian English",
regional_categories = "Oceanian",
parent = true,
}
labels["Palestine"] = {
Wikipedia = true,
regional_categories = "Palestinian",
parent = "Middle East",
}
labels["Papua New Guinea"] = {
Wikipedia = "Papua New Guinean English",
regional_categories = "Papua New Guinean",
parent = "Oceania,Commonwealth",
}
labels["Philippines"] = {
the = true,
aliases = {"Philippine", "Philippine English"},
Wikipedia = "Philippine English",
regional_categories = "Philippine",
parent = "Southeast Asia",
}
labels["Baguio"] = {
Wikipedia = true,
regional_categories = true,
parent = "Philippines",
}
labels["Réunion"] = {
Wikipedia = true,
regional_categories = true,
parent = "Africa",
}
labels["Rhodesia"] = {
region = "the historical state of [[Rhodesia]]",
Wikipedia = "Zimbabwean English",
regional_categories = "Rhodesian",
parent = "Africa",
}
labels["Rwanda"] = {
Wikipedia = true,
regional_categories = "Rwandan",
parent = "Africa",
}
labels["Singapore"] = {
aliases = {"SG", "Singaporean"},
Wikipedia = "Singapore English",
regional_categories = true,
parent = "Southeast Asia,Commonwealth",
}
labels["Singlish"] = {
def = "[[Singlish]], an English-based [[creole]] incorporating many words of [[Chinese]], [[Malay]] and [[Indian]] origin; used informally in [[Singapore]]",
Wikipedia = true,
plain_categories = true,
parent = "Singapore",
}
labels["Solomon Islands"] = {
the = true,
Wikipedia = "Solomon Islands English",
regional_categories = true,
parent = "Oceania",
}
labels["South Africa"] = {
aliases = {"South African", "South African English", "ZA"},
Wikipedia = "South African English",
accent_Wikipedia = "South African English phonology",
accent_display = "General South African",
regional_categories = "South African",
parent = "Africa,Commonwealth",
}
labels["South America"] = {
aliases = {"South American"},
Wikipedia = "South American English",
regional_categories = "South American",
parent = true,
}
labels["South Korea"] = {
Wikipedia = "Korean English",
regional_categories = "South Korean",
parent = "East Asia",
}
labels["Southeast Asia"] = {
aliases = {"Southeast Asian", "South-East Asia", "South-East Asian", "South-east Asia", "South-east Asian", "SEA"},
Wikipedia = "Southeast Asian English",
regional_categories = "Southeast Asian",
parent = "Asia",
}
labels["Taiwan"] = {
aliases = {"Taiwanese"},
Wikipedia = true,
regional_categories = "Taiwanese",
parent = "East Asia",
}
labels["Tanzania"] = {
aliases = {"Tanzanian"},
Wikipedia = true,
regional_categories = "Tanzanian",
parent = "East Africa",
}
labels["Thailand"] = {
Wikipedia = "Tinglish",
regional_categories = "Thai",
parent = "Southeast Asia",
}
labels["Trinidad and Tobago"] = {
aliases = {"Trinidad", "Tobago", "Trinidadian"},
Wikipedia = "Trinidadian and Tobagonian English",
regional_categories = true,
parent = "Caribbean",
}
labels["Uganda"] = {
Wikipedia = "Ugandan English",
regional_categories = "Ugandan",
parent = "Africa",
}
labels["Vanuatu"] = {
Wikipedia = "Vanuatuan English",
regional_categories = true,
parent = "Oceania",
}
labels["Vietnam"] = {
Wikipedia = "Vietglish",
regional_categories = "Vietnamese",
parent = "Southeast Asia",
}
labels["West Africa"] = {
aliases = {"West African"},
Wikipedia = true,
regional_categories = "West African",
parent = "Africa",
}
labels["Zimbabwe"] = {
Wikipedia = "Zimbabwean English",
regional_categories = true,
parent = "Africa",
}
------------------------------------ non-regional ------------------------------------
labels["DoggoLingo"] = {
def = "[[DoggoLingo]]",
display = "[[DoggoLingo]]",
noreg = true,
plain_categories = true,
othercat = "English internet slang",
parent = "internet slang",
}
labels["Early Modern"] = {
prep = "from",
region = "the late 15th to the mid-17th centuries",
noreg = true,
nolink = true,
aliases = {"Early Modern English", "EME", "EMnE", "EModE"},
Wikipedia = "Early Modern English",
regional_categories = true,
parent = true,
}
labels["Late Modern"] = {
prep = "from",
region = "the mid-17th to the end of the 19th centuries",
noreg = true,
nolink = true,
aliases = {"Late Modern English", "LME"},
Wikipedia = "Late Modern English",
regional_categories = true,
parent = true,
}
-- for French terms used in French contexts
labels["Gallicism"] = {
noreg = true,
aliases = {"French", "Frenchism"},
plain_categories = true,
Wikipedia = "Glossary of French words and expressions in English",
othercat = "English terms by usage,English terms by orthographic property",
}
-- ideally this would sit at [[Module:category tree/pragmatic properties]] so it can be used for all languages, but those cats needs to begin with the language name...
labels["non-native speakers' English"] = {
display = "[[non-native speaker]]s' English",
noreg = true,
aliases = {"NNES", "NNSE"},
regional_categories = "Non-native speakers'",
Wikipedia = "English as a second or foreign language",
accent_Wikipedia = "Non-native pronunciations of English",
othercat = "English nonstandard terms,English terms by usage,English terms by orthographic property",
}
labels["Polari"] = {
def = "a form of cant slang used in [[Britain]] by some actors, circus and fairground showmen, professional wrestlers, merchant navy sailors, criminals, prostitutes, and the gay subculture",
noreg = true,
country = "the United Kingdom",
Wikipedia = true,
plain_categories = true,
othercat = "British slang,English cant,English gay slang",
parent = true,
}
-- Thieves' Cant is English-only, for other languages, use "criminal slang"
labels["thieves' cant"] = {
fulldef = "A secret language formerly used by thieves, beggars and hustlers of various kinds in [[Great Britain]] and to a lesser extent in other English-speaking countries",
noreg = true,
aliases = {"Thieves' Cant", "Thieves' cant", "thieves cant", "thieves'", "thieves"},
Wikipedia = true,
-- FIXME: Currently pos_categories aren't recognized.
-- pos_categories = "Thieves' Cant",
plain_categories = "English Thieves' Cant",
parent = true,
othercat = "English cant",
}
------------------------------------ English-specific qualifier labels ------------------------------------
labels["attributive"] = {
display = "[[Appendix:English nouns#Attributive|attributive]]",
}
labels["attributively"] = {
display = "[[Appendix:English nouns#Attributive|attributively]]",
}
------------------------------------ supporting [[Template:standard spelling of]] et al. ------------------------------------
labels["American spelling"] = {
aliases = {"American form", "US spelling", "US form",
-- As in "color" vs. "colour"
"or form", "-or form",
"or spelling", "-or spelling",
-- As in "meter" vs. "metre"
"er form", "-er form",
"er spelling", "-er spelling"
},
Wikipedia = "American and British English spelling differences",
form_of_display = "American",
plain_categories = "American English forms",
}
labels["Australian spelling"] = {
aliases = {"Australian form"},
Wikipedia = "Australian English#Spelling and style",
form_of_display = "Australian",
plain_categories = "Australian English forms",
}
labels["British spelling"] = {
aliases = {"British form", "UK spelling", "UK form"},
Wikipedia = "American and British English spelling differences",
form_of_display = "British",
plain_categories = "British English forms",
}
labels["Commonwealth spelling"] = {
aliases = {"Commonwealth form",
-- As in "color" vs. "colour"
"our form", "-our form", "our spelling", "-our spelling",
-- As in "meter" vs. "metre"
"re form", "-re form", "re spelling", "-re spelling"
},
Wikipedia = "American and British English spelling differences",
form_of_display = "Commonwealth",
plain_categories = {"British English forms", "Canadian English forms", "Australian English forms"},
}
labels["Canadian spelling"] = {
aliases = {"Canadian form"},
Wikipedia = true,
form_of_display = "Canadian",
plain_categories = "Canadian English forms",
}
labels["Oxford British spelling"] = {
aliases = {"Oxford", "Oxford form", "Oxford spelling", "en-GB-oxendict",},
display = "[[w:Oxford spelling|Oxford]] [[British English]]",
plain_categories = "Oxford spellings",
}
labels["non-Oxford British spelling"] = {
aliases = {"Non-Oxford British spelling", "non-Oxford British form", "Non-Oxford British form", "non-Oxford form", "Non-Oxford form", "non-Oxford", "Non-Oxford", "not Oxford", "Not Oxford"},
display = "non-[[w:Oxford spelling|Oxford]] [[British English]]",
plain_categories = "British English forms",
}
labels["ise spelling"] = {
aliases = {
"ise", "-ise", "ise form", "-ise form", "-ise spelling", "isation", "-isation", "isation form", "-isation form", "isation spelling", "-isation spelling",
"ise-form" -- backwards compatability
},
display = "non-[[w:Oxford spelling|Oxford]] [[w:American and British English spelling differences|British spelling]]",
form_of_display = "non-[[w:Oxford spelling|Oxford]] [[w:American and British English spelling differences|British]]",
plain_categories = "British English forms",
}
labels["ize spelling"] = {
aliases = {
"ize", "-ize", "ize form", "-ize form", "-ize spelling", "ization", "-ization", "ization form", "-ization form", "ization spelling", "-ization spelling",
"ize-form" -- backwards compatability
},
display = "[[w:American and British English spelling differences|American]] and [[w:Oxford spelling|Oxford]] [[British English|British spelling]]",
form_of_display = "[[w:American and British English spelling differences|American]] and [[w:Oxford spelling|Oxford]] [[British English|British]]",
plain_categories = {"American English forms", "Oxford spellings"},
}
------------------------------------ supporting [[Template:inflection of]] ------------------------------------
-- This is added to an inflection line when something like {{infl of|en|make||th-form}} is used. Specifically, the form-of
-- tag 'th-form' is a shortcut for '3-th|s|spres|ind' (see [[Module:form of/lang-data/en]]); the tag '3-th' displays as
-- "third-person" (see [[Module:form of/lang-data/en]]) and attaches the following label (see [[Module:form of/cats]]).
labels["archaic third singular"] = {
display = "archaic",
Wikipedia = "English verbs#Archaic forms",
pos_categories = "archaic third-person singular forms",
}
-- This is added to an inflection line when something like {{infl of|en|make||st-form}} is used. See above.
labels["archaic second singular present"] = {
display = "archaic",
Wikipedia = "English verbs#Archaic forms",
pos_categories = "second-person singular forms",
}
-- This is added to an inflection line when something like {{infl of|en|make||st-past-form}} is used. See above.
labels["archaic second singular past"] = {
display = "archaic",
Wikipedia = "English verbs#Archaic forms",
pos_categories = "second-person singular past tense forms",
}
------------------------------------ accent qualifiers ------------------------------------
-- Generate the inverse of a sound change. In general, we should do this for sound changes that are either
-- extremely common (e.g. 'cot-caught') or dominant ('horse-hoarse', 'wine-whine') or are at least locally
-- dominant (e.g. 'cheer-chair'). The idea is that when presenting the pronunciation of an area with a locally
-- dominant pronunciation, we may want to also present the alternative pronunciation lacking the change, as long
-- as it is found at least somewhere in the area. Hence, for New Zealand, which typically has the cheer-chair
-- merger, we might might to present the non-merger pronunciation as well; but for a change like card-cord that
-- is recessive everywhere, it's unlikely we'll need to specifically highlight the inverse pronunciation (which
-- would be the standard, already covered elsewhere), and we can save memory and time by omitting the label.
local function generate_non(key, display)
if not labels[key] then
error(("Internal error: No label definition for key '%s'"):format(key))
end
local labval = mw.clone(labels[key])
labels["non-" .. key] = labval
if labval.aliases then
for i, alias in ipairs(labval.aliases) do
labval.aliases[i] = "non-" .. alias
end
end
if not display then
display = labval.display or key
if display:find("ing$") or display:find("[st]ion$") then
-- e.g. "Canadian raising", "t-glottalization"
display = "without " .. display
else
-- e.g. "cot-caught merger"
display = "without the " .. display
end
end
labval.display = display
end
labels["Anglicised"] = {
aliases = {"Anglicized"},
Wikipedia = "Anglicisation#Anglicisation of non-English-language vocabulary and names",
}
labels["bad-lad split"] = {
Wikipedia = true,
display = "{{m|en||bad}}–{{m|en||lad}} split",
}
labels["Canadian raising"] = {
aliases = {"North American raising"},
Wikipedia = true,
}
generate_non("Canadian raising")
labels["Canadian shift"] = {
aliases = {"Canadian Shift", "Canadian vowel shift", "Canadian Vowel Shift"},
Wikipedia = true,
display = "Canadian vowel shift",
}
generate_non("Canadian shift")
labels["card-cord"] = {
Wikipedia = "Card-cord merger",
display = "{{m|en||card}}–{{m|en||cord}} merger",
}
labels["cheer-chair"] = {
aliases = {"near-square"},
Wikipedia = "near-square merger",
display = "{{m|en||cheer}}–{{m|en||chair}} merger",
}
generate_non("cheer-chair")
labels["cot-caught"] = {
aliases = {"caught-cot"},
Wikipedia = "Cot–caught merger",
display = "{{m|en||cot}}–{{m|en||caught}} merger",
}
generate_non("cot-caught")
labels["cure-fir"] = {
aliases = {"cure-nurse"},
Wikipedia = "Cure-nurse merger",
display = "{{m|en||cure}}–{{m|en||fir}} merger",
}
generate_non("cure-fir")
labels["doll-dole"] = {
Wikipedia = "Doll-dole merger",
display = "{{m|en||doll}}–{{m|en||dole}} merger",
}
labels["dough-door"] = {
Wikipedia = "Dough-door merger",
display = "{{m|en||dough}}–{{m|en||door}} merger",
}
generate_non("dough-door")
labels["Estuary English"] = {
Wikipedia = true,
}
labels["fair-fur"] = {
aliases = {"square-nurse"},
Wikipedia = "Square-nurse merger",
display = "{{m|en||fair}}–{{m|en||fur}} merger",
}
generate_non("fair-fur")
labels["father-bother"] = {
Wikipedia = "Father–bother merger",
display = "{{m|en||father}}–{{m|en||bother}} merger",
}
generate_non("father-bother")
labels["fern-fir-fur"] = {
aliases = {"nurse merger"},
Wikipedia = "Fern-fir-fur merger",
display = "{{m|en||fern}}–{{m|en||fir}}–{{m|en||fur}} merger",
}
generate_non("fern-fir-fur")
labels["fool-fall"] = {
Wikipedia = "English-language vowel changes before historic /l/#Fool–fall_merger",
display = "{{m|en||fool}}–{{m|en||fall}} merger",
}
labels["foot-goose"] = {
Wikipedia = "Foot-goose merger",
display = "{{m|en||foot}}–{{m|en||goose}} merger",
}
-- Most labels of the form 'foo-bar' are mergers. Since this is rather a split, include the word "split"
-- for clarity.
labels["foot-strut split"] = {
Wikipedia = "Phonological history of English close back vowels#FOOT–STRUT split",
display = "{{m|en||foot}}–{{m|en||strut}} split",
}
generate_non("foot-strut split")
labels["full-fool"] = {
Wikipedia = "English-language vowel changes before historic /l/#Full–fool_merger",
display = "{{m|en||full}}–{{m|en||fool}} merger",
}
labels["g-dropping"] = {
aliases = {"g dropping"},
Wikipedia = "G-dropping",
display = "{{m|en||g}}-dropping",
}
generate_non("g-dropping")
labels["General American"] = {
aliases = {"GenAm", "GA"},
Wikipedia = "General American English",
}
labels["glottalized"] = {
aliases = {"glottalization", "glottalised", "glottalisation"},
Wikipedia = "Phonological history of English consonant clusters#Glottalization",
}
labels["goose split"] = {
-- Phonemic split in some Southeastern England English variants.
Wikipedia = "English-language vowel changes before historic /l/#GOOSE split",
display = "{{m|en||goose}} split",
}
labels["gulf-golf"] = {
Wikipedia = "English-language vowel changes before historic /l/#Gulf-golf merger",
display = "{{m|en||gulf}}–{{m|en||golf}} merger",
}
labels["h-dropping"] = {
Wikipedia = "H-dropping",
display = "{{m|en||h}}-dropping",
}
generate_non("h-dropping")
labels["happy-tensing"] = {
aliases = {"happy tensing"},
Wikipedia = "Happy tensing",
display = "{{m|en||happy}}-tensing",
}
generate_non("happy-tensing")
labels["horse-hoarse"] = {
Wikipedia = "horse–hoarse merger",
display = "{{m|en||horse}}–{{m|en||hoarse}} merger",
}
generate_non("horse-hoarse")
labels["hurry-furry"] = {
Wikipedia = "hurry-furry merger",
display = "{{m|en||hurry}}–{{m|en||furry}} merger",
}
generate_non("hurry-furry")
labels["Inland Northern US"] = {
aliases = {"Great Lakes", "Inland Northern", "Inland North", "Inland Northern American", "Inland Northern American English", "Inland Northern English", "Northern Cities Vowel Shift", "US Inland North", "northern cities vowel shift"},
Wikipedia = "Inland Northern American English",
display = "Inland Northern American",
}
labels["intrusive r"] = {
aliases = {"intrusive R", "intrusive-r", "intrusive-R", "intrusive -r-", "intrusive -R-", 'intrusive "r"', 'intrusive "R"', 'intrusive "-r-"', 'intrusive "-R-"', "intrusive /r/", "intrusive /ɹ/"},
Wikipedia = "Intrusive R",
display = "intrusive R",
}
generate_non("intrusive r", "without intrusive R")
labels["laxing"] = {
Wikipedia = "Trisyllabic laxing"
}
generate_non("laxing")
labels["linking r"] = {
aliases = {"linking R", "linking-r", "linking-R", "linking -r-", "linking -R-", 'linking "r"', 'linking "R"', 'linking "-r-"', 'linking "-R-"', "linking /r/", "linking /ɹ/"},
Wikipedia = "Linking R",
display = "linking R"
}
labels["linking w"] = {
aliases = {"linking W", "linking-w", "linking-W", "linking -w-", "linking -W-", 'linking "w"', 'linking "W"', 'linking "-w-"', 'linking "-W-"', "linking /w/"},
Wiktionary = "Appendix:English_pronunciation#Linking_semivowels",
display = "linking W"
}
labels["linking y"] = {
aliases = {"linking Y", "linking-y", "linking-Y", "linking -y-", "linking -Y-", 'linking "y"', 'linking "Y"', 'linking "-y-"', 'linking "-Y-"', "linking /j/"},
Wiktionary = "Appendix:English_pronunciation#Linking_semivowels",
display = "linking Y"
}
labels["l-vocalization"] = {
aliases = {"l-vocalisation"},
Wikipedia = "L-vocalization#Modern English",
display = "{{m|en||l}}-vocalization",
}
generate_non("l-vocalization")
labels["Latinate"] = {
Wikipedia = "Latin#Phonology",
}
labels["lot-cloth split"] = {
Wikipedia = true,
display = "{{m|en||lot}}–{{m|en||cloth}} split",
}
generate_non("lot-cloth split")
labels["low-back-merger shift"] = {
aliases = {"LBMS", "low back merger shift"},
Wikipedia = "Low-back-merger shift",
display = "low-back-merger shift",
}
labels["Mary-marry-merry"] = {
aliases = {"Mmmm"},
Wikipedia = "Mary–marry–merry merger",
display = "{{m|en||Mary}}–{{m|en||marry}}–{{m|en||merry}} merger",
}
generate_non("Mary-marry-merry")
table.insert(labels["non-Mary-marry-merry"].aliases, "nMmmm")
labels["Mary-marry"] = {
Wikipedia = "Mary–marry–merry merger",
display = "{{m|en||Mary}}–{{m|en||marry}} merger",
parent = "Mary-marry-merry",
}
generate_non("Mary-marry")
labels["Mary-merry"] = {
Wikipedia = "Mary–marry–merry merger",
display = "{{m|en||Mary}}–{{m|en||merry}} merger",
parent = "Mary-marry-merry",
}
generate_non("Mary-merry")
labels["marry-merry"] = {
Wikipedia = "Mary–marry–merry merger",
display = "{{m|en||marry}}–{{m|en||merry}} merger",
parent = "Mary-marry-merry",
}
generate_non("marry-merry")
labels["merry-Murray"] = {
aliases = {"Merry-Murray"},
Wikipedia = "Merry–Murray merger",
display = "{{m|en||merry}}–{{m|en||Murray}} merger",
}
labels["mirror-nearer"] = {
aliases = {"Sirius-serious"},
Wikipedia = "Mirror-nearer merger",
display = "{{m|en||mirror}}–{{m|en||nearer}} merger",
}
generate_non("mirror-nearer")
labels["nt-flapping"] = {
Wikipedia = "Flapping#Distribution",
display = "{{m|en||nt}}-flapping",
}
generate_non("nt-flapping")
labels["pane-pain"] = {
Wikipedia = "Pane–pain merger",
display = "{{m|en||pane}}–{{m|en||pain}} merger"
}
generate_non("pane-pain")
labels["paw-poor"] = {
Wikipedia = "Rhoticity in English#/ɔː/–/ʊər/ merger",
display = "{{m|en||paw}}–{{m|en||poor}} merger",
}
labels["pin-pen"] = {
aliases = {"pen-pin"},
Wikipedia = "pin–pen merger",
display = "{{m|en||pin}}–{{m|en||pen}} merger",
}
labels["pour-poor"] = {
aliases = {"poor-pour", "cure-force"},
Wikipedia = "Cure–force merger",
display = "{{m|en||pour}}–{{m|en||poor}} merger",
}
generate_non("pour-poor")
labels["r-dissimilation"] = {
Wikipedia = "Dissimilation",
display = "{{m|en||r}}-dissimilation",
}
labels["rhotic"] = {
Wikipedia = "Rhoticity in English",
}
labels["non-rhotic"] = {
aliases = {"nonrhotic"},
Wikipedia = "Rhoticity in English",
}
labels["Received Pronunciation"] = {
aliases = {"RP"},
Wikipedia = true,
}
labels["salary-celery"] = {
Wikipedia = "Salary–celery merger",
display = "{{m|en||salary}}–{{m|en||celery}} merger",
}
labels["show-sure"] = {
Wikipedia = "Show-sure merger",
display = "{{m|en||show}}–{{m|en||sure}} merger",
}
labels["Standard Southern British English"] = {
aliases = {"SSB", "SSBE", "Standard Southern British"},
Wikipedia = "Standard Southern British",
display = "Standard Southern British",
}
labels["stressed"] = {
Wikipedia = "Stress and vowel reduction in English#Weak and strong forms of function words",
display = "stressed form",
}
labels["tar-tire"] = {
Wikipedia = "/aɪər/–/ɑr/ merger",
display = "tar-tire merger",
}
labels["tar-tire-tower"] = {
Wikipedia = "English-language vowel changes before historic /r/#/aɪə/–/aʊə/–/ɑː/ merger",
display = "tar-tire-tower merger",
}
labels["t-flapping"] = {
Wikipedia = "t-flapping",
}
generate_non("t-flapping")
labels["t-glottalization"] = {
aliases = {"t-glottalisation", "t-glottaling"},
Wikipedia = "T-glottalization",
display = "{{m|en||t}}-glottalization",
}
generate_non("t-glottalization")
labels["th-fronting"] = {
Wikipedia = true,
display = "{{m|en||th}}-fronting",
}
labels["th-stopping"] = {
Wikipedia = true,
display = "{{m|en||th}}-stopping",
}
labels['toe-tow'] = {
Wikipedia = "Phonological history of English diphthongs#Toe–tow merger",
display = "{{m|en||toe}}–{{m|en||tow}} merger"
}
generate_non("toe-tow")
labels["trap-bath split"] = {
Wikipedia = "trap–bath split",
display = "{{m|en||trap}}–{{m|en||bath}} split",
}
generate_non("trap-bath split")
labels["triphthong smoothing"] = {
Wikipedia = "Triphthong smoothing",
display = "triphthong smoothing",
}
generate_non("triphthong smoothing")
labels["unstressed"] = {
Wikipedia = "Stress and vowel reduction in English#Weak and strong forms of function words",
display = "unstressed form",
}
labels["weak vowel"] = {
aliases = {"weak vowel merger"},
Wikipedia = "Weak vowel merger",
display = "weak vowel merger",
}
generate_non("weak vowel", "weak vowel distinction")
labels["wine-whine"] = {
Wikipedia = "wine–whine merger",
display = "{{m|en||wine}}–{{m|en||whine}} merger",
}
generate_non("wine-whine")
labels["yod-coalescence"] = {
aliases = {"yod coalescence"},
Wikipedia = "yod-coalescence",
}
generate_non("yod-coalescence")
labels["yod-dropping"] = {
aliases = {"yod dropping"},
Wikipedia = "yod-dropping",
}
generate_non("yod-dropping")
labels["ng-coalescence"] = {
aliases = {"NG-coalescence", "ng coalescence", "NG coalescence"},
Wikipedia = "Ng coalescence",
display = "{{m|en||ng}}-coalescence",
}
generate_non("ng-coalescence")
labels["æ-raising"] = {
aliases = {"æ-tensing", "/æ/ raising", "/æ/ tensing", "ae-raising", "ae-tensing"},
Wikipedia = "/æ/ raising",
display = "{{IPAchar|/æ/}} raising",
}
generate_non("æ-raising")
return require("Module:labels").finalize_data(labels)
38e2qdlxj2dbc8uiqukjhki5g7s10br
Module:labels/data/qualifiers
828
8151
55056
2026-09-30T22:10:19Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local labels = {} -- Qualifiers and similar labels. -- NOTE: This module is loaded both by [[Module:labels]] and by [[Module:accent qualifier]]. -- Helper labels labels["_"] = { display = "", omit_preComma = true, omit_postComma = true, } labels[","] = { -- forced comma omit_preComma = true, omit_postComma = true, omit_preSpace = true, } labels[";"] = { omit_preComma = true, omit_postComma = true, omit_preSpace = true, } labels[":"] = { omit_preC...'
55056
Scribunto
text/plain
local labels = {}
-- Qualifiers and similar labels.
-- NOTE: This module is loaded both by [[Module:labels]] and by [[Module:accent qualifier]].
-- Helper labels
labels["_"] = {
display = "",
omit_preComma = true,
omit_postComma = true,
}
labels[","] = { -- forced comma
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
}
labels[";"] = {
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
}
labels[":"] = {
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
}
labels["?"] = {
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
}
labels["(?)"] = {
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
}
labels["-"] = { -- hyphen
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
omit_postSpace = true,
}
labels["–"] = { -- en dash
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
omit_postSpace = true,
}
labels["—"] = { -- em dash
omit_preComma = true,
omit_postComma = true,
omit_preSpace = true,
omit_postSpace = true,
}
labels["also"] = {
omit_postComma = true,
}
labels["and"] = {
aliases = {"&"},
omit_preComma = true,
omit_postComma = true,
}
-- e.g. "informal, but formal in Louisiana"
labels["but"] = {
omit_postComma = true,
}
labels["by"] = {
omit_preComma = true,
omit_postComma = true,
}
-- e.g. "except in", "except with" etc.
labels["except"] = {
omit_preComma = true,
omit_postComma = true,
}
labels["or"] = {
omit_preComma = true,
omit_postComma = true,
}
labels["outside"] = {
aliases = {"except in"},
omit_preComma = true,
omit_postComma = true,
}
labels["with"] = {
aliases = {"+"},
omit_preComma = true,
omit_postComma = true,
}
-- Qualifier labels
labels["attested in"] = {
omit_postComma = true,
}
labels["chiefly"] = {
aliases = {"mainly", "mostly", "primarily", "Chiefly"},
omit_postComma = true,
}
labels["especially"] = {
omit_postComma = true,
}
labels["excluding"] = {
omit_postComma = true,
}
labels["exclusively"] = {
aliases = {"strictly"},
omit_postComma = true,
}
labels["extremely"] = {
omit_postComma = true,
}
labels["formerly"] = {
omit_postComma = true,
}
labels["frequently"] = {
omit_postComma = true,
}
-- e.g. "highly nonstandard"
labels["highly"] = {
omit_postComma = true,
}
labels["in"] = {
omit_postComma = true,
}
labels["in a"] = {
omit_postComma = true,
}
labels["in an"] = {
omit_postComma = true,
}
labels["in the"] = {
omit_postComma = true,
}
labels["including"] = {
omit_postComma = true,
}
-- e.g. "less common"
labels["less"] = {
omit_postComma = true,
}
-- e.g. "many dialects"
labels["many"] = {
omit_postComma = true,
}
labels["markedly"] = {
omit_postComma = true,
}
labels["mildly"] = {
omit_postComma = true,
}
-- e.g. "more common"
labels["more"] = {
omit_postComma = true,
}
labels["now"] = {
aliases = {"nowadays"},
omit_postComma = true,
}
labels["occasionally"] = {
omit_postComma = true,
}
labels["of"] = {
omit_postComma = true,
}
labels["of a"] = {
omit_postComma = true,
}
labels["of an"] = {
omit_postComma = true,
}
labels["of the"] = {
omit_postComma = true,
}
labels["often"] = {
aliases = {"commonly"},
omit_postComma = true,
}
labels["originally"] = {
omit_postComma = true,
}
-- e.g. "law, otherwise archaic"
labels["otherwise"] = {
omit_postComma = true,
}
labels["particularly"] = {
omit_postComma = true,
}
labels["possibly"] = {
-- aliases = {"perhaps"},
omit_postComma = true,
}
labels["predominantly"] = {
omit_postComma = true,
}
labels["rarely"] = {
omit_postComma = true,
}
labels["rather"] = {
omit_postComma = true,
}
labels["relatively"] = {
omit_postComma = true,
}
labels["slightly"] = {
omit_postComma = true,
}
labels["sometimes"] = {
omit_postComma = true,
}
labels["somewhat"] = {
omit_postComma = true,
}
labels["strongly"] = {
omit_postComma = true,
}
labels["the"] = {
omit_postComma = true,
}
-- e.g. "then colloquial, now dated"
labels["then"] = {
omit_postComma = true,
}
labels["typically"] = {
omit_postComma = true,
}
labels["usually"] = {
omit_postComma = true,
}
labels["very"] = {
omit_postComma = true,
}
labels["with a"] = {
omit_postComma = true,
}
labels["with an"] = {
omit_postComma = true,
}
labels["with the"] = {
omit_postComma = true,
}
labels["with respect to"] = {
aliases = {"wrt"},
omit_postComma = true,
}
return require("Module:labels").finalize_data(labels)
ow7lz65j1yqeur8mu3w2vng38trdiut
Module:labels/data/regional
828
8152
55057
2026-09-30T22:11:34Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local labels = {} ------------------------------------------ Generic ------------------------------------------ --not sure where to put this labels["Classical"] = { aliases = {"classical"}, -- "ar", ca", "fa", "id", la", "zh" handled in lang-specific module langs = {"az", "ja", "jv", "kum", "ms", "quc", "sa", "tl"}, special_display = "[[Classical <canonical_name>]]", regional_categories = true, } labels["Epigraphic"] = { langs = {"grc", "pgd", "pra", "sa...'
55057
Scribunto
text/plain
local labels = {}
------------------------------------------ Generic ------------------------------------------
--not sure where to put this
labels["Classical"] = {
aliases = {"classical"},
-- "ar", ca", "fa", "id", la", "zh" handled in lang-specific module
langs = {"az", "ja", "jv", "kum", "ms", "quc", "sa", "tl"},
special_display = "[[Classical <canonical_name>]]",
regional_categories = true,
}
labels["Epigraphic"] = {
langs = {"grc", "pgd", "pra", "sa"},
special_display = "[[w:Epigraphy|Epigraphic <canonical_name>]]",
regional_categories = true,
}
labels["regional"] = {
aliases = {"regionally"},
display = "[[regional#English|regional]]",
regional_categories = true,
}
------------------------------------------ Places ------------------------------------------
labels["Anatri"] = {
aliases = {"Lower Chuvash"},
langs = {"cv"}, -- e.g. вот "fire" vs the Upper Chuvash / literary standard вут
Wikipedia = true,
regional_categories = true,
}
labels["Australia"] = {
aliases = {"AU", "Australian"},
-- "de", "en", "mt", "zh" handled in lang-specific modules
langs = {"el", "it", "ko", "ru"},
Wikipedia = true,
regional_categories = "Australian",
}
labels["Black Isle"] = {
langs = {"sco"}, -- conceivably also en, gd, perhaps enm, but -sche could only find sco
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Bogor"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
labels["Brazil"] = {
aliases = {"Brazilian"},
-- "pt" handled in lang-specific module
langs = {"ja", "mch", "vec", "yi"},
Wikipedia = true,
regional_categories = "Brazilian",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Brebes"] = {
aliases = {"Brebian"},
langs = {},
Wikipedia = "Brebes Regency",
regional_categories = true,
}
labels["Bukovina"] = {
aliases = {"Bucovina", "Bukovinian", "Bukowina"},
langs = {"pl", "ro"},
Wikipedia = true,
regional_categories = "Bukovinian",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Burundi"] = {
aliases = {"Burundian"},
langs = {},
Wikipedia = true,
regional_categories = "Burundian",
}
labels["Canada"] = {
aliases = {"Canadian"},
-- "en", "fr", "zh" handled in lang-specific module
langs = {"gd", "haa", "is", "ko", "ru", "tli", "vi"},
Wikipedia = true,
regional_categories = "Canadian",
}
labels["China"] = {
-- "en", "ko" handled in lang-specific module
langs = {"ja", "khb", "kk", "mhx", "mn", "ug"},
Wikipedia = true,
regional_categories = "Chinese",
}
labels["Cisalpine"] = {
aliases = {"xcg"},
langs = {"cel-gau"},
Wikipedia = "Cisalpine Gaulish",
regional_categories = "Cisalpine",
}
labels["Congo"] = {
aliases = {"Democratic Republic of the Congo", "Democratic Republic of Congo", "DR Congo", "Congo-Kinshasa", "Republic of the Congo", "Republic of Congo", "Congo-Brazzaville", "Congolese"}, -- these could be split if need be
-- "fr" handled in lang-specific module
langs = {"avu", "yom"},
Wikipedia = true,
regional_categories = "Congolese",
}
labels["Cyprus"] = {
aliases = {"cypriot", "Cypriot"},
-- "ar", tr" handled in lang-specific module
langs = {"el"},
Wikipedia = true,
regional_categories = "Cypriot",
}
labels["Dobruja"] = {
aliases = {"Dobrogea", "Dobrujan"},
langs = {"crh", "ro"},
Wikipedia = true,
regional_categories = "Dobrujan",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Durban"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
labels["Europe"] = {
-- "en", "es", "fr", "pt" handled in lang-specific module
langs = {"ur"},
Wikipedia = true,
regional_categories = "European",
}
labels["France"] = {
aliases = {"French"},
-- "fr", "zh" handled in lang-specific module
langs = {"la", "lad", "nrf", "vi", "yi"},
Wikipedia = true,
regional_categories = "French",
}
labels["India"] = {
aliases = {"Indian"},
-- "en", "pa", "pt" handled in lang-specific module
langs = {"bn", "dv", "fa", "ml", "ta", "ur"},
Wikipedia = true,
regional_categories = "Indian",
}
labels["Indonesia"] = {
aliases = {"Indonesian"},
-- "en", "zh" handled in lang-specific module
langs = {"id", "jv", "ms", "nl"},
Wikipedia = true,
regional_categories = "Indonesian",
}
labels["Israel"] = {
aliases = {"Israeli"},
-- "ar", en" handled in lang-specific module
langs = {"ajp", "he", "ru", "yi"},
Wikipedia = true,
regional_categories = "Israeli",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Kalix"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
-- FIXME: Move to Uyghur label data module
labels["Kazakhstan"] = {
aliases = {"Kazakhstani", "Kazakh"},
langs = {"ug"},
Wikipedia = true,
regional_categories = "Kazakhstani",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Kemaliye"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Kitti"] = {
langs = {},
Wikipedia = "Kitti, Federated States of Micronesia",
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Kukkuzi"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Lucknow"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Luleå"] = {
aliases = {"Lulea"},
langs = {},
Wikipedia = true,
regional_categories = true,
}
labels["Lviv"] = {
aliases = {"Lvov", "Lwow", "Lwów"},
langs = {"pl"},
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Muş"] = {
aliases = {"Mush"},
langs = {},
Wikipedia = true,
regional_categories = true,
}
labels["Myanmar"] = {
aliases = {"Myanmarese", "Burma", "Burmese"},
-- "en", "my", "zh" handled in lang-specific module; FIXME: move ksw and mnw to lang-specific modules
langs = {"ksw", "mnw"},
Wikipedia = true,
regional_categories = true,
}
labels["Nigeria"] = {
aliases = {"Nigerian"},
-- "ar", en" handled in lang-specific module
langs = {"ff", "guw", "ha", "yo"},
Wikipedia = true,
regional_categories = "Nigerian",
}
labels["Palestine"] = {
aliases = {"Palestinian"},
-- "ar", "en" handled in lang-specific module
langs = {"ajp", "arc"},
Wikipedia = true,
regional_categories = "Palestinian",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Priangan"] = {
langs = {},
Wikipedia = "Parahyangan",
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Rome"] = {
aliases = {"Roma", "Romano"},
langs = {},
Wikipedia = true,
regional_categories = "Roman",
}
labels["Scania"] = {
aliases = {"Scanian", "Skanian", "Skåne"},
langs = {"gmq-oda", "sv"},
Wikipedia = true,
regional_categories = "Scanian",
}
-- Silesia German, Silesia Polish; for differentiation between sli "Silesian East Central German"
-- don't add Silesian as alias
labels["Silesia"] = {
langs = {"de", "pl"},
Wikipedia = true,
}
labels["South Africa"] = {
aliases = {"South African"},
-- "de", "en", "pt" handled in lang-specific module
langs = {"af", "nl", "st", "te", "yi", "zu"},
Wikipedia = true,
regional_categories = "South African",
}
labels["Spain"] = {
aliases = {"Spanish", "ES"},
-- "ca", "es" handled in lang-specific module
langs = {"la"},
Wikipedia = true,
regional_categories = "Spanish",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Surati"] = {
langs = {},
Wikipedia = "Surat district",
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Surgut"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
labels["Suriname"] = {
aliases = {"Surinamese"},
langs = {"car", "hns", "jv", "nl"},
Wikipedia = true,
regional_categories = "Surinamese",
}
labels["Thailand"] = {
aliases = {"Thai"},
-- "en", "zh" handled in lang-specific module
langs = {"khb", "mnw", "th"},
Wikipedia = true,
regional_categories = "Thai",
}
labels["Transalpine"] = {
aliases = {"xtg"},
langs = {"cel-gau"},
Wikipedia = "Transalpine Gaulish",
regional_categories = "Transalpine",
}
labels["UK"] = {
aliases = {"United Kingdom", "Britain", "Brit", "British", "Great Britain"},
-- "en", "zh" handled in lang-specific module
langs = {"bn", "ur", "vi"},
Wikipedia = "United Kingdom",
regional_categories = "British",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Old Ukrainian"] = {
langs = {},
Wikipedia = true,
plain_categories = true,
}
labels["US"] = {
aliases = {"U.S.", "United States", "United States of America", "USA", "America", "American"}, -- America/American: should these be aliases of 'North America'?
-- DO NOT include "es" here, otherwise {{lb|es|American}} will categorize in [[:Category:American Spanish]]; see [[:Category:United States Spanish]].
-- "de", "en", "pt", "zh" handled in lang-specific module
langs = {"hi", "is", "it", "ja", "ko", "nl", "ru", "tli", "ur", "vi", "yi"},
Wikipedia = "United States",
regional_categories = "American",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Vilhelmina"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
labels["Viryal"] = {
aliases = {"Upper Chuvash"},
langs = {"cv"},
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Wallonia"] = {
aliases = {"Wallonian"},
langs = {},
Wikipedia = true,
regional_categories = "Wallonian",
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Special Region of Yogyakarta"] = {
aliases = {"SR Yogyakarta"},
langs = {},
Wikipedia = true,
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Zakarpattia"] = {
langs = {},
Wikipedia = "Zakarpattia Oblast",
regional_categories = true,
}
-- WARNING: No existing languages or categories associated with label; add to `langs` as needed
labels["Zululand"] = {
langs = {},
Wikipedia = true,
regional_categories = true,
}
------------------------------------------ Chinese romanizations ------------------------------------------
labels["Hanyu Pinyin"] = {
aliases = {"Hanyu pinyin", "Pinyin", "pinyin"},
Wikidata = "Q42222",
plain_categories = true,
}
labels["Postal Romanization"] = {
aliases = {"Postal romanization", "postal romanization"},
Wikidata = "Q151868",
plain_categories = true,
}
labels["Tongyong Pinyin"] = {
aliases = {"Tongyong pinyin"},
Wikidata = "Q700739",
plain_categories = true,
}
labels["Wade–Giles"] = {
aliases = {"Wade-Giles"},
Wikidata = "Q208442",
plain_categories = true,
}
return require("Module:labels").finalize_data(labels)
473c6skcpxp1apfry68vrgkn72k2wlr
Module:labels/data/topical
828
8153
55058
2026-09-30T22:12:02Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local labels = {} -- To sort these, you first have to convert each label section into a single line, and then sort the lines, and undo -- the single-line conversion. This can be done using Vim commands, something like this: -- 1. Mark the first line to be changed using `ma`. -- 2. Go to the last line and use `'a,.s/\n/\\n/g` to convert newlines to \n sequences. -- 3. Use `'a,.s/\\n\\n/\r/g` to convert sequences of two \n's (marking section divisions) back to new...'
55058
Scribunto
text/plain
local labels = {}
-- To sort these, you first have to convert each label section into a single line, and then sort the lines, and undo
-- the single-line conversion. This can be done using Vim commands, something like this:
-- 1. Mark the first line to be changed using `ma`.
-- 2. Go to the last line and use `'a,.s/\n/\\n/g` to convert newlines to \n sequences.
-- 3. Use `'a,.s/\\n\\n/\r/g` to convert sequences of two \n's (marking section divisions) back to newlines.
-- 4. Go to the last line again and use `'a,.!sort -f -d` to sort. The `-f` makes it case-insensitive and the `-d`
-- selects "dictionary order", which is needed to get 'yoga' to sort before 'yoga pose' instead of the other way
-- around.
-- 5. Go to the last line again and use `'a,.s/\\n/\r/g` to convert \n sequences back to newlines.
-- 6. Go to the last line again and use `'a,.s/^labels/\rlabels/` to put an extra newline before each section.
labels["3D printing"] = {
aliases = {"3D printer", "3D printers"},
Wiktionary = "3D printing#Noun",
Wikipedia = true,
Wikidata = "Q229367",
topical_categories = true,
}
labels["ABDL"] = {
aliases = {"AB/DL"},
Wiktionary = true,
Wikipedia = true,
topical_categories = true,
}
labels["Abrahamism"] = {
Wiktionary = "Abrahamism#Noun",
topical_categories = true,
}
labels["accounting"] = {
Wiktionary = "accounting#Noun",
topical_categories = true,
}
labels["acoustics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["acting"] = {
Wiktionary = "acting#Noun",
topical_categories = true,
}
labels["advertising"] = {
Wiktionary = "advertising#Noun",
topical_categories = true,
}
labels["aeronautics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["aesthetic"] = {
aliases = {"aesthetics"},
Wiktionary = true,
topical_categories = "Aesthetics",
}
labels["age regression"] = {
aliases = {"agere", "agereg"},
Wiktionary = true,
topical_categories = "Age regression"
}
labels["ageplay"] = {
aliases = {"age play"},
Wiktionary = true,
topical_categories = true,
}
labels["agriculture"] = {
aliases = {"farming"},
Wiktionary = true,
topical_categories = true,
}
labels["Ahmadiyya"] = {
aliases = {"Ahmadiyyat", "Ahmadi"},
Wiktionary = true,
topical_categories = true,
}
labels["aircraft"] = {
Wiktionary = true,
topical_categories = true,
}
labels["aircraft components"] = {
aliases = {"aircraft component"},
display = "[[aircraft]]",
topical_categories = true,
}
labels["alchemy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["alcoholic beverages"] = {
aliases = {"alcohol"},
display = "[[alcoholic#Adjective|alcoholic]] [[beverage]]s",
topical_categories = true,
}
labels["alcoholism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["algebra"] = {
Wiktionary = true,
topical_categories = true,
}
labels["algebraic geometry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["algebraic topology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["algorithms"] = {
aliases = {"algorithm"},
display = "[[computer science]]",
topical_categories = true,
}
labels["alternative history"] = {
aliases = {"alt hist", "alternate history"},
Wikidata = "Q224989",
topical_categories = true,
}
labels["alternative medicine"] = {
Wiktionary = true,
topical_categories = true,
}
labels["alt-right"] = {
aliases = {"altright", "Alt-right", "Altright"},
Wiktionary = true,
topical_categories = true,
}
labels["amateur radio"] = {
aliases = {"ham radio"},
Wiktionary = true,
topical_categories = true,
}
labels["American football"] = {
Wiktionary = true,
topical_categories = "Football (American)",
}
labels["amino acid"] = {
display = "[[biochemistry]]",
topical_categories = "Amino acids",
}
labels["analytic geometry"] = {
Wiktionary = true,
topical_categories = "Geometry",
}
labels["analytical chemistry"] = {
display = "[[analytical]] [[chemistry]]",
topical_categories = true,
}
labels["anarchism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["anatomy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Ancient Greece"] = {
aliases = {"ancient Greece"},
Wiktionary = true,
topical_categories = true,
}
labels["Ancient Rome"] = {
aliases = {"ancient Rome"},
Wiktionary = true,
topical_categories = true,
}
labels["Anglicanism"] = {
aliases = {"Anglican", "Anglicanist", "Anglican Church"},
Wiktionary = true,
topical_categories = true,
}
labels["animation"] = {
Wiktionary = true,
topical_categories = true,
}
labels["anime"] = {
Wiktionary = true,
topical_categories = "Japanese fiction",
}
labels["anthropology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Arabian god"] = {
display = "[[Arabian]] [[mythology]]",
topical_categories = "Arabian deities",
}
labels["arachnology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["archaeological culture"] = {
aliases = {"archeological culture", "archaeological cultures", "archeological cultures"},
display = "[[archaeology]]",
topical_categories = "Archaeological cultures",
}
labels["archaeology"] = {
aliases = {"archeology"},
Wiktionary = true,
topical_categories = true,
}
labels["archery"] = {
Wiktionary = true,
topical_categories = true,
}
labels["architectural element"] = {
aliases = {"architectural elements"},
display = "[[architecture]]",
topical_categories = "Architectural elements",
}
labels["architecture"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Argentine politics"] = {
aliases = {"Argentina politics", "Argentinian politics"},
Wikipedia = "Politics of Argentina",
topical_categories = true,
}
labels["arithmetic"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Armenian mythology"] = {
display = "[[Armenian]] [[mythology]]",
topical_categories = true,
}
labels["art"] = {
aliases = {"arts"},
Wiktionary = "art#Noun",
topical_categories = true,
}
labels["Arthurian legend"] = {
aliases = {"Arthurian mythology"},
Wikipedia = "Matter_of_Britain#Arthurian_legend",
topical_categories = "Arthurian mythology",
}
labels["artificial intelligence"] = {
aliases = {"AI"},
Wiktionary = true,
topical_categories = true,
}
labels["artillery"] = {
display = "[[weaponry]]",
topical_categories = true,
}
labels["artistic work"] = {
display = "[[art#Noun|art]]",
topical_categories = "Artistic works",
}
labels["asterism"] = {
display = "[[uranography]]",
topical_categories = "Asterisms",
}
labels["asteroid"] = {
display = "[[astronomy]]",
topical_categories = {"Asteroids", "Astronomy"}
}
labels["astrology"] = {
aliases = {"horoscope", "zodiac"},
Wiktionary = true,
topical_categories = true,
}
labels["astronomy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["astrophysics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Asturian mythology"] = {
display = "[[Asturian]] [[mythology]]",
topical_categories = true,
}
labels["athletics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Australian Aboriginal mythology"] = {
Wikipedia = true,
topical_categories = true,
}
labels["Australian politics"] = {
Wikipedia = "Politics of Australia",
topical_categories = true,
}
labels["Australian rules football"] = {
aliases = {"Australian Rules football"},
Wiktionary = true,
topical_categories = true,
}
labels["autism"] = {
Wiktionary = true,
Wikipedia = true,
topical_categories = true,
}
labels["automotive"] = {
aliases = {"automotives"},
Wiktionary = true,
topical_categories = true,
}
labels["automotive parts"] = {
aliases = {"automotive part", "auto part", "auto parts"},
display = "[[automotive]]",
topical_categories = true,
}
labels["aviation"] = {
aliases = {"air transport"},
Wiktionary = true,
topical_categories = true,
}
labels["backgammon"] = {
Wiktionary = true,
topical_categories = true,
}
labels["bacteria"] = {
display = "[[bacteriology]]",
topical_categories = true,
}
labels["bacteriology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["badminton"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Baháʼí Faith"] = {
aliases = {"Baháʼí", "Bahaʼi", "Bahá'í", "Baha'i", "Bahai", "Bahaʼi Faith", "Bahá'í Faith", "Baha'i Faith", "Bahai Faith"},
Wiktionary = true,
topical_categories = true,
}
labels["baking"] = {
Wiktionary = "baking#Noun",
topical_categories = true,
}
labels["ball games"] = {
aliases = {"ball sports"},
display = "[[ball game]]s",
topical_categories = true,
}
labels["ballet"] = {
Wiktionary = true,
topical_categories = true,
}
labels["ballistics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Bangladeshi politics"] = {
Wikipedia = "Politics of Bangladesh",
topical_categories = true,
}
labels["banking"] = {
Wiktionary = "banking#Noun",
topical_categories = true,
}
labels["baseball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["basketball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["BDSM"] = {
Wiktionary = true,
topical_categories = true,
}
labels["beekeeping"] = {
aliases = {"melittology", "apiology", "apidology"}, -- could potentially be split out
Wiktionary = true,
topical_categories = true,
}
labels["beer"] = {
Wiktionary = true,
topical_categories = true,
}
labels["betting"] = {
aliases = {"bet", "bets"},
display = "[[gambling#Noun|gambling]]",
topical_categories = true,
}
labels["biblical"] = {
aliases = {"Bible", "bible", "Biblical"},
Wiktionary = "Bible",
topical_categories = "Bible",
}
labels["biblical character"] = {
aliases = {"Biblical character", "biblical figure", "Biblical figure"},
display = "[[Bible|biblical]]",
topical_categories = "Biblical characters",
}
labels["bibliography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["bicycle parts"] = {
aliases = {"bicycle part"},
display = "[[w:List of bicycle parts|cycling]]",
topical_categories = true,
}
labels["billiards"] = {
aliases = {"cue sports"},
Wiktionary = true,
topical_categories = true,
}
labels["bingo"] = {
Wiktionary = true,
topical_categories = true,
}
labels["biochemistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["biology"] = {
aliases = {"biological"},
Wiktionary = true,
topical_categories = true,
}
labels["biotechnology"] = {
aliases = {"biotechnological"},
Wiktionary = true,
topical_categories = true,
}
labels["birdwatching"] = {
aliases = {"birding"},
Wiktionary = "birdwatching#Noun",
topical_categories = true,
}
labels["blacksmithing"] = {
aliases = {"blacksmith"},
Wiktionary = true,
topical_categories = true,
}
labels["blogging"] = {
aliases = {"blog"},
Wiktionary = "blogging#Noun",
topical_categories = "Internet",
}
labels["board games"] = {
aliases = {"board game"},
display = "[[board game]]s",
topical_categories = true,
}
labels["board sports"] = {
Wiktionary = "boardsport",
topical_categories = true,
}
labels["bodybuilding"] = {
Wiktionary = "bodybuilding#Noun",
topical_categories = true,
}
labels["book of the Bible"] = {
aliases = {"book of the bible", "books of the Bible", "books of the bible", "Biblical book", "biblical book"},
display = "[[Bible|biblical]]",
topical_categories = "Books of the Bible",
}
labels["bookbinding"] = {
Wiktionary = true,
topical_categories = true,
}
labels["botany"] = {
Wiktionary = true,
topical_categories = true,
}
labels["bowling"] = {
Wiktionary = "bowling#Noun",
topical_categories = true,
}
labels["bowls"] = {
aliases = {"lawn bowls", "crown green bowls"},
Wiktionary = true,
topical_categories = "Bowls (game)",
}
labels["boxing"] = {
Wiktionary = "boxing#Noun",
topical_categories = true,
}
labels["brass instruments"] = {
aliases = {"brass instrument"},
display = "[[music]]",
topical_categories = true,
}
labels["Brazilian politics"] = {
Wikipedia = "Politics of Brazil",
topical_categories = true,
}
labels["brewing"] = {
Wiktionary = "brewing#Noun",
topical_categories = true,
}
labels["bridge"] = {
Wiktionary = "bridge#English:_game",
topical_categories = true,
}
labels["broadcasting"] = {
Wiktionary = "broadcasting#Noun",
topical_categories = true,
}
labels["bryology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Buddhism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Buddhist deity"] = {
aliases = {"Buddhist god", "Buddhist goddess"},
display = "[[Buddhism]]",
topical_categories = "Buddhist deities",
}
labels["Bulgarian politics"] = {
Wikipedia = "Politics of Bulgaria",
topical_categories = true,
}
labels["bullfighting"] = {
aliases = {"bullfight"},
Wiktionary = true,
topical_categories = true,
}
labels["business"] = {
aliases = {"professional"},
Wiktionary = true,
topical_categories = true,
}
labels["Byzantine Empire"] = {
aliases = {"Byzantine"},
Wiktionary = true,
topical_categories = true,
}
labels["calculus"] = {
Wiktionary = true,
topical_categories = true,
}
labels["calligraphy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Calvinism"] = {
aliases = {"Calvinist", "Reformed Christianity", "Calvinist Church", "Reformed Church"},
Wikipedia = true,
topical_categories = true,
}
labels["Canadian football"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Canadian politics"] = {
Wikipedia = "Politics of Canada",
topical_categories = true,
}
labels["Candomblé"] = {
aliases = {"candomblé"},
Wiktionary = true,
topical_categories = true,
}
labels["canid"] = {
display = "[[zoology]]",
topical_categories = "Canids",
}
labels["canoeing"] = {
aliases = {"canoe"},
Wiktionary = "canoeing#Noun",
topical_categories = "Water sports",
}
labels["capitalism"] = {
aliases = {"capitalist"},
Wiktionary = true,
topical_categories = true,
}
labels["carbohydrate"] = {
aliases = {"carbohydrates"},
display = "[[biochemistry]]",
topical_categories = "Carbohydrates",
}
labels["carboxylic acid"] = {
aliases = {"carboxylic acids"},
display = "[[organic chemistry]]",
topical_categories = "Carboxylic acids",
}
labels["card games"] = {
aliases = {"cards", "card game", "playing card"},
display = "[[card game]]s",
topical_categories = true,
}
labels["cardiology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["carpentry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cartography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cartomancy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["castells"] = {
Wiktionary = true,
topical_categories = true,
}
labels["category theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Catholicism"] = {
aliases = {"catholicism", "Catholic", "catholic"},
Wiktionary = true,
topical_categories = true,
}
labels["caving"] = {
Wiktionary = "caving#Noun",
topical_categories = true,
}
labels["cellular automata"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Celtic mythology"] = {
display = "[[Celtic]] [[mythology]]",
topical_categories = true,
}
labels["ceramics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cheerleading"] = {
Wiktionary = "cheerleading#Noun",
topical_categories = true,
}
labels["chemical element"] = {
display = "[[chemistry]]",
topical_categories = "Chemical elements",
}
labels["chemical element symbol"] = {
-- Compare "systematic chemical element symbol" and "obsolete chemical element symbol".
display = "[[chemistry]]",
plain_categories = "Chemical element symbols",
}
labels["chemical engineering"] = {
Wiktionary = true,
topical_categories = true,
}
labels["chemistry"] = {
aliases = {"chemical"},
Wiktionary = true,
topical_categories = true,
}
labels["chess"] = {
Wiktionary = true,
topical_categories = true,
}
labels["children's games"] = {
aliases = {"children's game"},
display = "[[children|children's]] [[game]]s",
topical_categories = true,
}
labels["Chilean politics"] = {
Wikipedia = "Politics of Chile",
topical_categories = true,
}
labels["Chinese astronomy"] = {
display = "[[Chinese]] [[astronomy]]",
topical_categories = true,
}
labels["Chinese calligraphy"] = {
display = "[[Chinese]] [[calligraphy]]",
topical_categories = "Calligraphy",
}
labels["Chinese constellation"] = {
display = "[[Chinese]] [[astronomy]]",
topical_categories = "Constellations",
}
labels["Chinese folk religion"] = {
display = "[[Chinese]] [[folk religion]]",
topical_categories = "Religion",
}
labels["Chinese linguistics"] = {
display = "[[Chinese]] [[linguistics]]",
topical_categories = "Linguistics",
}
labels["Chinese mythology"] = {
display = "[[Chinese]] [[mythology]]",
topical_categories = true,
}
labels["Chinese philosophy"] = {
display = "[[Chinese]] [[philosophy]]",
topical_categories = true,
}
labels["Chinese phonetics"] = {
display = "[[Chinese]] [[phonetics]]",
topical_categories = true,
}
labels["Chinese religion"] = {
display = "[[Chinese]] [[religion]]",
topical_categories = "Religion",
}
labels["Chinese star"] = {
display = "[[Chinese]] [[astronomy]]",
topical_categories = "Stars",
}
labels["Christianity"] = {
aliases = {"christianity", "Christian", "christian"},
Wiktionary = true,
topical_categories = true,
}
labels["Church of England"] = {
aliases = {"C of E", "CofE"},
Wikipedia = true,
topical_categories = true,
}
labels["Church of the East"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cinematography"] = {
aliases = {"filmology"},
Wiktionary = true,
topical_categories = true,
}
labels["cladistics"] = {
Wiktionary = true,
topical_categories = "Taxonomy",
}
labels["classical mechanics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["classical studies"] = {
Wiktionary = true,
topical_categories = true,
}
labels["climate change"] = {
Wiktionary = true,
topical_categories = true,
}
labels["climatology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["climbing"] = {
aliases = {"rock climbing"},
Wiktionary = "climbing#Noun",
topical_categories = true,
}
labels["clinical psychology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["clothing"] = {
Wiktionary = "clothing#Noun",
topical_categories = true,
}
labels["cloud computing"] = {
Wiktionary = true,
topical_categories = "Computing",
}
labels["cockfighting"] = {
aliases = {"cockfight"},
Wiktionary = true,
topical_categories = true,
}
labels["codicology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["coenzyme"] = {
aliases = {"coenzymes"},
display = "[[biochemistry]]",
topical_categories = "Coenzymes",
}
labels["coins"] = { -- Do not merge with "numismatics", as the category is different.
aliases = {"coin"},
display = "[[numismatics]]",
topical_categories = true,
}
labels["collectible card games"] = {
aliases = {"trading card games", "collectible cards", "trading cards"},
Wikipedia = true,
topical_categories = true,
}
labels["combinatorics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["comedy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["comics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["commerce"] = {
Wiktionary = true,
topical_categories = true,
}
labels["commercial law"] = {
display = "[[commercial#Adjective|commercial]] [[law]]",
topical_categories = true,
}
labels["communication"] = {
aliases = {"communications"},
Wiktionary = true,
topical_categories = true,
}
labels["communism"] = {
aliases = {"Communism", "communist"},
Wiktionary = true,
topical_categories = true,
}
labels["compilation"] = {
aliases = {"compiler"},
display = "[[software]] [[compilation]]",
topical_categories = true,
}
labels["complex analysis"] = {
Wiktionary = true,
topical_categories = true,
}
labels["computational linguistics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["computer chess"] = {
Wiktionary = true,
topical_categories = true,
}
labels["computer games"] = {
aliases = {"computer game", "computer gaming"},
display = "[[computer game]]s",
topical_categories = "Video games",
}
labels["computer graphics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["computer hardware"] = {
display = "[[computer]] [[hardware]]",
topical_categories = true,
}
labels["computer languages"] = {
aliases = {"computer language", "programming language", "programming languages"},
display = "[[computer language]]s",
topical_categories = true,
}
labels["computer science"] = {
aliases = {"comp sci", "CompSci", "compsci"},
Wiktionary = true,
topical_categories = true,
}
labels["computer security"] = {
Wiktionary = true,
topical_categories = true,
}
labels["computing"] = {
aliases = {"computer", "computers"},
Wiktionary = "computing#Noun",
topical_categories = true,
}
labels["computing theory"] = {
aliases = {"comptheory", "computability theory"},
display = "[[computing#Noun|computing]] [[theory]]",
topical_categories = "Theory of computing",
}
labels["conchology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Confucianism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["conlanging"] = {
aliases = {"conlang", "conlanger", "constructed languages", "constructed language"},
Wiktionary = true,
topical_categories = true,
}
labels["conservatism"] = {
aliases = {"conservative"},
Wiktionary = true,
topical_categories = true,
}
labels["conspiracy theories"] = {
aliases = {"conspiracy theory", "conspiracy"},
Wiktionary = "conspiracy theory#Noun",
topical_categories = true,
}
labels["constellation"] = {
display = "[[astronomy]]",
topical_categories = "Constellations",
}
labels["construction"] = {
Wiktionary = true,
topical_categories = true,
}
labels["control theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cooking"] = {
aliases = {"culinary", "cuisine", "cookery", "gastronomy"},
Wiktionary = "cooking#Noun",
topical_categories = true,
}
labels["cookware"] = {
aliases = {"bakeware"},
display = "[[cooking#Noun|cooking]]",
topical_categories = "Cookware and bakeware",
}
labels["Coptic Orthodoxy"] = {
aliases = {"Coptic Orthodox", "Coptic Orthodox Church"},
Wikipedia = true,
topical_categories = true,
}
labels["copyright"] = {
aliases = {"copyright law", "intellectual property", "intellectual property law", "IP law"},
display = "[[copyright]] [[law]]",
topical_categories = true,
}
labels["copyright license"] = {
aliases = {"copyright licenses", "license", "copyright licence", "copyright licences", "licence"},
display = "[[w:Copyright license|copyright law]]",
Wikipedia = true,
topical_categories = "Copyright licenses",
}
labels["cosmetics"] = {
aliases = {"cosmetology"},
Wiktionary = true,
topical_categories = true,
}
labels["cosmology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["creationism"] = {
aliases = {"baraminology"},
Wiktionary = "creationism#English",
topical_categories = true,
}
labels["cribbage"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cricket"] = {
Wiktionary = true,
topical_categories = true,
}
labels["crime"] = {
aliases = {"criminal"},
Wiktionary = true,
topical_categories = true,
}
labels["criminal law"] = {
Wiktionary = true,
topical_categories = true,
}
labels["criminology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["crochet"] = {
aliases = {"crocheting"},
Wiktionary = true,
topical_categories = true,
}
labels["croquet"] = {
Wiktionary = true,
topical_categories = true,
}
labels["crosswording"] = {
aliases = {"crosswords", "cruciverbalism", "cryptic crosswords", "crossword puzzles"},
Wiktionary = true,
topical_categories = true,
}
labels["cryptocurrencies"] = {
aliases = {"cryptocurrency", "crypto"},
Wiktionary = "cryptocurrency",
topical_categories = "Cryptocurrency",
}
labels["cryptography"] = {
aliases = {"cryptographic"},
Wiktionary = true,
topical_categories = true,
}
labels["cryptozoology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["crystallography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["cultural anthropology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["curling"] = {
Wiktionary = true,
topical_categories = true,
}
labels["currencies"] = { -- Do not merge with "numismatics", as the category is different.
aliases = {"currency"},
display = "[[numismatics]]",
topical_categories = true,
}
labels["cybernetics"] = {
aliases = {"cybernetic"},
Wiktionary = true,
topical_categories = true,
}
labels["cybersecurity"] = {
Wiktionary = true,
topical_categories = "Networking",
}
labels["cycle racing"] = {
aliases = {"cycle sport"},
Wikipedia = "cycle sport",
topical_categories = true,
}
labels["cycling"] = {
aliases = {"bicycling", "bicycle", "bike"},
Wiktionary = "cycling#Noun",
topical_categories = true,
}
labels["cytology"] = {
aliases = {"cell biology", "cellular biology"},
Wiktionary = true,
topical_categories = true,
}
labels["dance"] = {
aliases = {"dancing"},
Wiktionary = "dance#Noun",
topical_categories = true,
}
labels["dances"] = {
display = "[[dance#Noun|dance]]",
topical_categories = true,
}
labels["darts"] = {
Wiktionary = true,
topical_categories = true,
}
labels["data management"] = {
Wiktionary = true,
topical_categories = true,
}
labels["data modeling"] = {
Wiktionary = true,
topical_categories = true,
}
labels["data structures"] = {
aliases = {"data structure"},
display = "[[computer science]]",
topical_categories = true,
}
labels["databases"] = {
aliases = {"database"},
display = "[[database]]s",
topical_categories = true,
}
labels["decision theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["deltiology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["demography"] = {
aliases = {"demographics"},
Wiktionary = true,
topical_categories = true,
}
labels["demonym"] = {
aliases = {"demonyms"},
Wiktionary = true,
topical_categories = "Demonyms",
}
labels["demoscene"] = {
topical_categories = true,
}
labels["dentistry"] = {
aliases = {"dentist"},
Wiktionary = true,
topical_categories = true,
}
labels["dermatology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["design"] = {
Wiktionary = "design#Noun",
topical_categories = true,
}
labels["developmental psychology"] = {
Wiktionary = true,
topical_categories = "Psychology",
}
labels["dice games"] = {
aliases = {"dice"},
display = "[[dice game]]s",
topical_categories = true,
}
labels["dictation"] = {
Wiktionary = true,
topical_categories = true,
}
labels["differential geometry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["diplomacy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["disc golf"] = {
Wiktionary = true,
topical_categories = true,
}
labels["disease"] = {
aliases = {"diseases"},
display = "[[pathology]]",
topical_categories = "Diseases",
}
labels["divination"] = {
Wiktionary = true,
topical_categories = true,
}
labels["diving"] = {
Wiktionary = "diving#Noun",
topical_categories = true,
}
labels["dominoes"] = {
Wiktionary = true,
topical_categories = true,
}
labels["dou dizhu"] = {
Wikipedia = true,
topical_categories = true,
}
labels["drama"] = {
Wiktionary = true,
topical_categories = true,
}
labels["dressage"] = {
Wiktionary = true,
topical_categories = true,
}
labels["E number"] = {
display = "[[food]] [[manufacture]]",
plain_categories = "European food additive numbers",
}
labels["early Christianity"] = {
aliases = {"early christianity", "Early Christianity", "early Church", "early church", "Early Church", "the early Church", "the early church", "the Early Church"},
Wikipedia = true,
topical_categories = true,
}
labels["earth science"] = {
Wiktionary = true,
topical_categories = "Earth sciences",
}
labels["Eastern Catholicism"] = {
aliases = {"Eastern Catholic"},
Wikipedia = true,
topical_categories = true,
}
labels["Eastern Christianity"] = {
aliases = {"Eastern christianity", "Eastern Christian", "Eastern christian", "Eastern Church", "Eastern church"},
Wikipedia = true,
topical_categories = true,
}
labels["Eastern Orthodoxy"] = {
aliases = {"Eastern Orthodox", "Eastern Orthodox Church"},
Wikipedia = true,
topical_categories = true,
}
labels["eating disorders"] = {
aliases = {"eating disorder"},
display = "[[eating disorder]]s",
topical_categories = true,
}
labels["ecology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["economics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["education"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Egyptian god"] = {
aliases = {"Egyptian goddess", "Egyptian deity"},
display = "[[Egyptian]] [[mythology]]",
topical_categories = "Egyptian deities",
}
labels["Egyptian mythology"] = {
display = "[[Egyptian]] [[mythology]]",
topical_categories = true,
}
labels["Egyptology"] = {
Wiktionary = true,
aliases = {"Ancient Egypt"},
topical_categories = "Ancient Egypt",
}
labels["electrencephalography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["electrical engineering"] = {
Wiktionary = true,
topical_categories = true,
}
labels["electricity"] = {
aliases = {"electrical"},
Wiktionary = true,
topical_categories = true,
}
labels["electrochemistry"] = {
aliases = {"electrochemical"},
Wiktionary = true,
topical_categories = true,
}
labels["electrodynamics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["electromagnetism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["electronics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["embryology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["emergency medicine"] = {
Wiktionary = true,
topical_categories = true,
}
labels["emergency services"] = {
Wiktionary = true,
topical_categories = true,
}
labels["endocrinology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["engineering"] = {
Wiktionary = "engineering#Noun",
topical_categories = true,
}
labels["enterprise engineering"] = {
Wiktionary = true,
topical_categories = true,
}
labels["entomology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["enzyme"] = {
aliases = {"enzymes"},
display = "[[biochemistry]]",
topical_categories = "Enzymes",
}
labels["epidemiology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["epigraphy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["epistemology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["equestrianism"] = {
aliases = {"equestrian", "horses", "horsemanship"},
Wiktionary = true,
topical_categories = true,
}
labels["espionage"] = {
Wiktionary = true,
topical_categories = true,
}
labels["ethics"] = {
aliases = {"ethical"},
Wiktionary = true,
topical_categories = true,
}
labels["ethnography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["ethology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["EU politics"] = {
aliases = {"European Union politics"},
Wikipedia = "Politics of the European Union",
topical_categories = true,
}
labels["European folklore"] = {
display = "[[European]] [[folklore]]",
topical_categories = true,
}
labels["European politics"] = {
Wikipedia = "Politics of Europe",
topical_categories = true,
}
labels["European Union"] = {
aliases = {"EU"},
Wiktionary = true,
topical_categories = true,
}
labels["Evangelicalism"] = {
aliases = {"Evangelical", "evangelical", "Evangelical Christianity", "Evangelical Christian", "Evangelical Protestantism", "Evangelical Protestant"},
Wikipedia = true,
topical_categories = true,
}
labels["evolutionary theory"] = {
aliases = {"evolutionary biology"},
Wiktionary = true,
topical_categories = true,
}
labels["exercise"] = {
Wiktionary = true,
topical_categories = true,
}
labels["eye color"] = {
aliases = {"eye colour"},
display = "[[eye]] [[color]]",
topical_categories = "Eye colors",
}
labels["eyewear"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fairy tale"] = { -- names of fairy tales
aliases = {"fairytale", "fairy-tale"},
Wiktionary = true,
topical_categories = true,
}
labels["fairy tales"] = { -- relating to fairy tales
aliases = {"fairytales", "fairy-tales"},
Wiktionary = true,
topical_categories = true,
}
labels["falconry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fantasy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["farriery"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fascism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fashion"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fatty acid"] = {
display = "[[organic chemistry]]",
topical_categories = "Fatty acids",
}
labels["felid"] = {
aliases = {"cat"},
display = "[[zoology]]",
topical_categories = "Felids",
}
labels["feminism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fencing"] = {
Wiktionary = "fencing#Noun",
topical_categories = true,
}
labels["feudalism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fiction"] = {
aliases = {"fictional"},
Wiktionary = true,
topical_categories = true,
}
labels["fictional character"] = {
display = "[[fiction]]",
topical_categories = "Fictional characters",
}
labels["field hockey"] = {
Wiktionary = true,
topical_categories = true,
}
labels["figure of speech"] = {
display = "[[rhetoric]]",
topical_categories = "Figures of speech",
}
labels["figure skating"] = {
Wiktionary = true,
topical_categories = true,
}
labels["file format"] = {
Wiktionary = true,
topical_categories = "File formats",
}
labels["film"] = {
Wiktionary = "film#Noun",
topical_categories = true,
}
labels["film genre"] = {
aliases = {"cinema"},
display = "[[film#Noun|film]]",
topical_categories = "Film genres",
}
labels["finance"] = {
Wiktionary = "finance#Noun",
topical_categories = true,
}
labels["Finnic mythology"] = {
aliases = {"Finnish mythology"},
display = "[[Finnic]] [[mythology]]",
topical_categories = true,
}
labels["firearms"] = {
aliases = {"firearm"},
display = "[[firearm]]s",
topical_categories = true,
}
labels["firefighting"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fish"] = {
display = "[[zoology]]",
topical_categories = true,
}
labels["fishing"] = {
aliases = {"angling"},
Wiktionary = "fishing#Noun",
topical_categories = true,
}
labels["flamenco"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fluid dynamics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["fluid mechanics"] = {
Wiktionary = true,
topical_categories = "Mechanics",
}
labels["folklore"] = {
Wiktionary = true,
topical_categories = true,
}
labels["footwear"] = {
Wiktionary = true,
topical_categories = true,
}
labels["forestry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Forteana"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Freemasonry"] = {
aliases = {"freemasonry"},
Wiktionary = true,
topical_categories = true,
}
labels["French politics"] = {
Wikipedia = "Politics of France",
topical_categories = true,
}
labels["functional analysis"] = {
Wiktionary = true,
topical_categories = true,
}
labels["functional group prefix"] = {
display = "[[organic chemistry]]",
topical_categories = "Functional group prefixes",
}
labels["functional group suffix"] = {
display = "[[organic chemistry]]",
topical_categories = "Functional group suffixes",
}
labels["functional programming"] = {
Wiktionary = true,
topical_categories = "Programming",
}
labels["furniture"] = {
Wiktionary = true,
topical_categories = true,
}
labels["furry fandom"] = {
aliases = {"furry", "furry community", "fursuit", "kemonā", "kemona", "kemono", "kemonomimi"},
display = "[[furry#Noun|furry]] [[fandom]]",
topical_categories = true,
}
labels["fuzzy logic"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Gaelic football"] = {
Wiktionary = true,
topical_categories = true,
}
labels["galaxy"] = {
display = "[[astronomy]]",
topical_categories = "Galaxies",
}
labels["gambling"] = {
Wiktionary = "gambling#Noun",
topical_categories = true,
}
labels["game theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["games"] = {
aliases = {"game"},
Wiktionary = "game#Noun",
topical_categories = true,
}
labels["gaming"] = {
Wiktionary = "gaming#Noun",
topical_categories = true,
}
labels["gender critical"] = {
aliases = {"gender-critical", "gender critical feminism", "gender-critical feminism", "GC", "GCF", "trans-exclusionary radical feminism", "TERF", "TERFism"},
Wiktionary = "gender-critical#Adjective",
Wikipedia = "Gender-critical feminism",
topical_categories = "Gender-critical feminism",
}
labels["genealogy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["general semantics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["genetic disorder"] = {
display = "[[medical]] [[genetics]]",
topical_categories = "Genetic disorders",
}
labels["genetics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["geography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["geological period"] = {
Wikipedia = true,
display ="[[geology]]",
topical_categories = "Geological periods",
}
labels["geology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["geometry"] = {
aliases = {"geometric", "geometrical"},
Wiktionary = true,
topical_categories = true,
}
labels["geomorphology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["geopolitics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["German politics"] = {
Wikipedia = "Politics of Germany",
topical_categories = true,
}
labels["Germanic god"] = {
aliases = {"Germanic goddess", "Germanic deity"},
display = "[[Germanic]] [[mythology]]",
topical_categories = "Germanic deities",
}
labels["Germanic paganism"] = {
aliases = {"Asatru", "Ásatrú", "Germanic neopaganism", "Germanic Paganism", "Heathenry", "heathenry", "Norse neopaganism", "Norse paganism"},
display = "[[Germanic#Adjective|Germanic]] [[paganism]]",
topical_categories = true,
}
labels["gerontology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["gladiatorial combat"] = {
Wikipedia = true,
topical_categories = true,
}
labels["glassblowing"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Gnosticism"] = {
aliases = {"gnosticism"},
Wiktionary = true,
topical_categories = true,
}
labels["go"] = {
aliases = {"Go", "game of go", "game of Go"},
display = "{{l|en|go|id=game}}",
topical_categories = true,
}
labels["golf"] = {
aliases = {"golfing"},
Wiktionary = true,
topical_categories = true,
}
labels["government"] = {
Wiktionary = true,
topical_categories = true,
}
labels["grammar"] = {
aliases = {"grammatical"},
Wiktionary = true,
topical_categories = true,
}
labels["grammatical case"] = {
display = "[[grammar]]",
topical_categories = "Grammatical cases",
}
labels["grammatical mood"] = {
display = "[[grammar]]",
topical_categories = "Grammatical moods",
}
labels["graph theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["graphic design"] = {
Wiktionary = true,
topical_categories = true,
}
labels["graphical user interface"] = {
aliases = {"GUI"},
Wiktionary = true,
topical_categories = true,
}
labels["Greek god"] = {
aliases = {"Greek goddess", "Greek deity"},
display = "[[Greek]] [[mythology]]",
topical_categories = "Greek deities",
}
labels["Greek mythology"] = {
display = "[[Greek]] [[mythology]]",
topical_categories = true,
}
labels["Greek Orthodoxy"] = {
aliases = {"Greek Orthodox", "Greek Orthodox Church"},
Wikipedia = true,
topical_categories = true,
}
labels["group theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["gun mechanisms"] = {
aliases = {"firearm mechanism", "firearm mechanisms", "gun mechanism"},
display = "[[firearm]]s",
topical_categories = true,
}
labels["gun sports"] = {
aliases = {"shooting sports"},
display = "[[gun]] [[sport]]s",
topical_categories = true,
}
labels["gymnastics"] = {
Wiktionary = true,
Wikipedia = true,
topical_categories = true,
}
labels["gynaecology"] = {
aliases = {"gynecology"},
Wiktionary = "gynecology",
topical_categories = true,
}
labels["hair color"] = {
aliases = {"hair colour"},
display = "[[hair]] [[color]]",
topical_categories = "Hair colors",
}
labels["hairdressing"] = {
Wiktionary = true,
topical_categories = true,
}
labels["hanafuda"] = {
Wikipedia = true,
topical_categories = true,
}
labels["hand games"] = {
aliases = {"hand game"},
display = "[[hand]] [[game]]s",
topical_categories = true,
}
labels["handball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Hawaiian mythology"] = {
display = "[[Hawaiian]] [[mythology]]",
topical_categories = true,
}
labels["headwear"] = {
display = "[[clothing#Noun|clothing]]",
topical_categories = true,
}
labels["healthcare"] = {
Wiktionary = true,
topical_categories = true,
}
labels["helminthology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["hematology"] = {
aliases = {"haematology"},
Wiktionary = true,
topical_categories = true,
}
labels["heraldic charge"] = {
aliases = {"heraldiccharge"},
display = "[[heraldry]]",
topical_categories = "Heraldic charges",
}
labels["heraldry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["herbalism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["herpetology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Hindu god"] = {
display = "[[Hinduism]]",
topical_categories = "Hindu deities",
}
labels["Hinduism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Hindutva"] = {
Wiktionary = true,
topical_categories = true,
}
labels["historical currencies"] = {
aliases = {"historical currency"},
display = "[[numismatics]]",
sense_categories = "historical",
topical_categories = "Historical currencies",
}
labels["historical linguistics"] = {
Wiktionary = true,
topical_categories = "Linguistics",
}
labels["historical period"] = {
aliases = {"historical periods"},
display = "[[history]]",
topical_categories = "Historical periods",
}
labels["historiography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["history"] = {
Wiktionary = true,
topical_categories = true,
}
labels["hockey"] = {
display = "[[field hockey]] or [[ice hockey]]",
topical_categories = {"Field hockey", "Ice hockey"},
}
labels["homeopathy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Homestuck"] = {
display = "''[[Homestuck]]''",
Wiktionary = true,
topical_categories = true,
}
labels["Hong Kong politics"] = {
aliases = {"HK politics"},
Wikipedia = "Politics of Hong Kong",
topical_categories = true,
}
labels["hormone"] = {
display = "[[biochemistry]]",
topical_categories = "Hormones",
}
labels["horse color"] = {
aliases = {"horse colour"},
display = "[[horse]] [[color]]",
topical_categories = "Horse colors",
}
labels["horse racing"] = {
Wiktionary = true,
topical_categories = true,
}
labels["horticulture"] = {
aliases = {"gardening"},
Wiktionary = true,
topical_categories = true,
}
labels["HTML"] = {
Wiktionary = "Hypertext Markup Language",
topical_categories = true,
}
labels["human resources"] = {
aliases = {"HR"},
Wiktionary = true,
topical_categories = true,
}
labels["humanities"] = {
Wiktionary = true,
topical_categories = true,
}
labels["hunting"] = {
Wiktionary = "hunting#Noun",
topical_categories = true,
}
labels["hurling"] = {
Wiktionary = "hurling#Noun",
topical_categories = true,
}
labels["hydroacoustics"] = {
Wikipedia = true,
topical_categories = true,
}
labels["hydrocarbon chain prefix"] = {
display = "[[organic chemistry]]",
topical_categories = "Hydrocarbon chain prefixes",
}
labels["hydrocarbon chain suffix"] = {
display = "[[organic chemistry]]",
topical_categories = "Hydrocarbon chain suffixes",
}
labels["hydrology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["ice hockey"] = {
Wiktionary = true,
topical_categories = true,
}
labels["ichthyology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["idol fandom"] = {
aliases = {"idol"},
display = "[[idol]] [[fandom]]",
topical_categories = true,
}
labels["immunochemistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["immunology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["import/export"] = {
aliases = {"import", "export"},
display = "[[import#Noun|import]]/[[export#Noun|export]]",
topical_categories = true,
}
labels["incoterm"] = {
display = "[[Incoterm]]",
topical_categories = "Incoterms",
}
labels["Indian politics"] = {
Wikipedia = "Politics of India",
topical_categories = true,
}
labels["Indo-European studies"] = {
aliases = {"indo-european studies"},
Wiktionary = true,
topical_categories = true,
}
labels["Indonesian politics"] = {
aliases = {"Indonesia politics"},
Wikipedia = "Politics of Indonesia",
topical_categories = true,
}
labels["information science"] = {
Wiktionary = true,
topical_categories = true,
}
labels["information technology"] = {
aliases = {"IT"},
Wiktionary = true,
topical_categories = "Computing",
}
labels["information theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["inheritance law"] = {
Wiktionary = true,
topical_categories = true,
}
labels["inorganic chemistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["inorganic compound"] = {
display = "[[inorganic chemistry]]",
topical_categories = "Inorganic compounds",
}
labels["insurance"] = {
Wiktionary = true,
topical_categories = true,
}
labels["international law"] = {
Wiktionary = true,
topical_categories = true,
}
labels["international relations"] = {
Wiktionary = true,
topical_categories = true,
}
labels["international standards"] = {
aliases = {"international standard", "ISO", "International Organization for Standardization", "International Organisation for Standardisation"},
Wikipedia = "International standard",
}
labels["Internet"] = {
aliases = {"internet", "online"},
Wiktionary = true,
topical_categories = true,
}
labels["Iranian mythology"] = {
display = "[[Iranian]] [[mythology]]",
topical_categories = true,
}
labels["Irish mythology"] = {
display = "[[Irish]] [[mythology]]",
topical_categories = true,
}
labels["Irish politics"] = {
Wikipedia = "Politics of the Republic of Ireland",
topical_categories = true,
}
labels["Islam"] = {
aliases = {"islam", "Islamic", "Muslim"},
Wikipedia = true,
topical_categories = true,
}
labels["Islamic finance"] = {
aliases = {"Islamic banking", "Muslim finance", "Muslim banking", "Sharia-compliant finance"},
Wikipedia = true,
topical_categories = true,
}
labels["Islamic law"] = {
aliases = {"Islamic legal", "Sharia"},
Wikipedia = true,
topical_categories = true,
}
labels["isotope"] = {
display = "[[physics]]",
topical_categories = "Isotopes",
}
labels["Jainism"] = {
Wiktionary = true,
Wikipedia = true,
topical_categories = true,
}
labels["Japanese fiction"] = {
-- aliases = {"anime", "manga", "anime and manga", "manga and anime"},
display = "[[Japanese#Adjective|Japanese]] [[fiction]]",
Wikipedia = true,
topical_categories = true,
}
labels["Japanese god"] = {
display = "[[Japanese#Adjective|Japanese]] [[mythology]]",
topical_categories = "Japanese deities",
}
labels["Japanese mythology"] = {
display = "[[Japanese#Adjective|Japanese]] [[mythology]]",
topical_categories = true,
}
labels["Japanese politics"] = {
Wikipedia = "Politics of Japan",
topical_categories = true,
}
labels["Japanese pornography"] = {
aliases = {"Japanese porn", "hentai", "adult anime", "erotic anime", "ero anime"},
display = "[[Japanese#Adjective|Japanese]] [[pornography]]",
Wikipedia = true,
topical_categories = true,
}
labels["Java programming language"] = {
aliases = {"JavaPL", "Java PL"},
Wikipedia = "Java (programming language)",
topical_categories = true,
}
labels["jazz"] = {
Wiktionary = "jazz#Noun",
topical_categories = true,
}
labels["jewelry"] = {
aliases = {"jewellery"},
Wiktionary = true,
topical_categories = true,
}
labels["Jewish law"] = {
aliases = {"Halacha", "Halachah", "Halakha", "Halakhah", "halacha", "halachah", "halakha", "halakhah", "Jewish Law", "jewish law"},
display = "[[Jewish]] [[law]]",
topical_categories = true,
}
labels["journalism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["journalism types"] = {
aliases = {"journalism type"},
display = "[[journalism]]",
topical_categories = true,
}
labels["Judaism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["judo"] = {
Wiktionary = true,
topical_categories = true,
}
labels["juggling"] = {
Wiktionary = "juggling#Noun",
topical_categories = true,
}
labels["karuta"] = {
Wiktionary = true,
topical_categories = true,
}
labels["kendo"] = {
Wiktionary = true,
topical_categories = true,
}
labels["knitting"] = {
Wiktionary = "knitting#Noun",
topical_categories = true,
}
labels["Korean mythology"] = {
display = "[[Korean#Adjective|Korean]] [[mythology]]",
topical_categories = true,
}
labels["labour"] = {
aliases = {"labor", "labour movement", "labor movement"},
Wiktionary = true,
topical_categories = true,
}
labels["labour law"] = {
aliases = {"labor law"},
Wiktionary = true,
topical_categories = "Law",
}
labels["lacrosse"] = {
Wiktionary = true,
topical_categories = true,
}
labels["landforms"] = {
display = "[[geography]]",
topical_categories = true,
}
labels["law"] = {
aliases = {"legal"},
Wiktionary = "law#English",
topical_categories = true,
}
labels["law enforcement"] = {
aliases = {"police", "policing"},
Wiktionary = true,
topical_categories = true,
}
labels["leatherworking"] = {
Wiktionary = true,
topical_categories = true,
}
labels["leftism"] = {
aliases = {"leftist"},
Wiktionary = true,
topical_categories = true,
}
labels["letterpress"] = {
aliases = {"metal type", "metal typesetting"},
display = "[[letterpress]] [[typography]]",
topical_categories = "Typography",
}
labels["lexicography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["LGBTQ"] = {
aliases = {"LGBT", "LGBT+", "LGBT*", "LGBTQ+", "LGBTQ*", "LGBTQIA", "LGBTQIA+", "LGBTQIA*", "queer"},
Wiktionary = true,
topical_categories = true,
}
labels["liberalism"] = {
aliases = {"liberal"},
Wiktionary = true,
topical_categories = true,
}
labels["library science"] = {
Wiktionary = true,
topical_categories = true,
}
labels["lichenology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["limnology"] = {
Wiktionary = true,
topical_categories = "Ecology",
}
labels["linear algebra"] = {
aliases = {"vector algebra"},
Wiktionary = true,
topical_categories = true,
}
labels["linguistic morphology"] = {
display = "[[linguistic]] [[morphology]]",
topical_categories = true,
}
labels["linguistics"] = {
aliases = {"linguistic", "philology"},
Wiktionary = true,
topical_categories = true,
}
labels["lipid"] = {
aliases = {"lipids"},
display = "[[biochemistry]]",
topical_categories = "Lipids",
}
labels["literature"] = {
Wiktionary = true,
topical_categories = true,
}
labels["locksmithing"] = {
Wiktionary = true,
display = "[[locksmithing]]",
topical_categories = true,
}
labels["logic"] = {
Wiktionary = true,
topical_categories = true,
}
labels["logical fallacy"] = {
aliases = {"fallacies"},
display = "[[rhetoric]]",
topical_categories = "Logical fallacies",
}
labels["logistics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["looksmaxxing"] = {
Wiktionary = "looksmaxxing#English: enhancement",
topical_categories = true,
}
labels["luge"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Lutheranism"] = {
aliases = {"Lutheran", "Lutheranist", "Lutheran Church"},
Wikipedia = true,
topical_categories = true,
}
labels["lutherie"] = {
Wiktionary = true,
topical_categories = true,
}
labels["machine learning"] = {
aliases = {"ML"},
Wiktionary = true,
topical_categories = true,
}
labels["machining"] = {
Wiktionary = "machining#Noun",
topical_categories = true,
}
labels["macroeconomics"] = {
Wiktionary = true,
topical_categories = "Economics",
}
labels["mahjong"] = {
Wiktionary = true,
topical_categories = true,
}
labels["malacology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Malaysian politics"] = {
aliases = {"Malaysia politics"},
Wikipedia = "Politics of Malaysia",
topical_categories = true,
}
labels["mammalogy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["management"] = {
Wiktionary = true,
topical_categories = true,
}
labels["manga"] = {
aliases = {"Japanese comics"},
Wiktionary = true,
topical_categories = "Japanese fiction",
}
labels["manhua"] = {
aliases = {"Chinese comics"},
Wiktionary = true,
topical_categories = "Chinese fiction",
}
labels["manhwa"] = {
aliases = {"Korean comics"},
Wiktionary = true,
topical_categories = "Korean fiction",
}
labels["Manichaeism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["manufacturing"] = {
Wiktionary = "manufacturing#Noun",
topical_categories = true,
}
labels["Maoism"] = {
aliases = {"Maoist"},
Wiktionary = true,
topical_categories = true,
}
labels["marching"] = {
Wiktionary = "marching#Noun",
topical_categories = true,
}
labels["marine biology"] = {
aliases = {"coral science"},
Wiktionary = true,
topical_categories = true,
}
labels["marketing"] = {
Wiktionary = "marketing#Noun",
topical_categories = true,
}
labels["martial arts"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Marxism"] = {
aliases = {"Marxist"},
Wiktionary = true,
topical_categories = true,
}
labels["masonry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["massage"] = {
Wiktionary = true,
topical_categories = true,
}
labels["materials science"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mathematical analysis"] = {
aliases = {"analysis"},
Wiktionary = true,
topical_categories = true,
}
labels["mathematics"] = {
aliases = {"math", "maths"},
Wiktionary = true,
topical_categories = true,
}
labels["measure theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mechanical engineering"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mechanics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["media"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mediaeval folklore"] = {
aliases = {"medieval folklore"},
display = "[[mediaeval]] [[folklore]]",
topical_categories = "European folklore",
}
labels["medical genetics"] = {
display = "[[medical]] [[genetics]]",
topical_categories = true,
}
labels["medical sign"] = {
aliases = {"medical symptoms", "symptom", "symptoms"},
display = "[[medicine]]",
topical_categories = "Medical signs and symptoms",
}
labels["medicine"] = {
aliases = {"medical"},
Wiktionary = true,
topical_categories = true,
}
labels["Meitei god"] = {
aliases = {"Meitei goddess", "Meitei deity"},
display = "[[Meitei]] [[mythology]]",
topical_categories = "Meitei deities",
}
labels["mental health"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Mesopotamian god"] = {
aliases = {"Mesopotamian goddess", "Mesopotamian deity",
"Mesopotamian diety" -- FIXME: existing typo
},
display = "[[Mesopotamian]] [[mythology]]",
topical_categories = "Mesopotamian deities",
}
labels["Mesopotamian mythology"] = {
display = "[[Mesopotamian]] [[mythology]]",
topical_categories = true,
}
labels["metadata"] = {
Wiktionary = true,
topical_categories = "Data management",
}
labels["metallurgy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["metalworking"] = {
Wiktionary = true,
topical_categories = true,
}
labels["metamaterial"] = {
display = "[[physics]]",
topical_categories = "Metamaterials",
}
labels["metaphysics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["meteorology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Methodism"] = {
aliases = {"Methodist", "methodism", "methodist"},
Wiktionary = true,
topical_categories = true,
}
labels["metrology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Mexican politics"] = {
aliases = {"Mexico politics"},
Wikipedia = "Politics of Mexico",
topical_categories = true,
}
labels["microbiology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["microelectronics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["micronationalism"] = {
aliases = {"micronation", "micronations"},
Wiktionary = true,
topical_categories = true,
}
labels["microscopy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["military"] = {
aliases = {"army"},
Wiktionary = true,
topical_categories = true,
}
labels["military ranks"] = {
aliases = {"military rank"},
display = "[[military]]",
topical_categories = true,
}
labels["military unit"] = {
display = "[[military]]",
topical_categories = "Military units",
}
labels["milling"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Minecraft"] = {
display = "''[[Minecraft]]''",
topical_categories = true,
}
labels["mineral"] = {
display = "[[mineralogy]]",
topical_categories = "Minerals",
}
labels["mineralogy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mining"] = {
Wiktionary = "mining#Noun",
topical_categories = true,
}
labels["mobile phones"] = {
aliases = {"cell phone", "cell phones", "mobile phone", "mobile telephony", "smartphone", "smartphones", "mobile"},
display = "[[mobile telephone|mobile telephony]]",
topical_categories = true,
}
labels["molecular biology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["monarchy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["money"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Mormonism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["motor racing"] = {
-- There are other types of racing, but 99% of the time "racing" on its own refers to motorsports.
aliases = {"motor sport", "motorsport", "motorsports", "racing"},
Wiktionary = true,
topical_categories = true,
}
labels["motorcycling"] = {
aliases = {"motorcycle", "motorcycles", "motorbike"},
Wiktionary = "motorcycling#Noun",
topical_categories = "Motorcycles",
}
labels["multiplicity"] = {
aliases = {"plurality", "polypsychism", "dissociative identity disorder", "DID"},
display = "{{l|en|multiplicity|id=multiple personalities}}",
topical_categories = "Multiplicity (psychology)",
}
labels["muscle"] = {
aliases = {"muscles"},
display = "[[anatomy]]",
topical_categories = "Muscles",
}
labels["mushroom"] = {
aliases = {"mushrooms"},
display = "[[mycology]]",
topical_categories = "Mushrooms",
}
labels["music"] = {
aliases = {"musical"},
Wiktionary = true,
topical_categories = true,
}
labels["music genre"] = {
display = "[[music]]",
topical_categories = "Musical genres",
}
labels["music industry"] = {
Wikipedia = true,
topical_categories = true,
}
labels["musical instruments"] = {
aliases = {"musical instrument"},
display = "[[music]]",
topical_categories = true,
}
labels["musician"] = {
display = "[[music]]",
topical_categories = "Musicians",
}
labels["musicology"] = {
Wiktionary = true,
topical_categories = "Music",
}
labels["mycology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mysticism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["mythological creature"] = {
aliases = {"mythological creatures"},
display = "[[mythology]]",
topical_categories = "Mythological creatures",
}
labels["mythology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["nanotechnology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["narratology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["nautical"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Navajo mythology"] = {
display = "[[Navajo]] [[mythology]]",
topical_categories = true,
}
labels["navigation"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Nazism"] = { -- see also Neo-Nazism
aliases = {"nazism", "Nazi", "nazi", "Nazis", "nazis"},
Wikipedia = true,
topical_categories = true,
}
labels["nematology"] = {
Wiktionary = true,
topical_categories = "Zoology",
}
labels["neo-Nazism"] = { -- Often used to indicate Nazi-used jargon; compare "white supremacist ideology"
aliases = {"Neo-Nazism", "Neo-nazism", "neo-nazism", "Neo-Nazi", "Neo-nazi", "neo-Nazi", "neo-nazi", "Neo-Nazis", "Neo-nazis", "neo-Nazis", "neo-nazis", "NeoNazism", "Neonazism", "neoNazism", "neonazism", "NeoNazi", "Neonazi", "neoNazi", "neonazi", "NeoNazis", "Neonazis", "neoNazis", "neonazis"},
Wikipedia = true,
topical_categories = true,
}
labels["netball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["networking"] = {
Wiktionary = "networking#Noun",
topical_categories = true,
}
labels["neuroanatomy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["neurology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["neuroscience"] = {
Wiktionary = true,
topical_categories = true,
}
labels["neurosurgery"] = {
Wiktionary = true,
topical_categories = true,
}
labels["neurotoxin"] = {
display = "[[neurotoxicology]]",
topical_categories = "Neurotoxins",
}
labels["neurotransmitter"] = {
display = "[[biochemistry]]",
topical_categories = "Neurotransmitters",
}
labels["New Zealand politics"] = {
Wikipedia = "Politics of New Zealand",
topical_categories = true,
}
labels["newspapers"] = {
display = "[[newspaper]]s",
topical_categories = true,
}
labels["Norse god"] = {
aliases = {"Norse goddess", "Norse deity"},
display = "[[Norse]] [[mythology]]",
topical_categories = "Norse deities",
}
labels["Norse mythology"] = {
display = "[[Norse]] [[mythology]]",
topical_categories = true,
}
labels["nuclear energy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["nuclear physics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["number theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["numismatics"] = {
Wiktionary = true,
topical_categories = "Currency",
}
labels["nutrition"] = {
Wiktionary = true,
topical_categories = true,
}
labels["object-oriented programming"] = {
aliases = {"object-oriented", "OOP"},
Wiktionary = true,
topical_categories = true,
}
labels["obsolete chemical element symbol"] = {
display = "[[chemistry]], [[obsolete]]",
plain_categories = "Obsolete chemical element symbols",
}
labels["obstetrics"] = {
aliases = {"obstetric"},
Wiktionary = true,
topical_categories = true,
}
labels["occult"] = {
Wiktionary = true,
topical_categories = true,
}
labels["oceanography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Odinani"] = {
aliases = {"Odinala", "Omenala", "Odinana", "Omenana", "Igbo religion"},
Wikipedia = true,
topical_categories = true,
}
labels["oil industry"] = {
aliases = {"oil", "oil drilling", "petroleum industry", "petroleum"},
Wikipedia = true,
topical_categories = true,
}
labels["oncology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["online gaming"] = {
aliases = {"online games", "MMO", "MMORPG"},
display = "[[online]] [[gaming#Noun|gaming]]",
topical_categories = "Video games",
}
labels["onomastics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["opera"] = {
Wiktionary = true,
topical_categories = true,
}
labels["operating systems"] = {
display = "[[operating system]]s",
topical_categories = "Software",
}
labels["ophthalmology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["optics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["organic chemistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["organic compound"] = {
display = "[[organic chemistry]]",
topical_categories = "Organic compounds",
}
labels["Oriental Orthodoxy"] = {
aliases = {"Oriental Orthodox", "Oriental Orthodox Church"},
Wikipedia = true,
topical_categories = true,
}
labels["ornithology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["orthodontics"] = {
Wiktionary = true,
topical_categories = "Dentistry",
}
labels["orthography"] = {
Wiktionary = true,
topical_categories = true,
}
labels["paganism"] = {
aliases = {"pagan", "neopagan", "neopaganism", "neo-pagan", "neo-paganism"},
Wiktionary = true,
topical_categories = true,
}
labels["pain"] = {
display = "[[medicine]]",
topical_categories = true,
}
labels["paintball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["painting"] = {
Wiktionary = "painting#Noun",
topical_categories = true,
}
labels["Pakistani politics"] = {
Wikipedia = "Politics of Pakistan",
topical_categories = true,
}
labels["palaeography"] = {
aliases = {"paleography"},
Wiktionary = true,
topical_categories = true,
}
labels["paleontology"] = {
aliases = {"palaeontology"},
Wiktionary = true,
topical_categories = true,
}
labels["Palestinian politics"] = {
aliases = {"Palestine politics"},
Wikipedia = "Politics of the Palestinian National Authority",
topical_categories = true,
}
labels["palmistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["palynology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["papermaking"] = {
Wiktionary = true,
topical_categories = true,
}
labels["paraphilia"] = {
aliases = {"paraphilias", "paraphilic", "fetish", "fetishes", "fetishism", "fetishistic", "fetishization", "fetishisation"},
Wiktionary = "paraphilia#Noun",
topical_categories = "Paraphilias",
}
labels["parapsychology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["parasitology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["part of speech"] = {
aliases = {"PoS"},
display = "[[grammar]]",
topical_categories = "Parts of speech",
}
labels["particle"] = {
aliases = {"subatomic particle", "subatomic particles"},
display = "[[particle physics]]",
topical_categories = "Subatomic particles",
}
labels["particle physics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pasteurization"] = {
aliases = {"pasteurisation"},
Wiktionary = true,
topical_categories = true,
}
labels["patent law"] = {
aliases = {"patents"},
display = "[[patent#Noun|patent]] [[law]]",
topical_categories = true,
}
labels["pathology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pensions"] = {
aliases = {"pension"},
display = "[[pension]]s",
topical_categories = true,
}
labels["percussion instruments"] = {
aliases = {"percussion instrument"},
display = "[[music]]",
topical_categories = true,
}
labels["perfumery"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Peruvian politics"] = {
Wikipedia = "Politics of Peru",
topical_categories = true,
}
labels["pesäpallo"] = {
aliases = {"pesapallo"},
Wiktionary = true,
topical_categories = true,
}
labels["petrochemistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["petrology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pharmaceutical drug"] = {
display = "[[pharmacology]]",
topical_categories = "Pharmaceutical drugs",
}
labels["pharmaceutical effect"] = {
display = "[[pharmacology]]",
topical_categories = "Pharmaceutical effects",
}
labels["pharmacology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pharmacy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pharyngology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["philately"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Philippine politics"] = {
aliases = {"Filipino politics"},
Wikipedia = "Politics of the Philippines",
topical_categories = true,
}
labels["Philmont Scout Ranch"] = {
aliases = {"Philmont"},
Wikipedia = true,
topical_categories = true,
}
labels["philosophy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["phonetics"] = {
aliases = {"phonetic"},
Wiktionary = true,
topical_categories = true,
}
labels["phonology"] = {
aliases = {"phonological"},
Wiktionary = true,
topical_categories = true,
}
labels["photography"] = {
aliases = {"photograph"},
Wiktionary = true,
topical_categories = true,
}
labels["phrenology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["phycology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["physical chemistry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["physical quantity"] = {
display = "[[physics]]",
topical_categories = "Physical quantities",
}
labels["physics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["physiology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["phytopathology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pinball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["planetology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["plant"] = {
aliases = {"plants"},
display = "[[botany]]",
topical_categories = "Plants",
}
labels["plant disease"] = {
aliases = {"plant diseases"},
display = "[[phytopathology]]",
topical_categories = "Plant diseases",
}
labels["plastic surgery"] = {
Wiktionary = true,
topical_categories = true,
}
labels["playground games"] = {
aliases = {"playground game"},
display = "[[playground]] [[game]]s",
topical_categories = true,
}
labels["poetry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["poison"] = {
display = "[[toxicology]]",
topical_categories = "Poisons",
}
labels["Pokémon"] = {
aliases = {"Pokemon"},
display = "''[[w:Pokémon|Pokémon]]''",
Wikipedia = true,
topical_categories = true,
}
labels["poker"] = {
Wiktionary = true,
topical_categories = true,
}
labels["poker slang"] = {
display = "[[poker]] [[slang]]",
topical_categories = "Poker",
}
labels["political science"] = {
Wiktionary = true,
topical_categories = true,
}
labels["political subdivision"] = {
display = "[[government]]",
topical_categories = "Political subdivisions",
}
labels["politics"] = {
aliases = {"political"},
Wiktionary = true,
topical_categories = true,
}
labels["pornography"] = {
aliases = {"porn", "porno", "adult video", "adult videos"},
Wiktionary = true,
topical_categories = true,
}
labels["Portuguese folklore"] = {
display = "[[Portuguese#Adjective|Portuguese]] [[folklore]]",
topical_categories = "European folklore",
}
labels["Portuguese politics"] = {
Wikipedia = "Politics of Portugal",
topical_categories = true,
}
labels["post"] = {
aliases = {"mail", "postal"},
display = "[[postal]]",
topical_categories = true,
}
labels["postal abbreviation"] = {
aliases = {"postal abbr", "postal abbrev"},
display = "[[postal]]",
topical_categories = "Postal abbreviations",
}
labels["potential theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pottery"] = {
Wiktionary = true,
topical_categories = "Ceramics",
}
labels["pragmatics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["printing"] = {
Wiktionary = "printing#Noun",
topical_categories = true,
}
labels["probability theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["professional wrestling"] = {
aliases = {"pro wrestling"},
Wiktionary = true,
topical_categories = true,
}
labels["programming"] = {
aliases = {"computer programming"},
Wiktionary = "programming#Noun",
topical_categories = true,
}
labels["property law"] = {
aliases = {"land law", "real estate law"},
Wiktionary = true,
topical_categories = true,
}
labels["prosody"] = {
Wiktionary = true,
topical_categories = true,
}
labels["protein"] = {
aliases = {"proteins"},
display = "[[biochemistry]]",
topical_categories = "Proteins",
}
labels["Protestantism"] = {
aliases = {"protestantism", "Protestant", "protestant"},
Wiktionary = true,
topical_categories = true,
}
labels["pseudoscience"] = {
Wiktionary = true,
topical_categories = true,
}
labels["psychiatry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["psychoanalysis"] = {
Wiktionary = true,
topical_categories = true,
}
labels["psychology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["psychotherapy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["publishing"] = {
Wiktionary = "publishing#Noun",
topical_categories = true,
}
labels["pulmonology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["pyrotechnics"] = {
Wiktionary = true,
aliases = { "firework", "fireworks" },
topical_categories = true,
}
labels["QAnon"] = {
aliases = {"Qanon"},
Wikipedia = true,
topical_categories = true,
}
labels["Quakerism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["quantum computing"] = {
Wiktionary = true,
topical_categories = true,
}
labels["quantum mechanics"] = {
aliases = {"quantum physics"},
Wiktionary = true,
topical_categories = true,
}
labels["Quimbanda"] = {
Wiktionary = true,
topical_categories = true,
}
labels["radiation"] = {
-- TODO: What kind of topic is "radiation"? Is it specific kinds of radiation? That would be a set-type category.
display = "[[physics]]",
topical_categories = true,
}
labels["radio"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Raëlism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rail transport"] = {
aliases = {"rail", "railroading", "railroads"},
Wiktionary = true,
topical_categories = "Rail transportation",
}
labels["Rastafari"] = {
aliases = {"Rasta", "rasta", "Rastafarian", "rastafarian", "Rastafarianism"},
Wiktionary = true,
topical_categories = true,
}
labels["real estate"] = {
Wiktionary = true,
topical_categories = true,
}
labels["real tennis"] = {
Wiktionary = true,
topical_categories = "Tennis",
}
labels["recreational mathematics"] = {
Wiktionary = true,
topical_categories = "Mathematics",
}
labels["Reddit"] = {
Wiktionary = true,
topical_categories = true,
}
labels["regular expressions"] = {
aliases = {"regex"},
display = "[[regular expression]]s",
topical_categories = true,
}
labels["relativity"] = {
Wiktionary = true,
topical_categories = true,
}
labels["religion"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rhetoric"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rhythmic gymnastics"] = {
Wiktionary = true,
Wikipedia = true,
topical_categories = true,
}
labels["road transport"] = {
aliases = {"roads"},
Wikipedia = true,
topical_categories = true,
}
labels["robotics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rock"] = {
aliases = {"rocks"},
display = "[[geology]]",
topical_categories = "Rocks",
}
labels["rock paper scissors"] = {
topical_categories = true,
}
labels["rocketry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["roleplaying games"] = {
aliases = {"role playing games", "role-playing games", "RPG", "RPGs"},
display = "[[roleplaying game]]s",
topical_categories = "Role-playing games",
}
labels["roller derby"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Roman Catholicism"] = {
aliases = {"Roman Catholic", "Roman Catholic Church"},
Wiktionary = true,
topical_categories = true,
}
labels["Roman Empire"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Roman god"] = {
aliases = {"Roman goddess", "Roman deity"},
display = "[[Roman]] [[mythology]]",
topical_categories = "Roman deities",
}
labels["Roman mythology"] = {
display = "[[Roman]] [[mythology]]",
topical_categories = true,
}
labels["Roman numerals"] = {
display = "[[Roman numeral]]s",
topical_categories = true,
}
labels["roofing"] = {
Wiktionary = "roofing#Noun",
topical_categories = true,
}
labels["rosiculture"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rowing"] = {
Wiktionary = "rowing#Noun",
topical_categories = true,
}
labels["Rubik's Cube"] = {
aliases = {"Rubik's cube", "Rubik's cubes", "Magic Cube", "magic cube"},
Wiktionary = "Rubik's cube",
topical_categories = true,
}
labels["rugby"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rugby league"] = {
Wiktionary = true,
topical_categories = true,
}
labels["rugby union"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Russian Orthodoxy"] = {
aliases = {"Russian Orthodox", "Russian Orthodox Church"},
Wikipedia = true,
topical_categories = true,
}
labels["Russian space program"] = {
aliases = {"Soviet space program", "Roscosmos"},
Wikipedia = true,
topical_categories = true,
}
labels["sailing"] = {
Wiktionary = "sailing#Noun",
topical_categories = true,
}
labels["schools"] = {
display = "[[education]]",
topical_categories = true,
}
labels["science fiction"] = {
aliases = {"scifi", "sci fi", "sci-fi"},
Wiktionary = true,
topical_categories = true,
}
labels["sciences"] = {
display = "[[sciences#English: all fields|sciences]]",
aliases = {"science", "scientific"},
Wiktionary = true,
topical_categories = true,
}
labels["Scientology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Scots law"] = {
-- Note: this is the usual term, not "Scottish law".
aliases = {"Scottish law", "Scotland law", "Scots Law", "Scottish Law", "Scotland Law"},
Wikipedia = true,
topical_categories = true,
}
labels["Scouting"] = {
aliases = {"scouting"},
display = "[[scouting]]",
topical_categories = true,
}
labels["Scrabble"] = {
display = "''[[Scrabble]]''",
Wikipedia = true,
topical_categories = true,
}
labels["scrapbooks"] = {
display = "[[scrapbook]]s",
topical_categories = true,
}
labels["sculpture"] = {
Wiktionary = true,
topical_categories = true,
}
labels["seduction community"] = {
aliases = {"pickup artist", "pickup artists", "pickup artistry", "pickup community"},
Wikipedia = true,
topical_categories = true,
}
labels["seismology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["self-harm"] = {
aliases = {"selfharm", "self harm", "self-harm community"},
Wiktionary = true,
topical_categories = true,
}
labels["semantics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["semiconductors"] = {
display = "[[semiconductor]]s",
topical_categories = true,
}
labels["semiotics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["SEO"] = {
Wiktionary = "search engine optimization",
topical_categories = {"Internet", "Marketing"},
}
labels["set theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sewing"] = {
Wiktionary = "sewing#Noun",
topical_categories = true,
}
labels["sex"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sex position"] = {
display = "[[sex]]",
topical_categories = "Sex positions",
}
labels["sexology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sexuality"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Shaivism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["shamanism"] = {
aliases = {"Shamanism"},
Wiktionary = true,
topical_categories = true,
}
labels["Shi'ism"] = {
aliases = {"Shia", "Shi'ite", "Shi'i"},
display = "[[Shia Islam]]",
topical_categories = true,
}
labels["Shinto"] = {
Wiktionary = true,
topical_categories = true,
}
labels["ship parts"] = {
display = "[[nautical]]",
topical_categories = "Ship parts",
}
labels["shipping"] = {
Wiktionary = "shipping#Noun",
topical_categories = true,
}
labels["shoemaking"] = {
Wiktionary = true,
topical_categories = true,
}
labels["shogi"] = {
Wiktionary = true,
topical_categories = true,
}
labels["signal processing"] = {
Wikipedia = true,
topical_categories = true,
}
labels["Sikhism"] = {
aliases = {"Sikh"},
Wiktionary = true,
topical_categories = true,
}
labels["Singaporean politics"] = {
Wikipedia = "Politics of Singapore",
topical_categories = true,
}
labels["singing"] = {
Wiktionary = "singing#Noun",
topical_categories = true,
}
labels["skateboarding"] = {
Wiktionary = "skateboarding#Noun",
topical_categories = true,
}
labels["skating"] = {
Wiktionary = "skating#Noun",
topical_categories = true,
}
labels["skeleton"] = {
display = "[[anatomy]]",
topical_categories = true,
}
labels["skiing"] = {
Wiktionary = "skiing#Noun",
topical_categories = true,
}
labels["skydiving"] = {
Wiktionary = "skydiving#Noun",
topical_categories = true,
}
labels["Slavic god"] = {
display = "[[Slavic]] [[mythology]]",
topical_categories = "Slavic deities",
}
labels["Slavic mythology"] = {
display = "[[Slavic]] [[mythology]]",
topical_categories = true,
}
labels["smoking"] = {
Wiktionary = "smoking#Noun",
topical_categories = true,
}
labels["snooker"] = {
Wiktionary = "snooker#Noun",
topical_categories = true,
}
labels["snowboarding"] = {
Wiktionary = "snowboarding#Noun",
topical_categories = true,
}
labels["soccer"] = {
aliases = {"football", "association football"},
Wiktionary = true,
topical_categories = "Football (soccer)",
}
labels["social media"] = {
Wiktionary = true,
topical_categories = true,
}
labels["social sciences"] = {
aliases = {"social science"},
display = "[[social science]]s",
topical_categories = true,
}
labels["socialism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sociolinguistics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sociology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["softball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["software"] = {
Wiktionary = true,
topical_categories = true,
}
labels["software architecture"] = {
Wiktionary = true,
topical_categories = {"Software engineering", "Programming"},
}
labels["software engineering"] = {
aliases = {"software development"},
Wiktionary = true,
topical_categories = true,
}
labels["soil science"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sorting algorithms"] = {
aliases = {"sorting algorithm"},
display = "[[computer science]]",
topical_categories = true,
}
labels["sound"] = {
Wiktionary = "sound#Noun",
topical_categories = true,
}
labels["sound engineering"] = {
Wiktionary = true,
topical_categories = true,
}
labels["South Korean idol fandom"] = {
aliases = {"Korean idol fandom", "Korean idol"},
display = "[[South Korean]] [[idol]] [[fandom]]",
topical_categories = true,
}
labels["South Park"] = {
display = "''[[w:South Park|South Park]]''",
Wikipedia = true,
topical_categories = true,
}
labels["Soviet Union"] = {
aliases = {"USSR", "Soviet"},
Wiktionary = true,
topical_categories = true,
}
labels["space"] = {
Wiktionary = true,
topical_categories = true,
}
labels["space flight"] = {
aliases = {"astronautics", "spaceflight", "space travel"},
Wiktionary = true,
topical_categories = "Space flight",
}
labels["space industry"] = {
Wikipedia = true,
topical_categories = true,
}
labels["space science"] = {
Wiktionary = true,
topical_categories = true,
}
labels["spacecraft"] = {
aliases = {"space craft"},
display = "[[space flight]]",
Wiktionary = true,
topical_categories = true,
}
labels["spacecraft components"] = {
aliases = {"spacecraft component"},
display = "[[spacecraft]]",
topical_categories = true,
}
labels["Spanish politics"] = {
Wikipedia = "Politics of Spain",
topical_categories = true,
}
labels["spectroscopy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["speedrunning"] = {
aliases = {"speedrun", "speedruns"},
Wiktionary = true,
topical_categories = true,
}
labels["spinning"] = {
Wiktionary = true,
topical_categories = true,
}
labels["spiritualism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["sports"] = {
aliases = {"sport"},
Wiktionary = true,
topical_categories = true,
}
labels["square dancing"] = {
aliases = {"square dance"},
Wiktionary = true,
topical_categories = true,
}
labels["squash"] = {
Wikipedia = "Squash (sport)",
topical_categories = true,
}
labels["standard of identity"] = {
display = "[[standard of identity|standards of identity]]",
topical_categories = "Standards of identity",
}
labels["star"] = {
display = "[[astronomy]]",
topical_categories = "Stars",
}
labels["Star Wars"] = {
display = "''[[Star Wars]]''",
topical_categories = true,
}
labels["statistical mechanics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["statistics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["steroid"] = {
display = "[[biochemistry]]",
topical_categories = "Steroids",
}
labels["steroid hormone"] = {
aliases = {"steroid drug"},
display = "[[biochemistry]], [[steroids]]",
topical_categories = "Hormones",
}
labels["stock market"] = {
Wiktionary = true,
topical_categories = true,
}
labels["stock ticker symbol"] = {
aliases = {"stock symbol"},
Wiktionary = true,
topical_categories = "Stock symbols for companies",
}
labels["string instruments"] = {
aliases = {"string instrument"},
display = "[[music]]",
topical_categories = true,
}
labels["subculture"] = {
Wiktionary = true,
topical_categories = "Culture",
}
labels["Sufism"] = {
aliases = {"Sufi Islam"},
Wikipedia = true,
topical_categories = true,
}
labels["sugar acid"] = {
display = "[[organic chemistry]]",
topical_categories = "Sugar acids",
}
labels["sumo"] = {
Wiktionary = true,
topical_categories = true,
}
labels["supply chain"] = {
Wiktionary = true,
topical_categories = true,
}
labels["surface feature"] = {
display = "[[planetology]]",
topical_categories = "Planetary nomenclature",
}
labels["surfing"] = {
aliases = {"surf"},
Wiktionary = "surfing#Noun",
topical_categories = true,
}
labels["surgery"] = {
Wiktionary = true,
topical_categories = true,
}
labels["surveying"] = {
Wiktionary = "surveying#Noun",
topical_categories = true,
}
labels["sushi"] = {
Wiktionary = true,
topical_categories = true,
}
labels["swimming"] = {
Wiktionary = "swimming#Noun",
topical_categories = true,
}
labels["Swiss politics"] = {
Wikipedia = "Politics of Switzerland",
topical_categories = true,
}
labels["swords"] = {
display = "[[sword]]s",
topical_categories = true,
}
labels["syntax"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Syriac Orthodoxy"] = {
aliases = {"Syriac Orthodox", "Syriac Orthodox Church"},
Wikipedia = true,
topical_categories = true,
}
labels["systematic chemical element symbol"] = {
display = "[[chemistry]]",
plain_categories = "Systematic chemical element symbols",
}
labels["systematics"] = {
Wiktionary = true,
topical_categories = "Taxonomy",
}
labels["systems engineering"] = {
Wiktionary = true,
topical_categories = true,
}
labels["systems theory"] = {
Wiktionary = true,
topical_categories = true,
}
labels["table tennis"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Taoism"] = {
aliases = {"Daoism"},
Wiktionary = true,
topical_categories = true,
}
labels["tarot"] = {
Wiktionary = true,
topical_categories = "Cartomancy",
}
labels["taxation"] = {
aliases = {"tax", "taxes"},
Wiktionary = true,
topical_categories = true,
}
labels["taxonomic name"] = {
display = "[[taxonomy]]",
topical_categories = "Taxonomic names",
}
labels["taxonomy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["technology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["telecommunications"] = {
aliases = {"telecommunication", "telecom"},
Wiktionary = true,
topical_categories = true,
}
labels["telegraphy"] = {
Wiktionary = true,
topical_categories = true,
}
labels["telephony"] = {
aliases = {"telephone", "telephones"},
Wiktionary = true,
topical_categories = true,
}
labels["television"] = {
aliases = {"TV"},
Wiktionary = true,
topical_categories = true,
}
labels["tennis"] = {
Wiktionary = true,
topical_categories = true,
}
labels["teratology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Tetris"] = {
Wiktionary = true,
topical_categories = true,
}
labels["textiles"] = {
Wiktionary = true,
topical_categories = true,
}
labels["textual criticism"] = {
Wiktionary = true,
topical_categories = true,
}
labels["theater"] = {
aliases = {"theatre"},
Wiktionary = true,
topical_categories = true,
}
labels["theology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["thermodynamics"] = {
Wiktionary = true,
topical_categories = true,
}
labels["Thracian god"] = {
aliases = {"Thracian goddess", "Thracian deity"},
display = "[[w:Thracian religion|Thracian religion]]",
Wikipedia = "Thracian religion",
topical_categories = "Thracian deities",
}
labels["Tibetan Buddhism"] = {
Wiktionary = true,
topical_categories = "Buddhism",
}
labels["tiddlywinks"] = {
Wiktionary = true,
topical_categories = true,
}
labels["TikTok aesthetic"] = {
display = "[[TikTok]] aesthetic",
topical_categories = "Aesthetics",
}
labels["timber industry"] = {
aliases = {"timber", "wood industry", "lumber industry", "lumber", "logging"},
Wikipedia = true,
topical_categories = true,
}
labels["time"] = {
Wiktionary = true,
topical_categories = true,
}
labels["tincture"] = {
display = "[[heraldry]]",
topical_categories = "Heraldic tinctures",
}
labels["topology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["tort law"] = {
Wiktionary = true,
topical_categories = "Law",
}
labels["tourism"] = {
aliases = {"tourist"},
Wiktionary = true,
topical_categories = true,
}
labels["toxicology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["trading"] = {
aliases = {"trade"},
Wiktionary = "trading#Noun",
topical_categories = true,
}
labels["trading cards"] = {
display = "[[trading card]]s",
topical_categories = true,
}
labels["traditional Chinese medicine"] = {
aliases = {"TCM", "Chinese medicine"},
Wiktionary = true,
topical_categories = true,
}
labels["traditional Korean medicine"] = {
aliases = {"Korean medicine"},
topical_categories = true,
}
labels["transgender"] = {
aliases = {"trans"},
Wiktionary = true,
topical_categories = true,
}
labels["translation studies"] = {
Wiktionary = true,
topical_categories = true,
}
labels["transport"] = {
aliases = {"transportation"},
Wiktionary = true,
topical_categories = true,
}
labels["traumatology"] = {
Wiktionary = true,
topical_categories = "Emergency medicine",
}
labels["travel"] = {
aliases = {"travelling"},
Wiktionary = true,
topical_categories = true,
}
labels["trigonometric function"] = {
display = "[[trigonometry]]",
topical_categories = "Trigonometric functions",
}
labels["trigonometry"] = {
Wiktionary = true,
topical_categories = true,
}
labels["trust law"] = {
Wiktionary = true,
topical_categories = "Law",
}
labels["Tumblr aesthetic"] = {
display = "[[Tumblr]] [[aesthetic]]",
topical_categories = "Aesthetics",
}
labels["Twitter"] = {
aliases = {"twitter", "X"},
Wiktionary = "Twitter#Proper noun",
topical_categories = true,
}
labels["two-up"] = {
Wiktionary = true,
topical_categories = true,
}
labels["typography"] = {
aliases = {"typesetting"},
Wiktionary = true,
topical_categories = true,
}
labels["ufology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["UK politics"] = {
Wikipedia = "Politics of the United Kingdom",
topical_categories = true,
}
labels["Umbanda"] = {
Wiktionary = true,
topical_categories = true,
}
labels["underwater diving"] = {
aliases = {"scuba", "scuba diving"},
display = "[[underwater]] [[diving#Noun|diving]]",
topical_categories = true,
}
labels["Unicode"] = {
aliases = {"Unicode standard"},
Wikipedia = true,
topical_categories = true,
}
labels["United Nations"] = {
aliases = {"UN"},
display = "[[United Nations|UN]]",
Wikipedia = true,
topical_categories = true,
}
labels["Unix"] = {
Wiktionary = true,
topical_categories = true,
}
labels["urban studies"] = {
aliases = {"urbanism", "urban planning"},
Wiktionary = true,
topical_categories = true,
}
labels["urology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["US politics"] = {
Wikipedia = "Politics of the United States",
topical_categories = true,
}
labels["US space program"] = {
aliases = {"NASA"},
Wikipedia = true,
topical_categories = true,
}
labels["Usenet"] = {
aliases = {"newsgroup"},
Wiktionary = true,
topical_categories = true,
}
labels["Vaishnavism"] = {
aliases = {"Vaishnavist"},
Wiktionary = true,
topical_categories = true,
}
labels["Valentinianism"] = {
aliases = {"valentinianism", "Valentinianist", "valentinianist"},
Wikipedia = true,
topical_categories = true,
}
labels["Vedic religion"] = {
aliases = {"Vedic Hinduism", "Vedism", "Vedicism", "Ancient Hinduism", "ancient Hinduism"},
Wikipedia = "Historical Vedic religion",
topical_categories = true,
}
labels["vegetable"] = {
aliases = {"vegetables"},
Wiktionary = true,
topical_categories = "Vegetables",
}
labels["vehicles"] = {
aliases = {"vehicle"},
display = "[[vehicle]]s",
topical_categories = true,
}
labels["Venezuelan politics"] = {
aliases = {"Venezuela politics"},
Wikipedia = "Politics of Venezuela",
topical_categories = true,
}
labels["veterinary disease"] = {
display = "[[veterinary medicine]]",
topical_categories = "Veterinary diseases",
}
labels["veterinary medicine"] = {
aliases = {"veterinary", "vet"},
Wiktionary = true,
topical_categories = true,
}
labels["video compression"] = {
Wikipedia = true,
topical_categories = true,
}
labels["video game genre"] = {
display = "[[video game]]s",
topical_categories = "Video game genres",
}
labels["video games"] = {
aliases = {"video game", "video gaming"},
display = "[[video game]]s",
topical_categories = true,
}
labels["virology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["virus"] = {
display = "[[virology]]",
topical_categories = "Viruses",
}
labels["vitamin"] = {
display = "[[biochemistry]]",
topical_categories = "Vitamins",
}
labels["viticulture"] = {
Wiktionary = true,
topical_categories = {"Horticulture", "Wine"},
}
labels["volcanology"] = {
aliases = {"vulcanology"},
Wiktionary = true,
topical_categories = true,
}
labels["volleyball"] = {
Wiktionary = true,
topical_categories = true,
}
labels["voodoo"] = {
Wiktionary = true,
topical_categories = true,
}
labels["VTuber"] = {
aliases = {"Virtual YouTuber"},
Wiktionary = true,
topical_categories = "Virtual YouTuber",
}
labels["war"] = {
aliases = {"warfare"},
Wiktionary = true,
topical_categories = true,
}
labels["water sports"] = {
aliases = {"watersport", "watersports", "water sport"},
Wiktionary = "watersport",
topical_categories = true,
}
labels["watercraft"] = {
display = "[[nautical]]",
topical_categories = true,
}
labels["weaponry"] = {
aliases = {"weapon", "weapons"},
Wiktionary = true,
topical_categories = "Weapons",
}
labels["weather"] = {
topical_categories = true,
}
labels["weaving"] = {
Wiktionary = "weaving#Noun",
topical_categories = true,
}
labels["web design"] = {
Wiktionary = true,
topical_categories = true,
aliases = {"Web design"}
}
labels["web development"] = {
Wiktionary = true,
topical_categories = {"Programming", "Web design"},
}
labels["weightlifting"] = {
Wiktionary = true,
topical_categories = true,
}
labels["white supremacy"] = { -- Often used to indicate Nazi-used jargon; compare "neo-Nazism"
aliases = {"white nationalism", "white nationalist", "white power", "white racism", "white supremacist ideology", "white supremacism", "white supremacist"},
Wikipedia = true,
topical_categories = "White supremacist ideology",
}
labels["Wicca"] = {
Wiktionary = true,
topical_categories = true,
}
labels["wiki jargon"] = {
aliases = {"wiki", "wikis"},
display = "[[wiki]] [[jargon]]",
topical_categories = "Wiki",
}
labels["Wikimedia jargon"] = {
aliases = {
"Wikimedia", "Wiktionary", "Wiktionary jargon", "Wikipedia", "Wikipedia jargon",
"WMF", "WMF jargon" -- technically not correct
},
display = "[[w:Wikimedia movement|Wikimedia]] [[jargon]]",
topical_categories = "Wikimedia",
}
labels["wind instruments"] = {
aliases = {"wind instrument"},
display = "[[music]]",
topical_categories = true,
}
labels["wine"] = {
aliases = {"oenology", "winemaking"},
Wiktionary = true,
topical_categories = true,
}
labels["winter sports"] = {
display = "[[winter sport]]s",
topical_categories = true,
}
labels["woodwind instruments"] = {
aliases = {"woodwind instrument"},
display = "[[music]]",
topical_categories = true,
}
labels["woodworking"] = {
Wiktionary = true,
topical_categories = true,
}
labels["World War I"] = {
aliases = {"World War 1", "WWI", "WW I", "WW1", "WW 1"},
Wikipedia = true,
topical_categories = true,
}
labels["World War II"] = {
aliases = {"World War 2", "WWII", "WW II", "WW2", "WW 2"},
Wikipedia = true,
topical_categories = true,
}
labels["wrestling"] = {
Wiktionary = "wrestling#Noun",
topical_categories = true,
}
labels["writing"] = {
Wiktionary = "writing#Noun",
topical_categories = true,
}
labels["xiangqi"] = {
aliases = {"Chinese chess"},
Wiktionary = true,
topical_categories = true,
}
labels["Yazidism"] = {
aliases = {"Yezidism"},
Wiktionary = true,
topical_categories = true,
}
labels["yoga"] = {
Wiktionary = true,
topical_categories = true,
}
labels["yoga pose"] = {
aliases = {"asana"},
display = "[[yoga]]",
topical_categories = "Yoga poses",
}
labels["zodiac constellations"] = {
display = "[[astronomy]]",
topical_categories = "Constellations in the zodiac",
}
labels["zoology"] = {
Wiktionary = true,
topical_categories = true,
}
labels["zootomy"] = {
Wiktionary = true,
topical_categories = "Animal body parts",
}
labels["Zoroastrianism"] = {
Wiktionary = true,
topical_categories = true,
}
-- Deprecated/do not use warning (ambiguous, unsuitable etc)
labels["deprecated label"] = {
aliases = {"emergency", "greekmyth", "industry", "morphology", "musici", "quantum", "vector"},
display = "<span style=\"color:var(--wikt-palette-red,red);\"><b>deprecated label</b></span>",
deprecated = true,
}
return require("Module:labels").finalize_data(labels)
nsu97upae3m1op5c4wmnz78e1hmnrn4
throw
0
8154
55060
2026-09-30T22:19:46Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|en}}== ===Rihtstefn=== * IPA: /θɹəʊ/ ===Wordstǣr=== {{inh+|en|enm|throwen}}, of {{inh|en|ang|þrāwan}}, of{{inh|en|gmw-pro|*þrāan}}, of{{inh|en|gem-pro|*þrēaną}}, of {{der|en|ine-pro|*terh₁-}}. ===Word=== {{en-verb|+|+|threw,~ed<l:unregol>|n,~ed<l:unregol>,threw<l:unregol>}}'
55060
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /θɹəʊ/
===Wordstǣr===
{{inh+|en|enm|throwen}}, of {{inh|en|ang|þrāwan}}, of{{inh|en|gmw-pro|*þrāan}}, of{{inh|en|gem-pro|*þrēaną}}, of {{der|en|ine-pro|*terh₁-}}.
===Word===
{{en-verb|+|+|threw,~ed<l:unregol>|n,~ed<l:unregol>,threw<l:unregol>}}
9yxj39d3n47c1hxtad26c17yxdsg2x6
55061
55060
2026-09-30T22:20:22Z
Deadend0914
7211
/* Word */
55061
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /θɹəʊ/
===Wordstǣr===
{{inh+|en|enm|throwen}}, of {{inh|en|ang|þrāwan}}, of{{inh|en|gmw-pro|*þrāan}}, of{{inh|en|gem-pro|*þrēaną}}, of {{der|en|ine-pro|*terh₁-}}.
===Word===
{{en-verb|+|+|threw,~ed<l:unregol>|n,~ed<l:unregol>,threw<l:unregol>}}
# tō weorpenne
davpqdj8nh7vut1y54l520fnl7r5tzj
55062
55061
2026-09-30T22:21:24Z
Deadend0914
7211
/* Word */
55062
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /θɹəʊ/
===Wordstǣr===
{{inh+|en|enm|throwen}}, of {{inh|en|ang|þrāwan}}, of{{inh|en|gmw-pro|*þrāan}}, of{{inh|en|gem-pro|*þrēaną}}, of {{der|en|ine-pro|*terh₁-}}.
===Word===
{{en-verb|+|+|threw,~ed<l:unregol>|n,~ed<l:unregol>,threw<l:unregol>}}
# tō [[weorpan|weorpenne]]
o9mxbcdahcv83e5wqv97rpcnaufce1b
55063
55062
2026-09-30T22:25:21Z
Deadend0914
7211
/* Wordstǣr */
55063
wikitext
text/x-wiki
=={{sprǣc|en}}==
===Rihtstefn===
* IPA: /θɹəʊ/
===Wordstǣr===
{{inh+|en|enm|throwen}}, of {{inh|en|ang|þrāwan}}, of {{inh|en|gmw-pro|*þrāan}}, of {{inh|en|gem-pro|*þrēaną}}, of {{der|en|ine-pro|*terh₁-}}.
===Word===
{{en-verb|+|+|threw,~ed<l:unregol>|n,~ed<l:unregol>,threw<l:unregol>}}
# tō [[weorpan|weorpenne]]
p5h6h7xmautovjjntiv0pa6yj1torzv
Bysen:desctree
10
8155
55064
2026-09-30T22:31:38Z
Deadend0914
7211
Redirected page to [[Bysen:descendants tree]]
55064
wikitext
text/x-wiki
#REDIRECT [[Template:descendants tree]]
b11sln24gbntbu621khvtp67ff4tuv0
Bysen:descendants tree
10
8156
55065
2026-09-30T22:31:55Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:etymology/templates/descendant|descendants_tree}}<noinclude>{{documentation}}</noinclude>'
55065
wikitext
text/x-wiki
{{#invoke:etymology/templates/descendant|descendants_tree}}<noinclude>{{documentation}}</noinclude>
jextvnsqr4try0jwxxcgzfnuubngjor
Module:descendants tree
828
8157
55066
2026-09-30T22:32:16Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local debug_track_module = "Module:debug/track" local string_pattern_escape_module = "Module:string/patternEscape" local string_remove_comments_module = "Module:string/removeComments" local function debug_track(...) debug_track = require(debug_track_module) return debug_track(...) end local function pattern_escape(...) pattern_escape = require(string_pattern_escape_module) return pattern_escape(...) end local function remove_comments(......'
55066
Scribunto
text/plain
local export = {}
local debug_track_module = "Module:debug/track"
local string_pattern_escape_module = "Module:string/patternEscape"
local string_remove_comments_module = "Module:string/removeComments"
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
local function pattern_escape(...)
pattern_escape = require(string_pattern_escape_module)
return pattern_escape(...)
end
local function remove_comments(...)
remove_comments = require(string_remove_comments_module)
return remove_comments(...)
end
local function track(page)
--[[Special:WhatLinksHere/Wiktionary:Tracking/descendants tree/PAGE]]
return debug_track("descendants tree/" .. page)
end
local function preview_error(what, entry_name, language_name, reason)
mw.log("Could not retrieve " .. what .. " for " .. language_name .. " in the entry [["
.. entry_name .. "]]: " .. reason .. ".")
track(what .. " error")
end
local function get_content_after_senseid(content, entry_name, lang, id)
local code = lang:getFullCode()
local t_start, t_end
-- UGH. We need to set `not_transcluded` to true otherwise the template parser won't find all the templates
-- on large pages. We should probably default `not_transcluded` to true in general.
for template in require("Module:template parser").find_templates(content, true) do
local name = template:get_name()
if name == "senseid" then
local args = template:get_arguments()
if args[1] == code and args[2] == id then
t_start = template.index
end
elseif name == "etymid" then
local args = template:get_arguments()
if args[1] == code and args[2] == id then
t_start = template.index
elseif t_start ~= nil and t_end == nil then
t_end = template.index
end
elseif name == "head" or name == "etymon" then
local args = template:get_arguments()
local id_arg = args.id
if args[1] == code and id_arg == id then
t_start = template.index
elseif id_arg ~= nil and t_start ~= nil and t_end == nil then
t_end = template.index
end
end
end
if t_start == nil then
error("Could not find the correct senseid template in the entry [["
.. entry_name .. "]] (with language " .. code .. " and id '" .. id .. "')")
end
if t_end == nil then
-- terminate on L2 or another "Etymology ..." header
-- match L2 and remove it and everything after it
content = string.gsub(content:sub(t_start), "\n==[^=].+$", "")
-- match Etymology header and remove it and everything after it
content = string.gsub(content, "\n===+%s*Etymology.+$", "")
return content
end
return content:sub(t_start, t_end)
end
function export.get_alternative_forms(lang, entry_name, id, default_separator)
local page = mw.title.new(entry_name)
local content = page:getContent()
local function alt_form_error(reason)
preview_error("alternative forms", entry_name, lang:getFullName(), reason)
end
if not content then
-- FIXME, should be an error
alt_form_error("nonexistent page")
track("alts-nonexistent-page")
return ""
end
local _, index = string.find(content,
"==[ \t]*" .. pattern_escape(lang:getFullName()) .. "[ \t]*==")
if not index then
-- FIXME, should be an error
alt_form_error("L2 header for language not found")
track("alts-lang-not-found")
return ""
end
if id then
content = get_content_after_senseid(content, entry_name, lang, id)
index = 0
end
local _, next_lang = string.find(content, "\n==[^=\n]+==", index, false)
local _, index = string.find(content, "\n(====?=?)[ \t]*Alternative forms[ \t]*%1", index, false)
if not index then
_, index = string.find(content, "\n(====?=?)[ \t]*Alternative reconstructions[ \t]*%1", index, false)
end
if not index then
-- FIXME, should be an error
alt_form_error("'Alternative forms' section for language not found")
track("alts-section-not-found")
return ""
end
local langCodeRegex = pattern_escape(lang:getFullCode())
index = string.find(content, "{{alt[ei]?r?|" .. langCodeRegex .. "|[^|}]+", index)
if (not index) or (next_lang and next_lang < index) then
-- FIXME, should be an error
alt_form_error("no 'alt' or 'alter' template in 'Alternative forms' section for language")
track("alts-alter-not-found")
return ""
end
local next_section = string.find(content, "\n(=+)[^=]+%1", index)
local alternative_forms_section = string.sub(content, index, next_section)
local terms_list = {}
local altforms = require("Module:alternative forms")
for template in require("Module:template parser").find_templates(alternative_forms_section) do
if template:get_name() == "alter" then
local args = template:get_arguments()
if args[1] == lang:getFullCode() then
saw_alter = true
local formatted_altforms = altforms.display_alternative_forms(args, entry_name, "allow self link", default_separator)
table.insert(terms_list, formatted_altforms)
end
end
end
if #terms_list == 0 then
-- FIXME, should be an error
alt_form_error("no terms in 'alt' or 'alter' template in 'Alternative forms' section for language")
track("alts-no-terms-in-alter")
return ""
end
return table.concat(terms_list, default_separator or ", ")
end
function export.get_descendants(lang, entry_name, id, noerror)
local page = mw.title.new(entry_name)
local content = page:getContent()
local namespace = mw.title.getCurrentTitle().nsText
local function desc_error(reason)
preview_error("descendants", entry_name, lang:getFullName(), reason)
end
if not content then
-- FIXME, should be an error
desc_error("nonexistent page")
track("desctree-nonexistent-page")
return ""
end
-- Ignore HTML comments, columns and blank lines.
content = remove_comments(content)
:gsub("{{top%d}}%s", "")
:gsub("{{mid%d}}%s", "")
:gsub("{{bottom}}%s", "")
:gsub("\n?{{(desc?%-%l+)|?[^}]*}}",
function (template_name)
if template_name == "desc-top" or template_name == "desc-bottom" or template_name == "des-top" or template_name == "des-mid" or template_name == "des-bottom" then
return ""
end
end)
:gsub("\n%s*\n", "\n")
local _, index = string.find(content,
"%f[^\n%z]==[ \t]*" .. lang:getFullName() .. "[ \t]*==", nil, true)
if not index then
_, index = string.find(content, "%f[^\n%z]==[ \t]*"
.. pattern_escape(lang:getFullName())
.. "[ \t]*==", nil, false)
end
if not index then
desc_error("L2 header for language not found")
-- FIXME, should be an error
track("desctree-lang-not-found")
return ""
end
if id then
content = get_content_after_senseid(content, entry_name, lang, id)
index = 0
end
local _, next_lang = string.find(content, "\n==[^=\n]+==", index, false)
local _, index = string.find(content, "\n(====*)[ \t]*Descendants[ \t]*%1", index, false)
local function desctree_no_descendants(with_lang_in_error)
if noerror and (namespace == "" or namespace == "Reconstruction") then
track("desctree-no-descendants")
return "<small class=\"error previewonly\">(" ..
"Please either change this template to {{desc}} " ..
"or insert a ====Descendants==== section in [[" ..
entry_name .. "#" .. lang:getFullName() .. "]])</small>" ..
"[[Category:" .. lang:getFullName() .. " descendants to be fixed in desctree]]"
else
error(("No Descendants section was found in the entry [[%s]]%s"):format(entry_name,
with_lang_in_error and (" under the header for %s"):format(lang:getFullName()) or ""))
end
end
if not index then
return desctree_no_descendants()
elseif next_lang and next_lang < index then
return desctree_no_descendants("with lang in error")
end
-- Skip past final equals sign.
index = index + 1
-- Skip past spaces or tabs.
while true do
local new_index = string.match(content, "^[ \t]+()", index)
if not new_index then
break
end
index = new_index
end
local items = require("Module:array")()
local frame = mw.getCurrentFrame()
local previous_list_markers = ""
-- Skip paragraphs at beginning of Descendants section.
while true do
local new_index = content:match("^\n[^%*:=][^\n]*()", index)
if not new_index then
break
else
index = new_index
end
end
previous_index = 1
-- Find a consecutive series of list items that begins directly after the
-- Descendants header.
-- start_index and previous_index are used to check that list items are
-- consecutive.
for start_index, list_markers, item, index in string.gmatch(content:sub(index), "()\n([%*:]+) *([^\n]+)()") do
if start_index ~= previous_index then
break
end
-- Preprocess, but replace recursive calls to avoid template loop errors
item = string.gsub(item, "{{desctree|", "{{#invoke:etymology/templates/descendant|descendants_tree|")
item = frame:preprocess(item)
local difference = #list_markers - #previous_list_markers
if difference > 0 then
for i = #previous_list_markers + 1, #list_markers do
items:insert(list_markers:sub(i, i) == "*" and "<ul>" or "<dl>")
end
else
if difference < 0 then
for i = #previous_list_markers, #list_markers + 1, -1 do
items:insert(previous_list_markers:sub(i, i) == "*" and "</li></ul>" or "</dd></dl>")
end
else
items:insert(previous_list_markers:sub(-1, -1) == "*" and "</li>" or "</dd>")
end
if previous_list_markers:sub(#list_markers, #list_markers) ~= list_markers:sub(-1, -1) then
items:insert(list_markers:sub(-1, -1) == "*" and "</dl><ul>" or "</ul><dl>")
end
end
items:insert(list_markers:sub(-1, -1) == "*" and "<li>" or "<dd>")
items:insert(item)
previous_list_markers = list_markers
previous_index = index
end
for i = #previous_list_markers, 1, -1 do
items:insert(previous_list_markers:sub(i, i) == "*" and "</li></ul>" or "</dd></dl>")
end
return items:concat()
end
return export
pqx7k3asjvcuripstqlhxn72h8o44nv
deaþ
0
8158
55068
2026-09-30T22:36:16Z
Deadend0914
7211
Redirected page to [[dēaþ]]
55068
wikitext
text/x-wiki
#redirect [[dēaþ]]
9s5nd4r5sf98jqqhibofrvaxtrn8jog
Bysen:desc
10
8159
55069
2026-09-30T22:37:32Z
Deadend0914
7211
Redirected page to [[Bysen:descendant]]
55069
wikitext
text/x-wiki
#REDIRECT [[Bysen:descendant]]
nnsut0yu5ftp7jpywh922jj3qus8whg
Bysen:descendant
10
8160
55070
2026-09-30T22:37:48Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:etymology/templates/descendant|descendant}}<noinclude> {{documentation}} </noinclude>'
55070
wikitext
text/x-wiki
{{#invoke:etymology/templates/descendant|descendant}}<noinclude>
{{documentation}}
</noinclude>
5i762qwxh1fn2n8ve51agjddncrfsgo
Module:TemplateStyles
828
8161
55071
2026-09-30T22:38:08Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local frame = mw.getCurrentFrame() local args = {} return function (stylesheet) args.src = stylesheet return frame:extensionTag("templatestyles", nil, args) end'
55071
Scribunto
text/plain
local frame = mw.getCurrentFrame()
local args = {}
return function (stylesheet)
args.src = stylesheet
return frame:extensionTag("templatestyles", nil, args)
end
fpxp4rqu6felstio94845c8h0qu6kw7
Module:languages/data/3/s
828
8162
55072
2026-09-30T22:38:43Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local m_langdata = require("Module:languages/data") -- Loaded on demand, as it may not be needed (depending on the data). local function u(...) u = require("Module:string utilities").char return u(...) end local c = m_langdata.chars local p = m_langdata.puaChars local s = m_langdata.shared local m = {} m["saa"] = { "Saba", 3914885, "cdc-est", "Latn", } m["sab"] = { "Buglere", 3368506, "cba", "Latn", } m["sac"] = { "Fox", 12714767, "alg-sfk", "...'
55072
Scribunto
text/plain
local m_langdata = require("Module:languages/data")
-- Loaded on demand, as it may not be needed (depending on the data).
local function u(...)
u = require("Module:string utilities").char
return u(...)
end
local c = m_langdata.chars
local p = m_langdata.puaChars
local s = m_langdata.shared
local m = {}
m["saa"] = {
"Saba",
3914885,
"cdc-est",
"Latn",
}
m["sab"] = {
"Buglere",
3368506,
"cba",
"Latn",
}
m["sac"] = {
"Fox",
12714767,
"alg-sfk",
"Latn",
}
m["sad"] = {
"Sandawe",
34016,
"qfa-iso",
"Latn",
}
m["sae"] = {
"Sabanê",
3460478,
"sai-nmk",
"Latn",
}
m["saf"] = {
"Safaliba",
36432,
"nic-mre",
"Latn",
}
m["sah"] = {
"Yakut",
34299,
"trk-nsb",
"Cyrl",
translit = "sah-translit",
override_translit = true,
}
m["saj"] = {
"Sahu",
7399757,
"paa-sah",
"Latn",
}
m["sak"] = {
"Sake",
36425,
"bnt-kel",
"Latn",
}
m["sam"] = {
"Samaritan Aramaic",
56612,
"sem-arw",
"Samr",
translit = "Samr-translit",
-- Samr strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["sao"] = {
"Sause",
4409155,
"qfa-dis", -- isolate per Glottolog, possibly related to Kapauri in a Kapauri-Sause family under Greater Kwerba
"Latn",
}
m["saq"] = {
"Samburu",
56536,
"sdv-lma",
}
m["sar"] = {
"Saraveca",
3450556,
"awd",
"Latn",
}
m["sas"] = {
"Sasak",
1294047,
"poz-bss",
"Latn, Bali, Java",
}
m["sat"] = {
"Santali",
33965,
"mun",
"Olck",
translit = "Olck-translit",
override_translit = true,
}
m["sau"] = {
"Saleman",
7404262,
"poz-cet",
}
m["sav"] = {
"Saafi-Saafi",
36308,
"alv-cng",
"Arab, Latn",
}
m["saw"] = {
"Sawi",
677064,
"ngf-awd",
"Latn",
}
m["sax"] = {
"Sa",
3460352,
"poz-vnn",
"Latn",
}
m["say"] = {
"Saya",
3914431,
"cdc-wst",
"Latn",
}
m["saz"] = {
"Saurashtra",
13292,
"inc-wes",
"Saur, Latn, Taml, Deva",
translit = "saz-translit",
ancestors = "inc-ogu",
}
m["sba"] = {
"Ngambay",
2372207,
"csu-sar",
"Latn",
}
m["sbb"] = {
"Simbo",
3484101,
"poz-ocw",
}
m["sbc"] = {
"Gele'",
3194847,
"poz-aay",
"Latn",
}
m["sbd"] = {
"Southern Samo",
33122730,
"dmn-sam",
"Latn",
}
m["sbe"] = {
"Saliba (New Guinea)",
3469737,
"poz-ocw",
}
m["sbf"] = {
"Shabo",
36342,
"ssa",
"Latn",
}
m["sbg"] = {
"Seget",
7446237,
"paa-wbh",
"Latn",
}
m["sbh"] = {
"Sori-Harengan",
36515,
"poz-aay",
"Latn",
}
m["sbi"] = {
"Seti",
7456682,
"paa-trr",
"Latn",
}
m["sbj"] = {
"Surbakhal",
759995,
}
m["sbk"] = {
"Safwa",
4121160,
"bnt-mby",
"Latn",
}
m["sbl"] = {
"Botolan Sambal",
4095195,
"phi",
"Latn",
}
m["sbm"] = {
"Sagala",
11732610,
"bnt-ruv",
"Latn",
}
m["sbn"] = {
"Sindhi Bhil",
25559289,
"inc-snd",
"Arab, Deva, Sind, Guru",
ancestors = "sd",
}
m["sbo"] = {
"Sabüm",
7396535,
"mkh-asl",
}
m["sbp"] = {
"Sangu (Tanzania)",
7418149,
"bnt-bki",
"Latn",
}
m["sbq"] = {
"Sirva",
7514337,
"ngf-nso",
"Latn",
}
m["sbr"] = {
"Sembakung Murut",
7449148,
"poz-san",
}
m["sbs"] = {
"Subiya",
6442073,
"bnt-bot",
"Latn",
}
m["sbt"] = {
"Kimki",
6410160,
"qfa-dis", -- Papuan; Glottolog and Foley treat as an isolate, Usher puts in South Pauwasi
"Latn",
}
m["sbu"] = {
"Stod Bhoti",
15622700,
"sit-las",
}
m["sbv"] = {
"Sabine",
65455885,
"itc-sbl",
"Latn",
display_text = s["itc-Latn-displaytext"],
strip_diacritics = s["itc-Latn-stripdiacritics"],
sort_key = s["itc-Latn-sortkey"],
}
m["sbw"] = {
"Simba",
36430,
"bnt-tso",
"Latn",
}
m["sbx"] = {
"Seberuang",
12473470,
"poz-mly",
}
m["sby"] = {
"Soli",
7557754,
"bnt-bot",
"Latn",
}
m["sbz"] = {
"Sara Kaba",
25559318,
"csu-kab",
"Latn",
}
m["scb"] = {
"Chut",
2967709,
"mkh-vie",
"Latn",
}
m["sce"] = {
"Dongxiang",
32947,
"xgn-shr",
"Arab, Latn",
}
m["scf"] = {
"San Miguel Creole French",
12953094,
"crp",
"Latn",
ancestors = "gcf",
sort_key = s["roa-oil-sortkey"],
}
m["scg"] = {
"Sanggau",
12473466,
"day",
}
m["sch"] = {
"Sakachep",
37054,
"tbq-kuk",
}
m["sci"] = {
"Sri Lankan Creole Malay",
1089151,
"crp",
"Latn",
ancestors = "ms",
}
m["sck"] = {
"Sadri",
765922,
"inc-sad",
"Deva, Kthi",
translit = {
Deva = "hi-translit",
Kthi = "bho-Kthi-translit",
},
}
m["scl"] = {
"Shina",
1353320,
"inc-shn",
"Aran, Deva",
}
m["scn"] = {
"Sicilian",
33973,
"roa-itr",
ancestors = "scn-old",
"Latn",
}
m["sco"] = {
"Scots",
14549,
"gmw-ang",
"Latn",
ancestors = "gmw-msc",
}
m["scp"] = {
"Yolmo",
22662107,
"sit-kyk",
"Deva",
}
m["scq"] = {
"Sa'och",
6583617,
"mkh-pea",
}
m["scs"] = {
"North Slavey",
20628,
"den",
"Latn",
}
m["scu"] = {
"Shumcho",
22077739,
"sit-kin",
}
m["scv"] = {
"Sheni",
11015820,
"nic-jer",
"Latn",
ancestors = "zir",
}
m["scw"] = {
"Sha",
3438816,
"cdc-wst",
"Latn",
}
m["scx"] = {
"Sicel",
36667,
"itc",
"Polyt",
-- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["scz"] = {
"Shetland",
3069598,
"qfa-mix",
"Latn",
ancestors = "nrn, gmw-msc",
standard_chars = "AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZzØøÖüÜü0123456789" .. c.punc,
}
m["sda"] = {
"Toraja-Sa'dan",
36673,
"poz-ssw",
"Latn",
}
m["sdb"] = {
"Shabak",
3289596,
"ira-zgr",
ancestors = "hac",
}
m["sdc"] = {
"Sassarese",
845441,
"roa-itr",
"Latn",
}
m["sde"] = {
"Surubu",
3913336,
"nic-kau",
"Latn",
}
m["sdf"] = {
"Sarli",
7424256,
"ira-zgr",
ancestors = "hac",
}
m["sdg"] = {
"Savi",
3474654,
"inc-dng",
}
m["sdh"] = {
"Southern Kurdish",
1496597,
"ku",
"Arab",
translit = "sdh-translit",
strip_diacritics = {remove_diacritics = c.kasra .. c.sukun},
}
m["sdj"] = {
"Suundi",
7650407,
"bnt-kng",
"Latn",
}
m["sdk"] = {
"Sos Kundi",
7563811,
"paa-nnd",
"Latn",
}
m["sdl"] = {
"Saudi Arabian Sign Language",
3504160,
"sgn",
}
m["sdm"] = {
"Semandang",
7449012,
"day",
}
m["sdn"] = {
"Gallurese",
612220,
"roa-itr",
"Latn",
ancestors = "co",
}
m["sdo"] = {
"Bukar-Sadung Bidayuh",
2927799,
"day",
"Latn",
}
m["sdp"] = {
"Sherdukpen",
7494785,
"sit-khm",
}
m["sdr"] = {
"Oraon Sadri",
12953860,
"inc-bih",
}
m["sds"] = {
"Tunisian Berber",
5329732,
"ber",
"Latn",
}
m["sdu"] = {
"Sarudu",
7424700,
"poz-cet",
}
m["sdx"] = {
"Sibu Melanau",
18642842,
"poz-bnn",
}
m["sea"] = {
"Semai",
3135426,
"mkh-asl",
"Latn",
}
-- seb is a duplicate code of spp
m["sec"] = {
"Sechelt",
7442898,
"sal",
"Latn",
}
m["sed"] = {
"Sedang",
56448,
"mkh-nbn",
"Latn",
}
m["see"] = {
"Seneca",
1185133,
"iro-nor",
"Latn",
}
m["sef"] = {
"Cebaara",
10975121,
"alv-snr",
"Latn",
}
m["seg"] = {
"Segeju",
17584599,
"bnt-mij",
"Latn",
}
m["seh"] = {
"Sena",
2964008,
"bnt-sna",
"Latn",
}
m["sei"] = {
"Seri",
36583,
"qfa-iso",
"Latn",
}
m["sej"] = {
"Sene",
7450252,
"ngf-ehu",
"Latn",
}
m["sek"] = {
"Sekani",
28562,
"ath-nor",
"Latn",
}
m["sen"] = {
"Nanerige",
36002,
"alv-sma",
}
m["seo"] = {
"Asaba",
7630513,
"qfa-dis", -- Papuan; isolate or unclassified in Glottolog; Sepik language in Foley (2018)
"Latn",
}
m["sep"] = {
"Sicite",
56787,
"alv-sma",
}
m["seq"] = {
"Senara", -- not to be confused with Syenara (shz)
35210,
"alv-snr",
}
m["ser"] = {
"Serrano",
3479942,
"azc-tak",
"Latn",
}
m["ses"] = {
"Koyraboro Senni",
35655,
"son",
"Latn",
}
m["set"] = {
"Sentani",
3441672,
"paa-sen",
"Latn",
}
m["seu"] = {
"Serui-Laut",
7455503,
"poz-hce",
"Latn",
}
m["sev"] = {
"Nyarafolo",
36306,
"alv-snr",
}
m["sew"] = {
"Sewa Bay",
7458126,
"poz-ocw",
}
m["sey"] = {
"Secoya",
3477218,
"sai-tuc",
"Latn",
}
m["sez"] = {
"Senthang Chin",
7451223,
"tbq-kuk",
}
m["sfb"] = {
"French Belgian Sign Language",
3217332,
"sgn",
}
m["sfe"] = {
"Eastern Subanun",
63311321,
"phi",
"Latn",
}
m["sfm"] = {
"Small Flowery Miao",
7542773,
"hmn",
}
m["sfs"] = {
"South African Sign Language",
3322093,
"sgn",
}
m["sfw"] = {
"Sehwi",
36593,
"alv-ctn",
"Latn",
}
m["sga"] = {
"Old Irish",
35308,
"cel-gae",
"Latn, Ogam",
strip_diacritics = {remove_diacritics = c.dotabove .. c.diaer .. "·"},
sort_key = "sga-sortkey",
standard_chars = "AaÁáBbCcDdEeÉéFfGgHhIiÍíLlMmNnOoÓóPpRrSsTtUuÚú0123456789ᚁᚂᚃᚄᚅᚆᚇᚈᚉᚊᚋᚌᚍᚎᚏᚐᚑᚒᚓᚔ" .. c.punc,
}
m["sgb"] = {
"Mag-Anchi Ayta",
4356243,
"phi",
"Latn",
}
m["sgc"] = {
"Kipsigis",
56339,
"sdv-nma",
}
m["sgd"] = {
"Surigaonon",
34140,
"phi",
"Latn",
}
m["sge"] = {
"Segai",
7446180,
}
m["sgg"] = {
"Swiss-German Sign Language",
35150,
"sgn",
}
m["sgh"] = {
"Shughni",
34053,
"ira-shr",
"Latn, Cyrl, Arab",
translit = "sgh-translit",
override_translit = true,
}
m["sgi"] = {
"Suga",
36475,
"nic-mmb",
"Latn",
}
m["sgk"] = {
"Sangkong",
2945610,
"tbq-bis",
}
m["sgm"] = {
"Singa",
7522797,
"bnt-lok",
"Latn",
}
m["sgp"] = {
"Singpho",
7524158,
"sit-jnp",
"Latn",
}
m["sgr"] = {
"Sangisari",
3394363,
"ira-kms",
"Arab",
}
m["sgs"] = {
"Samogitian",
213434,
"bat-eas",
"Latn",
wikimedia_codes = "bat-smg",
ancestors = "olt",
display_text = "lt-common",
strip_diacritics = "lt-common",
sort_key = "lt-common",
}
m["sgt"] = {
"Brokpake",
56603,
"sit-tib",
"Tibt",
override_translit = true,
-- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["sgu"] = {
"Salas",
7403694,
"poz-cma",
}
m["sgw"] = {
"Sebat Bet Gurage",
2707343,
"sem-eth",
"Ethi",
}
m["sgx"] = {
"Sierra Leone Sign Language",
7511448,
"sgn-asl",
}
m["sgy"] = {
"Sanglechi",
3472220,
"ira-sgi",
}
m["sgz"] = {
"Sursurunga",
36511,
"poz-ocw",
"Latn",
}
m["sha"] = {
"Shall-Zwall",
3915355,
"nic-beo",
}
m["shb"] = {
"Ninam",
3436586,
"sai-ynm",
"Latn",
}
m["shc"] = {
"Sonde",
7560881,
"bnt-pen",
"Latn",
}
m["shd"] = {
"Kundal Shahi",
6444265,
"inc-shn",
"Aran",
}
m["she"] = {
"Sheko",
3183355,
"omv-diz",
}
m["shg"] = {
"Shua",
3501092,
"khi-kal",
"Latn",
}
m["shh"] = {
"Shoshone",
33811,
"azc-num",
"Latn",
}
m["shi"] = {
"Tashelhit",
34152,
"ber",
"Latn, Arab, Tfng, Hebr",
ancestors = "shi-med",
translit = {
Tfng = "Tfng-translit",
},
strip_diacritics = {
Arab = "ar-stripdiacritics",
},
-- Hebr display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["shj"] = {
"Shatt",
56344,
"sdv-daj",
}
m["shk"] = {
"Shilluk",
36486,
"sdv-lon",
"Latn",
}
m["shm"] = {
"Shahrudi",
7462280,
"xme-ttc",
"Arab, Latn",
ancestors = "xme-ttc-cen",
}
m["shn"] = {
"Shan",
56482,
"tai-swe",
"Mymr",
translit = "shn-translit",
sort_key = {
from = {"[ၢႃ]", "ဵ", "ႅ", "ႇ", "ႈ", "း", "ႉ", "ႊ"},
to = {"ာ", "ေ", "ႄ", "႒", "႓", "႔", "႕", "႖"}
},
}
m["sho"] = {
"Shanga",
3913931,
"dmn-bbu",
"Latn",
}
m["shp"] = {
"Shipibo-Conibo",
2671988,
"sai-pan",
"Latn",
}
m["shq"] = {
"Sala",
10961665,
"bnt-bot",
"Latn",
}
m["shr"] = {
"Shi",
3481999,
"bnt-shh",
"Latn",
}
m["shs"] = {
"Shuswap",
3482685,
"sal",
"Latn",
}
m["sht"] = {
"Shasta",
56396,
"nai-shs",
"Latn",
}
m["shu"] = {
"Chadian Arabic",
56497,
"sem-arb",
"Arab",
strip_diacritics = {
remove_diacritics = c.kashida .. c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.superalef,
from = {u(0x0671)},
to = {u(0x0627)}
},
}
m["shv"] = {
"Shehri",
33445,
"sem-sar",
"Arab, Latn",
}
m["shw"] = {
"Shwai",
36527,
"alv-hei",
}
m["shx"] = {
"She",
2605689,
"hmn",
}
m["shy"] = {
"Tachawit",
33274,
"ber",
"Tfng, Arab, Latn",
translit = "Tfng-translit",
}
m["shz"] = {
"Syenara", -- not to be confused with Senara (seq)
36316,
"alv-snr",
}
m["sia"] = {
"Akkala Sami",
35241,
"smi",
"Cyrl, Latn",
translit = "sia-translit",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = "'ˈ"},
}
m["sib"] = {
"Sebop",
7442799,
"poz-swa",
"Latn",
}
m["sid"] = {
"Sidamo",
33786,
"cus-hec",
"Latn, Ethi",
}
m["sie"] = {
"Simaa",
7517329,
"bnt-kav",
"Latn",
}
m["sif"] = {
"Siamou",
36252,
}
m["sig"] = {
"Paasaal",
36426,
"nic-sis",
"Latn",
}
m["sih"] = {
"Sîshëë",
8072753,
"poz-cln",
"Latn",
}
m["sii"] = {
"Shom Peng",
1039346,
"aav",
}
m["sij"] = {
"Numbami",
3346277,
"poz-ocw",
"Latn",
}
m["sik"] = {
"Sikiana",
3443734,
"sai-prk",
"Latn",
}
m["sil"] = {
"Tumulung Sisaala",
25383006,
"nic-sis",
"Latn",
}
m["sim"] = {
"Mende (New Guinea)",
7446815,
"paa-sep",
"Latn",
}
m["sip"] = {
"Sikkimese",
35285,
"sit-tib",
"Tibt",
ancestors = "xct",
override_translit = true,
-- Tibt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["siq"] = {
"Sonia",
7561770,
"ngf-bos",
"Latn",
}
m["sir"] = {
"Siri",
3438729,
"cdc-wst",
"Latn",
}
m["sis"] = {
"Siuslaw",
2315424,
}
m["siu"] = {
"Sinagen",
7521655,
"paa-wap",
"Latn",
}
m["siv"] = {
"Sumariup",
7636966,
"paa-sep",
"Latn",
}
m["siw"] = {
"Siwai",
7532519,
"paa-sbo",
"Latn",
}
m["six"] = {
"Sumau",
7637021,
"ngf-pek",
"Latn",
}
m["siy"] = {
"Sivandi",
13269,
"xme",
"Arab, Latn",
ancestors = "xme-mid",
}
m["siz"] = {
"Siwi",
36814,
"ber",
"Tfng, Arab, Latn",
}
m["sja"] = {
"Epena",
3055682,
"sai-chc",
"Latn",
}
m["sjb"] = {
"Sajau Basap",
4684353,
"poz-bnn",
}
m["sjc"] = {
"Shaojiang Min",
3431451,
"zhx-inm",
"Hants",
generate_alternants = "zh-generatealternants",
sort_key = "Hani-sortkey",
}
m["sjd"] = {
"Kildin Sami",
33656,
"smi",
"Cyrl",
translit = "sjd-translit",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = "'ˈ"},
}
m["sje"] = {
"Pite Sami",
56314,
"smi",
"Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = c.macron .. "'ˈ"},
sort_key = "sje-sortkey",
}
m["sjg"] = {
"Assangori",
3502255,
"sdv-tmn",
}
m["sjk"] = {
"Kemi Sami",
35871,
"smi",
"Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = "'ˈ"},
}
m["sjl"] = {
"Miji",
6845470,
"sit-hrs",
}
m["sjm"] = {
"Mapun",
3287253,
"poz-sbj",
"Latn",
}
m["sjn"] = {
"Sindarin",
56437,
"art",
"Latn, Teng",
type = "appendix-constructed",
}
m["sjo"] = {
"Xibe",
13223,
"tuw-jrc",
"sjo-Mong",
ancestors = "mnc",
}
m["sjp"] = {
"Surjapuri",
7645351,
"inc-krd",
"Deva, as-Beng, Kthi",
}
m["sjr"] = {
"Siar-Lak",
3482907,
"poz-ocw",
}
m["sjs"] = {
"Senhaja de Srair",
56744,
"ber",
"Latn, Tfng, Arab",
strip_diacritics = {
Arab = "ar-stripdiacritics",
},
translit = {
Tfng = "Tfng-translit",
}
}
m["sjt"] = {
"Ter Sami",
36656,
"smi",
"Cyrl, Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = "'ˈ"},
translit = "sjt-translit",
}
m["sju"] = {
"Ume Sami",
56415,
"smi",
"Latn",
strip_diacritics = {remove_diacritics = c.macron .. "'ˈ"},
display_text = {
from = {"'"},
to = {"ˈ"}
},
sort_key = "sju-sortkey",
}
m["sjw"] = {
"Shawnee",
2669206,
"alg",
"Latn",
}
-- [ska] Skagit is treated as part of [lut] Lushootseed
m["skb"] = {
"Saek",
36437,
"tai-nor",
"Thai",
sort_key = "Thai-sortkey",
}
m["skc"] = {
"Ma Manda",
6720783,
"ngf-era",
"Latn",
}
m["skd"] = {
"Southern Sierra Miwok",
3492334,
"nai-utn",
"Latn",
}
m["ske"] = {
"Ske",
7534244,
"poz-vnn",
"Latn",
}
m["skf"] = {
"Mekéns",
3304806,
"tup",
"Latn",
}
m["skh"] = {
"Sikule",
3121081,
"poz-nws",
}
m["ski"] = {
"Sika",
33960,
"poz-cet",
"Latn",
}
m["skj"] = { -- compare 'ths'
"Seke",
30226846,
"sit-tam",
}
m["skk"] = {
"Sok",
12953887,
"mkh-ban",
}
m["skm"] = {
"Sakam",
6448517,
"ngf-uru",
"Latn",
}
m["skn"] = {
"Kolibugan Subanon",
18755617,
"phi",
"Latn",
}
m["sko"] = {
"Seko Tengah",
15613270,
"poz",
}
m["skp"] = {
"Sekapan",
7447132,
"poz-bnn",
"Latn",
}
m["skq"] = {
"Sininkere",
3914896,
"dmn-snb",
"Latn",
}
m["skr"] = {
"Saraiki",
33902,
"inc-pan",
"Aran, Mult, Deva",
strip_diacritics = {remove_diacritics = c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.nunghunna},
translit = {
Aran = "pa-Aran-translit",
Deva = "hi-translit",
Mult = "Mult-translit",
},
}
m["sks"] = {
"Maia",
12952760,
"ngf-kau",
"Latn",
}
m["skt"] = {
"Sakata",
36691,
"bnt-bnm",
"Latn",
}
m["sku"] = {
"Sakao",
3298421,
"poz-vnn",
"Latn",
}
m["skv"] = {
"Skou",
3915200,
"paa-isk",
"Latn",
}
m["skw"] = {
"Skepi Creole Dutch",
2522153,
"crp",
"Latn",
ancestors = "nl",
}
m["skx"] = {
"Seko Padang",
15613282,
"poz-ssw",
"Latn",
}
m["sky"] = {
"Sikaiana",
7439242,
"poz-pnp",
"Latn",
}
m["skz"] = {
"Sekar",
7447136,
"poz-cet",
}
m["slc"] = {
"Saliba (Colombia)",
3441097,
nil,
"Latn",
}
m["sld"] = {
"Sisaala",
11020264,
"nic-sis",
"Latn",
}
m["sle"] = {
"Sholaga",
7500203,
"dra-kan",
"Knda",
-- Knda translit in [[Module:scripts/data]]
}
m["slf"] = {
"Swiss-Italian Sign Language",
12953479,
"sgn",
}
m["slg"] = {
"Selungai Murut",
7448844,
"poz-san",
}
-- [slh] Southern Puget Sound Salish is treated as part of [lut] Lushootseed
-- "sli" "Silesian German" IS SUBSUMED INTO "gmw-ecg" "East Central German"
m["slj"] = {
"Salumá",
7406296,
"sai-prk",
"Latn",
}
m["sll"] = {
"Salt-Yui",
7405785,
"ngf-sim",
"Latn",
}
m["slm"] = {
"Pangutaran Sama",
3362086,
"poz-sbj",
"Latn",
}
m["sln"] = {
"Salinan",
1568938,
"qfa-iso",
"Latn",
}
m["slp"] = {
"Lamaholot",
6480777,
"poz-cet",
"Latn",
}
m["slr"] = {
"Salar",
33963,
"trk-ogz",
"Arab, Latn",
ancestors = "trk-eog",
}
m["sls"] = {
"Singapore Sign Language",
7512563,
"sgn",
}
m["slt"] = {
"Sila",
7514021,
"tbq-sil",
}
m["slu"] = {
"Selaru",
7447500,
"poz-cet",
"Latn",
}
m["slw"] = {
"Sialum",
7506694,
"ngf-ehu",
"Latn",
}
m["slx"] = {
"Salampasu",
7403607,
"bnt-lun",
"Latn",
}
m["sly"] = {
"Selayar",
7447520,
"poz-ssw",
}
m["slz"] = {
"Ma'ya",
2291492,
"poz-hce",
"Latn",
}
m["sma"] = {
"Southern Sami",
13293,
"smi",
"Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = "'ˈ"},
sort_key = "sma-sortkey",
}
m["smb"] = {
"Simbari",
7517427,
"ngf-bsi",
"Latn",
}
m["smc"] = {
"Som",
7559081,
"ngf-uru",
"Latn",
}
m["smd"] = {
"Sama",
6407456,
"bnt-kmb",
"Latn",
}
m["smf"] = {
"Auwe",
3502072,
"paa-war",
"Latn",
ancestors = "dnd",
}
m["smg"] = {
"Simbali",
56692,
"paa-bai",
"Latn",
}
m["smh"] = {
"Samei",
7409269,
"tbq-axi",
}
m["smj"] = {
"Lule Sami",
56322,
"smi",
"Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = c.macron .. "'ˈ"},
sort_key = "smj-sortkey",
}
m["smk"] = {
"Bolinao",
2669235,
"phi",
"Latn, Tglg",
}
m["sml"] = {
"Central Sama",
3470593,
"poz-sbj",
"Latn",
}
m["smm"] = {
"Musasa",
6940122,
"inc-bih",
}
m["smn"] = {
"Inari Sami",
33462,
"smi",
"Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = c.dotbelow .. "'ˈ"},
sort_key = "smn-sortkey",
}
m["smp"] = {
"Samaritan Hebrew",
56502,
"sem-can",
"Samr",
translit = "Samr-translit",
-- Samr strip_diacritics, sort_key in [[Module:scripts/data]]
ancestors = "hbo",
}
m["smq"] = {
"Samo",
7409884,
"ngf-est",
"Latn",
}
m["smr"] = {
"Simeulue",
2992833,
"poz-nws",
"Latn",
}
m["sms"] = {
"Skolt Sami",
13271,
"smi",
"Latn",
display_text = {
from = {"'"},
to = {"ˈ"}
},
strip_diacritics = {remove_diacritics = c.dotbelow .. "'ˈ"},
sort_key = "sms-sortkey",
}
m["smt"] = {
"Simte",
7521268,
"tbq-kuk",
}
m["smu"] = {
"Somray",
6583612,
"mkh-pea",
}
m["smv"] = {
"Samvedi",
6345632,
"inc-sou",
}
m["smw"] = {
"Sumbawa",
3182585,
"poz-bss",
"Latn",
}
m["smx"] = {
"Samba",
11120157,
"bnt-pen",
"Latn",
}
m["smy"] = {
"Semnani",
14531212,
"xme",
"Arab, Latn",
}
m["smz"] = {
"Simeku",
7517534,
"paa-sbo",
"Latn",
}
m["snb"] = {
"Sebuyau",
7442836,
"poz-mly",
"Latn",
}
m["snc"] = {
"Sinaugoro",
4170719,
"poz-ocw",
"Latn",
}
m["sne"] = {
"Bau Bidayuh",
2891938,
"day",
"Latn",
}
m["snf"] = {
"Noon",
36304,
"alv-cng",
"Latn",
}
m["sng"] = {
"Sanga (Congo)",
3438316,
"bnt-lub",
"Latn",
}
m["sni"] = {
"Sensi",
7451029,
"sai-pan",
"Latn",
}
m["snj"] = {
"Riverain Sango",
25559751,
"crp",
"Latn",
ancestors = "ngb",
}
m["snk"] = {
"Soninke",
36660,
"dmn-snb",
"Latn",
}
m["snl"] = {
"Sangil",
3472206,
"phi",
"Latn",
}
m["snm"] = {
"Southern Ma'di",
15637273,
"csu-mma",
}
m["snn"] = {
"Siona",
3485116,
"sai-tuc",
"Latn",
}
-- [sno] Snohomish is treated as part of [lut] Lushootseed
m["snp"] = {
"Siane",
7506812,
"ngf-gor",
"Latn",
}
m["snq"] = {
"Sangu (Gabon)",
36609,
"bnt-sir",
"Latn",
}
m["snr"] = {
"Sihan",
7513400,
"ngf-gum",
"Latn",
}
m["sns"] = {
"Nahavaq",
2160435,
"poz-vnc",
"Latn",
}
m["snu"] = {
"Senggi",
7929052,
"paa-war",
"Latn",
}
m["snv"] = {
"Sa'ban",
3474891,
"poz-swa",
"Latn",
}
m["snw"] = {
"Selee",
36272,
"alv-ntg",
"Latn",
}
m["snx"] = {
"Sam",
7408387,
"ngf-min",
"Latn",
}
m["sny"] = {
"Saniyo-Hiyewe",
7418302,
"paa-sep",
"Latn",
}
m["snz"] = {
"Kou",
7525035, -- also 4803639
"ngf-eva",
"Latn",
}
m["soa"] = {
"Thai Song",
7709159,
"tai-swe",
"Tavt, Thai",
--translit = "Tavt-translit",
sort_key = {
from = {"([ꪵꪶꪹꪻꪼ])([ꪀ-ꪯ])", "([เแโใไ])([ก-ฮ])"},
to = {"%2%1", "%2%1"}
},
}
m["sob"] = {
"Sobei",
3121035,
"poz-ocw",
"Latn",
}
m["soc"] = {
"Soko",
7555138,
"bnt-ske",
"Latn",
}
m["sod"] = {
"Songoora",
7561296,
"bnt-lgb",
"Latn",
}
m["soe"] = {
"Songomeno",
5713543,
"bnt-bsh",
"Latn",
}
m["sog"] = {
"Sogdian",
205979,
"ira-sgc",
"Sogd, Mani, Syrc, Sogo",
translit = {
Sogd = "Sogd-translit",
-- Mani translit in [[Module:scripts/data]]
Sogo = "Sogo-translit",
},
}
m["soh"] = {
"Aka (Sudan)",
3450949,
"sdv-eje",
"Latn",
}
m["soi"] = {
"Sonha",
12953890,
"inc-eas",
}
m["sok"] = {
"Sokoro",
3441303,
"cdc-est",
"Latn",
}
m["sol"] = {
"Solos",
3489591,
"poz-ocw",
}
m["soo"] = {
"Nsong",
12953148,
"bnt-bdz",
"Latn",
}
m["sop"] = {
"Songe",
3130911,
"bnt-lbn",
"Latn",
}
m["soq"] = {
"Kanasi",
11732656,
"ngf-dag",
"Latn",
}
m["sor"] = {
"Somrai",
3123566,
"cdc-est",
"Latn",
}
m["sos"] = {
"Seenku",
36274,
"dmn-smg",
}
m["sou"] = {
"Southern Thai",
56508,
"tai-swe",
"Thai",
sort_key = "Thai-sortkey",
}
m["sov"] = {
"Sonsorolese",
13281,
"poz-mic",
"Latn",
}
m["sow"] = {
"Sowanda",
7571845,
"paa-war",
"Latn",
}
m["sox"] = {
"Swo",
36604,
"bnt-mka",
"Latn",
}
m["soy"] = {
"Miyobe",
35913,
"alv-sav",
"Latn",
}
m["soz"] = {
"Temi",
13278,
"bnt-kka",
"Latn",
}
m["spb"] = {
"Sepa (Indonesia)",
18603687,
"poz-cma",
"Latn",
}
m["spc"] = {
"Sapé",
2888158,
nil,
"Latn",
}
m["spd"] = {
"Saep",
7398312,
"ngf-yag",
"Latn",
}
m["spe"] = {
"Sepa (New Guinea)",
7451725,
"poz-ocw",
"Latn",
}
m["spg"] = {
"Sian",
7506806,
"poz-bnn",
}
m["spi"] = {
"Saponi",
3915418,
"paa-rsa",
"Latn",
}
m["spk"] = {
"Sengo",
7450584,
"paa-nnd",
"Latn",
}
m["spl"] = {
"Selepet",
7447917,
"ngf-kab",
"Latn",
}
m["spm"] = {
"Sepen",
4701931,
"paa-rub",
"Latn",
}
m["spn"] = {
"Sanapaná",
3033556,
"sai-mas",
"Latn",
}
m["spo"] = {
"Spokane",
3493704,
"sal",
}
m["spp"] = {
"Supyire",
56284,
"alv-sma",
"Latn",
}
m["spr"] = {
"Saparua",
7420921,
"poz-cma",
"Latn",
}
m["sps"] = {
"Saposa",
3473187,
"poz-ocw",
"Latn",
}
m["spt"] = {
"Spiti Bhoti",
22080879,
"sit-las",
}
m["spu"] = {
"Sapuan",
7421168,
"mkh-ban",
"Latn", -- in the limited documentation that so far exists
}
m["spv"] = {
"Sambalpuri",
6433240,
"inc-eas",
"Orya",
translit = "or-translit",
ancestors = "or",
}
m["spx"] = {
"South Picene",
36688,
"itc-sbl",
"Ital, Latn",
-- Ital translit in [[Module:scripts/data]]
display_text = {
Latn = s["itc-Latn-displaytext"]
},
strip_diacritics = {
Latn = s["itc-Latn-stripdiacritics"],
},
sort_key = {
Latn = s["itc-Latn-sortkey"],
},
}
m["spy"] = {
"Sabaot",
7395896,
"sdv-kln",
}
m["sqa"] = {
"Shama-Sambuga",
3914392,
"nic-kmk",
"Latn",
}
m["sqh"] = {
"Shau",
3913925,
"nic-jer",
"Latn",
}
m["sqk"] = {
"Albanian Sign Language",
4709168,
"sgn",
}
m["sqm"] = {
"Suma",
11008431,
"gba-wes",
}
m["sqn"] = {
"Susquehannock",
3505736,
"iro-nor",
"Latn",
}
m["sqo"] = {
"Sorkhei",
3491964,
"ira-kms",
}
m["sqq"] = {
"Sou",
16979751,
"mkh-ban",
}
m["sqr"] = {
"Siculo-Arabic",
1069489,
"sem-arb",
"Arab",
}
m["sqs"] = {
"Sri Lankan Sign Language",
3915466,
"sgn",
}
m["sqt"] = {
"Soqotri",
13283,
"sem-sar",
"Arab, Latn",
}
m["squ"] = {
"Squamish",
2484579,
"sal",
"Latn",
}
m["sra"] = {
"Saruga",
7424699,
"ngf-han",
"Latn",
}
m["srb"] = {
"Sora",
13284,
"mun",
"Sora, Latn, Orya",
}
m["sre"] = {
"Sara",
33957,
"day",
}
m["srf"] = {
"Nafi",
6958174,
"poz-ocw",
}
m["srg"] = {
"Sulod",
7636489,
"phi",
"Latn",
}
m["srh"] = {
"Sarikoli",
33873,
"ira-shr",
"Latn, Arab, Cyrl",
translit = "srh-translit",
}
m["sri"] = {
"Siriano",
3485264,
"sai-tuc",
"Latn",
}
m["srk"] = {
"Serudung Murut",
7455497,
"poz-san",
}
m["srl"] = {
"Isirawa",
4203802,
"paa-gkw",
"Latn",
}
m["srm"] = {
"Saramaccan",
33779,
"crp",
"Latn",
ancestors = "en, pt",
}
m["srn"] = {
"Sranan Tongo",
33989,
"crp",
"Latn",
ancestors = "en",
}
m["srq"] = {
"Sirionó",
3027953,
"tup-gua",
"Latn",
}
m["srr"] = {
"Serer",
36284,
"alv-fwo",
"Latn",
}
m["srs"] = {
"Tsuut'ina",
20825,
"ath-nor",
"Latn",
}
m["srt"] = {
"Sauri",
7427547,
"paa-egb",
"Latn",
}
m["sru"] = {
"Suruí",
7646993,
"tup",
"Latn",
}
m["srv"] = {
"Waray Sorsogon",
18755610,
"phi",
"Latn",
}
m["srw"] = {
"Serua",
14916905,
"poz-cet",
}
m["srx"] = {
"Sirmauri",
7530505,
"him",
}
m["sry"] = {
"Sera",
7452602,
"poz-ocw",
"Latn",
}
m["srz"] = {
"Shahmirzadi",
12953126,
"ira-msh",
"Arab",
}
m["ssb"] = {
"Southern Sama",
3470594,
"poz-sbj",
"Latn",
}
m["ssc"] = {
"Suba-Simbiti",
7630687,
"bnt-lok",
"Latn",
}
m["ssd"] = {
"Siroi",
10771067,
"ngf-rai",
"Latn",
}
m["sse"] = {
"Balangingi",
2880535,
"poz-sbj",
"Latn",
}
m["ssf"] = {
"Thao",
676492,
"map",
"Latn",
}
m["ssg"] = {
"Seimat",
3182581,
"poz-aay",
"Latn",
}
m["ssh"] = {
"Shihhi Arabic",
56571,
"sem-arb",
"Arab",
strip_diacritics = {
remove_diacritics = c.kashida .. c.fathatan .. c.dammatan .. c.kasratan .. c.fatha .. c.damma .. c.kasra .. c.shadda .. c.sukun .. c.superalef,
from = {u(0x0671)},
to = {u(0x0627)}
},
}
m["ssi"] = {
"Sansi",
3309366,
"inc-nwe",
}
m["ssj"] = {
"Sausi",
7427605,
"ngf-eva",
"Latn",
}
m["ssk"] = {
"Sunam",
11002210,
"sit-kin",
}
m["ssl"] = {
"Western Sisaala",
11154776,
"nic-sis",
"Latn",
}
m["ssm"] = {
"Semnam",
7449713,
"mkh-asl",
"Latn",
}
m["sso"] = {
"Sissano",
7530937,
"poz-ocw",
"Latn",
}
m["ssp"] = {
"Spanish Sign Language",
3100814,
"sgn",
}
m["ssq"] = {
"So'a",
7572120,
"poz-cet",
"Latn",
}
m["ssr"] = {
"Swiss-French Sign Language",
12953483,
"sgn",
}
m["sss"] = {
"Sô",
3082037,
"mkh-kat",
}
m["sst"] = {
"Sinasina",
7521813,
"ngf-sim",
"Latn",
}
m["ssu"] = {
"Susuami",
7649752,
"ngf-woj",
"Latn",
}
m["ssv"] = {
"Shark Bay",
7489783,
"poz-vnn",
"Latn",
}
m["ssx"] = {
"Samberigi",
7409020,
"ngf-sak",
"Latn",
}
m["ssy"] = {
"Saho",
36353,
"cus-eas",
"Latn, Ethi, Arab",
}
m["ssz"] = {
"Sengseng",
7450601,
"poz-ocw",
"Latn",
}
m["stb"] = {
"Northern Subanen",
12953892,
"phi",
"Latn",
}
m["std"] = {
"Sentinelese",
568377,
"qfa-unc", -- presumed Ongan
}
m["ste"] = {
"Liana-Seti",
6539924,
"poz-cma",
}
m["stf"] = {
"Seta",
7456326,
"paa-trr",
"Latn",
}
m["stg"] = {
"Trieng",
22694648,
"mkh-ban",
}
m["sth"] = {
"Shelta",
36705,
"qfa-mix",
"Latn",
ancestors = "ga, en",
}
m["sti"] = {
"Bulo Stieng",
15771431,
"mkh-ban",
"Khmr, Latn",
}
m["stj"] = {
"Matya Samo",
10974879,
"dmn-sam",
"Latn",
}
m["stk"] = {
"Arammba",
3502094,
"paa-ton",
"Latn",
}
m["stm"] = {
"Setaman",
7456333,
"ngf-mok",
"Latn",
}
m["stn"] = {
"Owa",
1324132,
"poz-sls",
"Latn",
}
m["sto"] = {
"Stoney",
3033570,
"sio-dkt",
"Latn",
}
m["stp"] = {
"Southeastern Tepehuan",
12953917,
"azc-pim",
"Latn",
}
m["stq"] = {
"Saterland Frisian",
27154,
"gmw-fri",
"Latn",
}
m["str"] = {
"Saanich",
36444,
"sal",
"Latn",
}
m["sts"] = {
"Shumashti",
33777,
"inc-kun",
"Arab",
}
m["stt"] = {
"Budeh Stieng",
12953891,
"mkh-ban",
}
m["stu"] = {
"Samtao",
25559550,
"mkh-pal",
}
m["stv"] = {
"Silt'e",
33880,
"sem-eth",
"Ethi",
}
m["stw"] = {
"Satawalese",
28477,
"poz-mic",
"Latn",
}
m["sty"] = {
"Siberian Tatar",
4418344,
"trk-kno",
"Cyrl",
}
m["sua"] = {
"Sulka",
7636341,
"qfa-iso", -- Papuan; isolate in Glottolog and Palmer (2018)
"Latn",
}
m["sub"] = {
"Suku",
12953160,
"bnt-yak",
"Latn",
}
m["suc"] = {
"Western Subanon",
16113894,
"phi",
"Latn",
}
m["sue"] = {
"Suena",
7634386,
"ngf-nbi",
"Latn",
}
m["sug"] = {
"Suganga",
7634706,
"ngf-msu",
"Latn",
}
m["sui"] = {
"Suki",
2089984,
"ngf-gsu",
"Latn",
}
m["suk"] = {
"Sukuma",
2638144,
"bnt-tkm",
"Latn",
}
m["suo"] = {
"Bouni",
65043887,
"paa-pio",
"Latn",
}
m["suq"] = {
"Suri",
5364172,
"sdv",
}
m["sur"] = {
"Mwaghavul",
3440486,
"cdc-wst",
"Latn",
}
m["sus"] = {
"Susu",
33990,
"dmn-sya",
"Latn",
}
m["sut"] = {
"Subtiaba",
3915405,
"omq",
"Latn",
}
m["suv"] = {
"Puroik",
56408,
"sit-khb",
"Beng, Deva, Latn",
ancestors = "sit-khp-pro",
}
m["suw"] = {
"Sumbwa",
7637055,
"bnt-glb",
"Latn",
}
m["sux"] = {
"Sumerian",
36790,
"qfa-iso",
"Xsux, Latn",
}
m["suy"] = {
"Suyá",
3505859,
"sai-nje",
"Latn",
}
m["suz"] = {
"Sunwar",
56549,
"sit-kiw",
"Deva, Sunu"
}
m["sva"] = {
"Svan",
34067,
"ccs",
"Geor, Cyrl",
translit = {
Geor = "sva-translit",
},
override_translit = true,
}
m["svb"] = {
"Ulau-Suain",
7878769,
"poz-ocw",
"Latn",
}
m["svc"] = {
"Vincentian Creole English",
3501785,
"crp",
"Latn",
ancestors = "en",
}
m["sve"] = {
"Serili",
7454834,
"poz-tim",
}
m["svk"] = {
"Slovakian Sign Language",
7541557,
"sgn",
}
m["svm"] = {
"Slavomolisano",
36254,
"zls",
"Latn",
ancestors = "sh",
}
m["svs"] = {
"Savosavo",
3130296,
"qfa-dis", -- Papuan; isolate in Glottolog; in the tentative Central Solomons family by Ross (2005) and Pedrós
-- (2015)
"Latn",
}
m["svx"] = {
"Skalvian",
3486125,
"bat-wes",
"Latn",
}
m["swb"] = {
"Maore Comorian",
34075,
"bnt-com",
"Latn",
sort_key = "bnt-com-sortkey",
}
m["swf"] = {
"Sere",
7453056,
"nic-ser",
"Latn",
}
m["swg"] = {
"Swabian",
327274,
"gmw-hgm",
"Latn",
ancestors = "gsw",
}
m["swi"] = {
"Sui",
3112388,
"qfa-kms",
"Latn, Shui, Hani",
sort_key = {Hani = "Hani-sortkey"},
}
m["swj"] = {
"Sira",
36599,
"bnt-sir",
"Latn",
}
m["swl"] = {
"Swedish Sign Language",
36558,
"sgn",
}
m["swm"] = {
"Samosa",
7410037,
"ngf-nwh",
"Latn",
}
m["swn"] = {
"Sokna",
2988323,
"ber",
}
m["swo"] = {
"Shanenawa",
61974839,
"sai-pan",
"Latn",
}
m["swp"] = {
"Suau",
3502368,
"poz-ocw",
}
m["swq"] = {
"Sharwa",
56791,
"cdc-cbm",
"Latn",
}
m["swr"] = {
"Saweru",
3474649,
"paa-ysa",
"Latn",
}
m["sws"] = {
"Seluwasan",
7448845,
"poz-cet",
"Latn",
}
m["swt"] = {
"Sawila",
7428639,
"paa-alp",
"Latn",
}
m["swu"] = {
"Suwawa",
7650588,
"phi",
"Latn",
}
m["sww"] = {
"Sowa",
7571843,
"poz-vnn",
"Latn",
}
m["swx"] = {
"Suruahá",
3114402,
"auf",
}
m["swy"] = {
"Sarua",
56261,
"cdc-est",
"Latn",
}
m["sxb"] = {
"Suba",
33916,
"bnt-lok",
"Latn",
}
m["sxc"] = {
"Sicanian",
36335,
"qfa-unc", -- extinct; lack of data: only names deciphered
"Polyt",
-- Polyt translit, display_text, strip_diacritics, sort_key in [[Module:scripts/data]]
}
m["sxe"] = {
"Sighu",
36431,
"bnt-kel",
"Latn",
}
m["sxg"] = {
"Shixing",
56337,
"sit-nax",
"Latn",
}
m["sxk"] = {
"Southern Kalapuya",
3192122,
"nai-klp",
}
m["sxl"] = {
"Selonian",
36491,
"bat-eas",
"Latn",
}
m["sxm"] = {
"Samre",
6583615,
"mkh-pea",
}
m["sxn"] = {
"Sangir",
25714758,
"phi",
"Latn",
}
m["sxo"] = {
"Sorothaptic",
2762254,
}
m["sxr"] = {
"Saaroa",
716599,
"map",
"Latn",
}
m["sxs"] = {
"Sasaru",
3913384,
"alv-yek",
"Latn",
}
-- "sxu" "Upper Saxon" IS SUBSUMED INTO "gmw-ecg" "East Central German"
m["sxw"] = {
"Saxwe Gbe",
7428892,
"alv-pph",
"Latn",
}
m["sya"] = {
"Siang",
3482903,
}
m["syb"] = {
"Central Subanen",
12953893,
"phi",
"Latn",
}
m["syc"] = {
"Classical Syriac",
33538,
"sem-are",
"Syrc",
strip_diacritics = {remove_diacritics = c.macron .. c.diaer .. c.macronbelow .. u(0x0730) .. "-" .. u(0x0748)},
}
m["syi"] = {
"Seki",
36547,
"bnt-kel",
"Latn",
}
m["syk"] = {
"Sukur",
56292,
"cdc-cbm",
"Latn",
}
m["syl"] = {
"Sylheti",
2044560,
"inc-bas",
"Sylo, Beng",
ancestors = "inc-obn",
translit = "syl-translit",
}
m["sym"] = {
"Maya Samo",
10950421,
"dmn-sam",
"Latn",
}
m["syn"] = {
"Senaya",
33914,
"sem-nna",
}
m["syo"] = {
"Suoy",
7641864,
"mkh-pea",
}
m["sys"] = {
"Sinyar",
56840,
"csu",
"Latn",
}
m["syw"] = {
"Kagate",
12952538,
"sit-kyk",
"Deva",
}
m["syx"] = {
"Osamayi",
7408415,
"bnt-kel",
"Latn",
}
m["syy"] = {
"Al-Sayyid Bedouin Sign Language",
2915457,
"sgn",
}
m["sza"] = {
"Semelai",
3111827,
"mkh-asl",
"Latn",
}
m["szb"] = {
"Ngalum",
11732516,
"ngf-okk",
"Latn",
}
m["szc"] = {
"Semaq Beri",
7449119,
"mkh-asl",
"Latn",
}
m["szd"] = {
"Seru",
7455488,
"poz-bnn",
"Latn",
}
m["sze"] = {
"Seze",
373683,
"omv-mao",
"Latn",
}
m["szg"] = {
"Sengele",
7450555,
"bnt-mon",
"Latn",
}
m["szl"] = {
"Silesian",
30319,
"zlw-lch",
"Latn",
ancestors = "zlw-opl",
}
m["szn"] = {
"Sula",
3503403,
"poz-cma",
"Latn",
}
m["szp"] = {
"Suabo",
7630429,
"ngf-sbh",
"Latn",
}
m["szs"] = {
"Solomon Islands Sign Language",
26449924,
"sgn",
}
m["szv"] = {
"Isubu",
35431,
"bnt-saw",
"Latn",
}
m["szw"] = {
"Sawai",
3447258,
"poz-hce",
"Latn",
}
m["szy"] = {
"Sakizaya",
718269,
"map",
"Latn",
}
return require("Module:languages").finalizeData(m, "language")
4tyniq3gtiv672879n154vrotvnoo1n
Module:etymology/style.css
828
8163
55073
2026-09-30T22:39:19Z
Deadend0914
7211
Gesceop tramet þe hafaþ '.desc-arr[title] { cursor: help } .desc-arr[title="uncertain"] { font-size:.7em; vertical-align: super }'
55073
sanitized-css
text/css
.desc-arr[title] { cursor: help }
.desc-arr[title="uncertain"] { font-size:.7em; vertical-align: super }
g0m44bo1acdws3qgtcpwoxx5bbrst6w
Bysen:enm-noun
10
8164
55074
2026-09-30T22:40:13Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:checkparams|error}}<!-- Validate template parameters -->{{head|enm|noun|head={{{head|}}}|sort={{{sort|}}}|var={{{var|}}}|g={{{g|}}}|g2={{{g2|}}}|g3={{{g3|}}}}}<!-- --> (<!-- -->{{#ifeq:{{{1}}}|-<!-- -->|<!-- -->''{{glossary|uncountable}}''{{cln|enm|uncountable nouns}}<!-- -->|{{#ifeq:{{{1}}}|singular only |<!-- -->''{{glossary|singular only}}''{{cln|enm|singularia tantum}}<!-- -->|<!-- -->''plural'' <!-- -->'''{{l-self|enm|{{{pl|{{{...'
55074
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{head|enm|noun|head={{{head|}}}|sort={{{sort|}}}|var={{{var|}}}|g={{{g|}}}|g2={{{g2|}}}|g3={{{g3|}}}}}<!--
--> (<!--
-->{{#ifeq:{{{1}}}|-<!--
-->|<!--
-->''{{glossary|uncountable}}''{{cln|enm|uncountable nouns}}<!--
-->|{{#ifeq:{{{1}}}|singular only
|<!--
-->''{{glossary|singular only}}''{{cln|enm|singularia tantum}}<!--
-->|<!--
-->''plural'' <!--
-->'''{{l-self|enm|{{{pl|{{{1|{{pagename}}s}}}}}}|accel-form=p}}'''<!--
-->{{#if:{{{pl2|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl2_qual|}}}<!--
-->|<!--
-->{{q|{{{pl2_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl2}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl3|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl3_qual|}}}<!--
-->|<!--
-->{{q|{{{pl3_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl3}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl4|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl4_qual|}}}<!--
-->|<!--
-->{{q|{{{pl4_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl4}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl5|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl5_qual|}}}<!--
-->|<!--
-->{{q|{{{pl5_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl5}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl6|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl6_qual|}}}<!--
-->|<!--
-->{{q|{{{pl6_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl6}}}|accel-form=p}}'''<!--
-->}}}}<!--
-->}}<!--
-->{{#if:{{{gen|}}}<!--
-->|<!--
-->, ''genitive singular'' <!--
-->'''{{l-self|enm|{{{gen}}}|accel-form=gen{{!}}s}}'''<!--
-->{{#if:{{{gen2|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen2}}}|accel-form=gen{{!}}s}}'''<!--
-->}}<!--
-->{{#if:{{{gen3|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen3}}}|accel-form=gen{{!}}s}}'''<!--
-->}}<!--
-->}}<!--
-->{{#if:{{{dat|}}}<!--
-->|<!--
-->, ''dative singular'' <!--
-->'''{{l-self|enm|{{{dat}}}|accel-form=dat{{!}}s}}'''<!--
-->{{#if:{{{dat2|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{dat2}}}|accel-form=dat{{!}}s}}'''<!--
-->}}<!--
-->{{#if:{{{dat3|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{dat3}}}|accel-form=dat{{!}}s}}'''<!--
-->}}<!--
-->}}<!--
-->{{#if:{{{gen_pl|}}}<!--
-->|<!--
-->, ''genitive plural'' <!--
-->'''{{l-self|enm|{{{gen_pl}}}|accel-form=gen{{!}}p}}'''<!--
-->{{#if:{{{gen_pl2|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen_pl2}}}|accel-form=gen{{!}}p}}'''<!--
-->}}<!--
-->{{#if:{{{gen_pl3|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen_pl3}}}|accel-form=gen{{!}}p}}'''<!--
-->}}<!--
-->}}<!--
-->)<!--
--><noinclude>{{documentation}}</noinclude>
dxzn2calmp1edw9sxva4fkkqmbxh2ow
55085
55074
2026-09-30T22:56:27Z
Deadend0914
7211
55085
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{head|enm|noun|head={{{head|}}}|sort={{{sort|}}}|var={{{var|}}}|g={{{g|}}}|g2={{{g2|}}}|g3={{{g3|}}}}}<!--
--> (<!--
-->{{#ifeq:{{{1}}}|-<!--
-->|<!--
-->''{{glossary|untellendlīc}}''{{cln|enm|uncountable nouns}}<!--
-->|{{#ifeq:{{{1}}}|singular only
|<!--
-->''{{glossary|ānlīc anfeald}}''{{cln|enm|singularia tantum}}<!--
-->|<!--
-->''mnf.'' <!--
-->'''{{l-self|enm|{{{pl|{{{1|{{pagename}}s}}}}}}|accel-form=p}}'''<!--
-->{{#if:{{{pl2|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl2_qual|}}}<!--
-->|<!--
-->{{q|{{{pl2_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl2}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl3|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl3_qual|}}}<!--
-->|<!--
-->{{q|{{{pl3_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl3}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl4|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl4_qual|}}}<!--
-->|<!--
-->{{q|{{{pl4_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl4}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl5|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl5_qual|}}}<!--
-->|<!--
-->{{q|{{{pl5_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl5}}}|accel-form=p}}'''<!--
-->}}<!--
-->{{#if:{{{pl6|}}}<!--
-->|<!--
--> ''or'' {{#if:{{{pl6_qual|}}}<!--
-->|<!--
-->{{q|{{{pl6_qual}}}}}<!--
--> <!--
-->}}<!--
-->'''{{l-self|enm|{{{pl6}}}|accel-form=p}}'''<!--
-->}}}}<!--
-->}}<!--
-->{{#if:{{{gen|}}}<!--
-->|<!--
-->, ''genitive singular'' <!--
-->'''{{l-self|enm|{{{gen}}}|accel-form=gen{{!}}s}}'''<!--
-->{{#if:{{{gen2|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen2}}}|accel-form=gen{{!}}s}}'''<!--
-->}}<!--
-->{{#if:{{{gen3|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen3}}}|accel-form=gen{{!}}s}}'''<!--
-->}}<!--
-->}}<!--
-->{{#if:{{{dat|}}}<!--
-->|<!--
-->, ''dative singular'' <!--
-->'''{{l-self|enm|{{{dat}}}|accel-form=dat{{!}}s}}'''<!--
-->{{#if:{{{dat2|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{dat2}}}|accel-form=dat{{!}}s}}'''<!--
-->}}<!--
-->{{#if:{{{dat3|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{dat3}}}|accel-form=dat{{!}}s}}'''<!--
-->}}<!--
-->}}<!--
-->{{#if:{{{gen_pl|}}}<!--
-->|<!--
-->, ''genitive plural'' <!--
-->'''{{l-self|enm|{{{gen_pl}}}|accel-form=gen{{!}}p}}'''<!--
-->{{#if:{{{gen_pl2|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen_pl2}}}|accel-form=gen{{!}}p}}'''<!--
-->}}<!--
-->{{#if:{{{gen_pl3|}}}<!--
-->|<!--
--> ''or'' <!--
-->'''{{l-self|enm|{{{gen_pl3}}}|accel-form=gen{{!}}p}}'''<!--
-->}}<!--
-->}}<!--
-->)<!--
--><noinclude>{{documentation}}</noinclude>
c6op0nte8pskvn2defddsxhr4hvxamt
Bysen:l-self
10
8165
55075
2026-09-30T22:40:38Z
Deadend0914
7211
Gesceop tramet þe hafaþ '<includeonly><onlyinclude>{{safesubst:<noinclude/>#invoke:links/templates|l_term_t|notself=1}}</onlyinclude></includeonly><!-- -->{{l-self|en|term}}{{documentation}}'
55075
wikitext
text/x-wiki
<includeonly><onlyinclude>{{safesubst:<noinclude/>#invoke:links/templates|l_term_t|notself=1}}</onlyinclude></includeonly><!--
-->{{l-self|en|term}}{{documentation}}
jhwc06ur1a5nmigfv5a7izr06tlqmpk
Module:links/templates
828
8166
55076
2026-09-30T22:41:02Z
Deadend0914
7211
Gesceop tramet þe hafaþ '-- Prevent substitution. if mw.isSubsting() then return require("Module:unsubst") end local export = {} local links_module = "Module:links" local process_params = require("Module:parameters").process local remove = table.remove local upper = require("Module:string utilities").upper --[=[ Modules used: [[Module:links]] [[Module:languages]] [[Module:scripts]] [[Module:parameters]] [[Module:debug]] ]=] do local function get_args(frame) -- `compat` is a...'
55076
Scribunto
text/plain
-- Prevent substitution.
if mw.isSubsting() then
return require("Module:unsubst")
end
local export = {}
local links_module = "Module:links"
local process_params = require("Module:parameters").process
local remove = table.remove
local upper = require("Module:string utilities").upper
--[=[
Modules used:
[[Module:links]]
[[Module:languages]]
[[Module:scripts]]
[[Module:parameters]]
[[Module:debug]]
]=]
do
local function get_args(frame)
-- `compat` is a compatibility mode for {{term}}.
-- If given a nonempty value, the function uses lang= to specify the
-- language, and all the positional parameters shift one number lower.
local iargs = frame.args
iargs.compat = iargs.compat and iargs.compat ~= ""
iargs.langname = iargs.langname and iargs.langname ~= ""
iargs.notself = iargs.notself and iargs.notself ~= ""
local alias_of_4 = {alias_of = 4}
local boolean = {type = "boolean"}
local params = {
[1] = {required = true, type = "language", default = "und"},
[2] = true,
[3] = true,
[4] = true,
g = {list = true, type = "genders", flatten = true},
gloss = alias_of_4,
id = true,
lit = true,
ng = true,
pos = true,
sc = {type = "script"},
t = alias_of_4,
tr = true,
ts = true,
q = {type = "qualifier"},
qq = {type = "qualifier"},
l = {type = "labels"},
ll = {type = "labels"},
ref = {type = "references"},
["accel-form"] = true,
["accel-translit"] = true,
["accel-lemma"] = true,
["accel-lemma-translit"] = true,
["accel-gender"] = true,
["accel-nostore"] = boolean,
}
if iargs.compat then
params.lang = {type = "language", default = "und"}
remove(params, 1)
alias_of_4.alias_of = 3
end
if iargs.langname then
params.w = boolean
end
return process_params(frame:getParent().args, params), iargs
end
-- Used in [[Template:l]] and [[Template:m]].
function export.l_term_t(frame)
local args, iargs = get_args(frame)
local compat = iargs.compat
local lang = args[compat and "lang" or 1]
-- Tracking for und.
if not compat and lang:getCode() == "und" then
require("Module:debug").track("link/und")
end
local term = args[(compat and 1 or 2)]
local alt = args[(compat and 2 or 3)]
term = term ~= "" and term or nil
if not term and not alt and iargs.demo then
term = iargs.demo
end
local langname = iargs.langname and (
args.w and lang:makeWikipediaLink() or
lang:getCanonicalName()
) or nil
if langname and term == "-" then
return langname
end
-- Forward the information to full_link
return (langname and langname .. " " or "") .. require(links_module).full_link(
{
lang = lang,
sc = args.sc,
track_sc = true,
term = term,
alt = alt,
gloss = args[4],
id = args.id,
tr = args.tr,
ts = args.ts,
genders = args.g,
pos = args.pos,
ng = args.ng,
lit = args.lit,
q = args.q,
qq = args.qq,
l = args.l,
ll = args.ll,
refs = args.ref,
show_decorations = true,
accel = args["accel-form"] and {
form = args["accel-form"],
translit = args["accel-translit"],
lemma = args["accel-lemma"],
lemma_translit = args["accel-lemma-translit"],
gender = args["accel-gender"],
nostore = args["accel-nostore"],
} or nil
},
iargs.face,
not iargs.notself
)
end
-- Used in [[Template:link-annotations]].
function export.l_annotations_t(frame)
local args, iargs = get_args(frame)
-- Forward the information to format_link_annotations
return require(links_module).format_link_annotations(
{
lang = args[1],
tr = { args.tr },
ts = { args.ts },
genders = args.g,
pos = args.pos,
ng = args.ng,
lit = args.lit
},
iargs.face
)
end
end
-- Used in [[Template:ll]].
do
local function get_args(frame)
return process_params(frame:getParent().args, {
[1] = {required = true, type = "language", default = "und"},
[2] = {allow_empty = true},
[3] = true,
id = true,
sc = {type = "script"},
})
end
function export.ll(frame)
local args = get_args(frame)
local lang = args[1]
local sc = args.sc
local term = args[2]
term = term ~= "" and term or nil
return require(links_module).language_link{
lang = lang,
sc = sc,
term = term,
alt = args[3],
id = args.id
} or "<small>[Term?]</small>" ..
require("Module:utilities").format_categories(
{lang:getFullName() .. " term requests"},
lang, "-", nil, nil, sc
)
end
end
function export.def_t(frame)
local args = process_params(frame:getParent().args, {
[1] = {required = true, default = ""},
})
local face = frame.args.face
local ret = require("Module:script utilities").tag_definition(require(links_module).embedded_language_links{
term = args[1],
lang = require("Module:languages").getByCode("en"),
sc = require("Module:scripts").getByCode("Latn")
}, face)
if face == "non-gloss" then
return ret
end
return '<span class="mention-gloss-paren">(</span>' .. ret .. '<span class="mention-gloss-paren">)</span>'
end
function export.linkify_t(frame)
local args = process_params(frame:getParent().args, {
[1] = {required = true, default = ""},
})
args[1] = mw.text.trim(args[1])
if args[1] == "" or args[1]:find("[[", nil, true) then
return args[1]
end
return "[[" .. args[1] .. "]]"
end
function export.cap_t(frame)
local args = process_params(frame:getParent().args, {
[1] = {required = true},
[2] = true,
lang = {type = "language", default = "en"},
})
local term = args[1]
return require(links_module).full_link{
lang = args.lang,
term = term,
alt = term:gsub("^.[\128-\191]*", upper) .. (args[2] or "")
}
end
function export.section_link_t(frame)
local args = process_params(frame:getParent().args, {
[1] = {},
})
return require(links_module).section_link(args[1])
end
return export
notqdjaat0bxru1ch2nr8ozilppfmpp
Bysen:glossary
10
8167
55077
2026-09-30T22:41:32Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:glossary|link}}<!-- --><noinclude> {{documentation}} </noinclude>'
55077
wikitext
text/x-wiki
{{#invoke:glossary|link}}<!--
--><noinclude>
{{documentation}}
</noinclude>
2gfc2bdgt00us8bb4kn5xfj7lxil6dj
Module:glossary
828
8168
55078
2026-09-30T22:41:53Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local anchors_module = "Module:anchors" local glossary_data_module = "Module:glossary/data" local parameters_module = "Module:parameters" local string_nowiki_module = "Module:string/nowiki" local anchor_encode = mw.uri.anchorEncode local require = require --[==[ Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenienc...'
55078
Scribunto
text/plain
local export = {}
local anchors_module = "Module:anchors"
local glossary_data_module = "Module:glossary/data"
local parameters_module = "Module:parameters"
local string_nowiki_module = "Module:string/nowiki"
local anchor_encode = mw.uri.anchorEncode
local require = require
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function normalize_anchor(...)
normalize_anchor = require(anchors_module).normalize_anchor
return normalize_anchor(...)
end
local function nowiki(...)
nowiki = require(string_nowiki_module)
return nowiki(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local data
local function get_data()
data, get_data = mw.loadData(glossary_data_module), nil
return data
end
function export.def(frame)
local required_default_ = {required = true, default = ""}
local args = process_params(frame:getParent().args, {
[1] = required_default_,
[2] = required_default_,
})
local term = args[1]
return "; <span id=\"" .. anchor_encode(term) .. "\">" .. term .. "</span>\n: " .. args[2]
end
local function get_link(formatted_anchor, text, anchor)
return "[[Appendix:Glossary#" .. nowiki(formatted_anchor) .. "|" .. (text or anchor) .. "]]"
end
-- TODO: make this externally-callable.
function export.link(frame)
local args = process_params(frame:getParent().args, {
[1] = {required = true, default = ""},
[2] = true,
})
local anchor, text = args[1], args[2]
local formatted_anchor = normalize_anchor(anchor)
if (data or get_data())[formatted_anchor] then
return get_link(formatted_anchor, text, anchor)
end
local lower_anchor = anchor:ulower()
formatted_anchor = normalize_anchor(lower_anchor)
local link = get_link(formatted_anchor, text, anchor)
if data[formatted_anchor] or formatted_anchor == "" then
return link
end
mw.log("The anchor " .. lower_anchor
.. (lower_anchor ~= anchor and " or " .. anchor or "")
.. " does not exist in Appendix:Glossary.")
return link
.. "[[Category:Pages linking to anchors not found in Appendix:Glossary|"
.. lower_anchor .. "]]"
end
return export
08kgb6yt06e00j6kl77b3kn5idr7nal
Module:glossary/data
828
8169
55079
2026-09-30T22:42:22Z
Deadend0914
7211
Gesceop tramet þe hafaþ '-- [[Module:glossary]] and [[Module:get IDs]] both process anchor strings with -- normalize_anchor, which handles character escapes, spaces, underscores etc. return require("Module:table/listToSet")(require("Module:get IDs").get("Appendix:Glossary"))'
55079
Scribunto
text/plain
-- [[Module:glossary]] and [[Module:get IDs]] both process anchor strings with
-- normalize_anchor, which handles character escapes, spaces, underscores etc.
return require("Module:table/listToSet")(require("Module:get IDs").get("Appendix:Glossary"))
r80o7ymm49ef5njwjnzw2jqshvfp4nr
Module:get IDs
828
8170
55080
2026-09-30T22:42:44Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local anchors_module = "Module:anchors" local parameters_module = "Module:parameters" local string_nowiki_module = "Module:string/nowiki" local string_utilities_module = "Module:string utilities" local table_module = "Module:table" local template_parser_module = "Module:template parser" local anchor_encode = mw.uri.anchorEncode local byte = string.byte local concat = table.concat local format = string.format local get -- Defined below. local g...'
55080
Scribunto
text/plain
local export = {}
local anchors_module = "Module:anchors"
local parameters_module = "Module:parameters"
local string_nowiki_module = "Module:string/nowiki"
local string_utilities_module = "Module:string utilities"
local table_module = "Module:table"
local template_parser_module = "Module:template parser"
local anchor_encode = mw.uri.anchorEncode
local byte = string.byte
local concat = table.concat
local format = string.format
local get -- Defined below.
local gsub = string.gsub
local lower = string.lower
local new_title = mw.title.new
local pcall = pcall
local require = require
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function class_else_type(...)
class_else_type = require(template_parser_module).class_else_type
return class_else_type(...)
end
local function decode_entities(...)
decode_entities = require(string_utilities_module).decode_entities
return decode_entities(...)
end
local function normalize_anchor(...)
normalize_anchor = require(anchors_module).normalize_anchor
return normalize_anchor(...)
end
local function nowiki(...)
nowiki = require(string_nowiki_module)
return nowiki(...)
end
local function parse(...)
parse = require(template_parser_module).parse
return parse(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
--[==[
Loaders for objects, which load data (or some other object) into some variable, which can then be accessed as "foo or get_foo()", where the function get_foo sets the object to "foo" and then returns it. This ensures they are only loaded when needed, and avoids the need to check for the existence of the object each time, since once "foo" has been set, "get_foo" will not be called again.]==]
local parameters_data
local function get_parameters_data()
parameters_data, get_parameters_data = mw.loadData("Module:parameters/data"), nil
return parameters_data
end
local template_names
local function get_template_names()
template_names = require(table_module).listToSet{
"anchor",
"etymid",
"etymon",
"senseid",
"trans-see",
"trans-top",
"trans-top-also",
"transclude"
}
get_template_names = nil
return template_names
end
local function handle_heading(name, seen, ids)
-- Repeated IDs are disambiguated by appending _N, where N is an
-- incremented integer. When determining this, ASCII characters are case-
-- insensitive, but all other characters are case-sensitive.
local check = lower(name)
if seen[check] then
local i, name_lower = 1, check
repeat
i = i + 1
check = format("%s_%d", name_lower, i)
until not seen[check]
name = format("%s_%d", name, i)
end
seen[check] = true
ids[#ids + 1] = name
end
local function pseudo_percent_encode(ch)
return format(".%02X", byte(ch))
end
local function handle_template(name, node, ids)
local args, success = node:get_arguments()
success, args = pcall(process_params, args, (parameters_data or get_parameters_data())[name])
if not success then
return
elseif name == "anchor" then
args = args[1]
for i = 1, #args do
ids[#ids + 1] = normalize_anchor(args[i])
end
return
end
local id1, id2
if name == "trans-top" or name == "trans-see" or name == "trans-top-also" then
id1 = "Translations-"
if name == "trans-top" then
id2 = args.id or args[1]
else
id2 = args.id.default or args[1]
end
else
id1 = args[1]:getCanonicalName() .. ": "
if name == "etymon" or name == "transclude" then
id2 = args["id"]
else
id2 = args[2]
end
end
if id2 then
ids[#ids + 1] = normalize_anchor(id1 .. id2)
end
end
function export.get(pagename, force_transclusion)
local page, seen, ids = new_title(pagename), {}, {}
for node in parse((page.redirectTarget or page):getContent() or error("Could not retrieve the page content of \"" .. pagename .. "\".")):iterate_nodes() do
local node_class = class_else_type(node)
if node_class == "heading" then
local name = node:get_anchor()
if name ~= nil then
handle_heading(name, seen, ids)
-- Headings get a fallback ID, which uses pseudo-percent-encoding
-- where "%" is replaced by "." (e.g. "!" is ".21").
local name2 = gsub(name, "[^%w%-.:_]", pseudo_percent_encode)
if name2 ~= name then
handle_heading(name2, seen, ids)
end
end
elseif node_class == "template" then
local name = node:get_name(nil, force_transclusion)
if (template_names or get_template_names())[name] then
handle_template(name, node, ids)
end
end
end
return ids
end
get = export.get
-- Used by [[MediaWiki:Gadget-OrangeLinks.js]].
function export.show(frame)
local output = {}
for i, pagename in ipairs(frame.args) do
output[i] = nowiki(concat(get(pagename, true), " "))
end
return concat(output, "\n\n")
end
return export
hj1g7rdgmd847d41eflsi8p4spx5anb
Bysen:lg
10
8171
55081
2026-09-30T22:49:08Z
Deadend0914
7211
Redirected page to [[Bysen:glossary]]
55081
wikitext
text/x-wiki
#REDIRECT [[Template:glossary]]
b2vde0oz3rhjfdc3n0td64qkis98kir
Module:string/nowiki
828
8172
55082
2026-09-30T22:49:38Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local table_list_to_set_module = "Module:table/listToSet" local byte = string.byte local char = string.char local find = string.find local format = string.format local gsub = string.gsub local lower = string.lower local sub = string.sub local function list_to_set(...) list_to_set = require(table_list_to_set_module) return list_to_set(...) end local function escape_byte(_, b) return format("&#%d;", b) end local absolute local function get_absolute() absolu...'
55082
Scribunto
text/plain
local table_list_to_set_module = "Module:table/listToSet"
local byte = string.byte
local char = string.char
local find = string.find
local format = string.format
local gsub = string.gsub
local lower = string.lower
local sub = string.sub
local function list_to_set(...)
list_to_set = require(table_list_to_set_module)
return list_to_set(...)
end
local function escape_byte(_, b)
return format("&#%d;", b)
end
local absolute
local function get_absolute()
absolute, get_absolute = list_to_set({"\"", "&", "'", ";", "<", "=", ">", "[", "]", "{", "|", "}"}, function(_, ch)
return format("&#%d;", byte(ch))
end), nil
return absolute
end
local after_newline
local function get_after_newline()
after_newline, get_after_newline = list_to_set({0x9, 0xA, 0xD, 0x20, 0x21, 0x23, 0x2A, 0x3A}, escape_byte), nil
return after_newline
end
local scheme_chars, unused_scheme_chars, uri_schemes
local function get_scheme_chars()
local _uri_schemes = {"bitcoin", "geo", "magnet", "mailto", "matrix", "news", "sip", "sips", "sms", "tel", "urn", "xmpp"}
uri_schemes, get_scheme_chars = list_to_set(_uri_schemes), nil
_uri_schemes = table.concat(_uri_schemes)
-- Add alphanumeric characters used in a listed scheme to `scheme_chars`,
-- and those that aren't to `unused_scheme_chars`.
scheme_chars, unused_scheme_chars = {}, {}
for i = 0x61, 0x7A do
local chars = find(_uri_schemes, char(i), nil, true) and scheme_chars or unused_scheme_chars
chars[i] = true
chars[i - 0x20] = true
end
unused_scheme_chars[0x5F] = true -- _
return scheme_chars
end
local after_magic_link
local function get_after_magic_link()
after_magic_link, get_after_magic_link = list_to_set({0x9, 0xA, 0xC, 0xD, 0x20}, escape_byte), nil
return after_magic_link
end
-- Temporary variables. No risk of collisions across stack levels, since there's
-- no way for nowiki() to be called recursively.
local _str, esc, esc_next, esc_next2
local function nowiki(loc, ch)
if esc_next ~= nil then
esc, esc_next, esc_next2 = esc_next, esc_next2, nil
return esc or nil
end
esc = (absolute or get_absolute())[ch]
if esc then
return esc
elseif ch == "\n" or ch == "\r" then
esc_next = (after_newline or get_after_newline())[byte(_str, loc + 1)]
elseif ch == "!" then
if byte(_str, loc + 1) == 0x21 then -- !
esc_next = "!"
end
elseif ch == ":" then
if sub(_str, loc + 1, loc + 2) == "//" then
return ":"
end
local n, b = 0
repeat
n = n + 1
b = byte(_str, loc - n)
until not (scheme_chars or get_scheme_chars())[b]
-- Abort on an unused scheme character, as it can't be a scheme.
if unused_scheme_chars[b] then
return
-- Otherwise, check if the the word before the colon matches a scheme.
elseif uri_schemes[lower(sub(_str, loc - n + 1, loc - 1))] then
return ":"
end
elseif ch == "-" then
local prev = byte(_str, loc - 1)
if (prev == 0xA or prev == 0xD) and sub(_str, loc + 1, loc + 3) == "---" then
return "-"
end
elseif ch == "I" then
local esc_sp = (after_magic_link or get_after_magic_link())[byte(_str, loc + 4)]
if esc_sp and sub(_str, loc + 1, loc + 3) == "SBN" then
esc_next = esc_sp
end
elseif ch == "P" then
local esc_sp = (after_magic_link or get_after_magic_link())[byte(_str, loc + 4)]
if esc_sp and sub(_str, loc + 1, loc + 3) == "MID" then
esc_next, esc_next2 = false, esc_sp -- to avoid escaping "I" in "PMID"
end
elseif ch == "R" then
local esc_sp = (after_magic_link or get_after_magic_link())[byte(_str, loc + 3)]
if esc_sp and sub(_str, loc + 1, loc + 2) == "FC" then
esc_next = esc_sp
end
elseif ch == "_" then
if byte(_str, loc + 1) == 0x5F then -- _
esc_next = "_"
end
elseif ch == "~" then
if sub(_str, loc + 1, loc + 2) == "~~" then
esc_next, esc_next2 = false, "~"
end
end
end
local first
local function get_first()
first, get_first = list_to_set({0x9, 0xA, 0xD, 0x20, 0x21, 0x23, 0x2A, 0x2B, 0x2D, 0x3A, 0x5F, 0x7E}, escape_byte), nil
return first
end
local last
local function get_last()
last, get_last = list_to_set({0x9, 0xA, 0xC, 0xD, 0x20, 0x21, 0x3A, 0x5F, 0x7E}, escape_byte), nil
return last
end
return function(str)
if #str == 0 then
return str
end
_str, esc, esc_next, esc_next2 = str, nil, nil, nil
str = gsub(str, "()([%s!\"#&'*%-:;<=>IPR[%]_{|}~])", nowiki)
local b = byte(str, 1)
local esc_first = (first or get_first())[b]
if #str == 1 then
return esc_first or (last or get_last())[b] or str
end
local esc_last = (last or get_last())[byte(str, -1)]
if esc_first then
if esc_last then
return esc_first .. sub(str, 2, -2) .. esc_last
end
return esc_first .. sub(str, 2)
elseif esc_last then
return sub(str, 1, -2) .. esc_last
end
return str
end
iqdxq0iiw3rpvozlhmhirkfu3sj92s0
Appendix:Glossary
0
8173
55083
2026-09-30T22:52:35Z
Deadend0914
7211
Gesceop tramet þe hafaþ '==0-9== ; 1st hād : Seoh '''{{lg|forma had}}''' ; 2nd hād : Seoh '''{{lg|oþer had}}''' ; 3rd hād : Seoh '''{{lg|þridda had}}''''
55083
wikitext
text/x-wiki
==0-9==
; 1st hād
: Seoh '''{{lg|forma had}}'''
; 2nd hād
: Seoh '''{{lg|oþer had}}'''
; 3rd hād
: Seoh '''{{lg|þridda had}}'''
d2w9x47hcom1218yp86q5qkj5a15ooz
deth
0
8174
55084
2026-09-30T22:52:48Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|enm}}== ===Rihtstefn=== * IPA: /dɛ̝ːθ/ ===Wordstǣr=== {{inh+|enm|ang|dēaþ}}, from {{inh|enm|gmw-pro|*dauþu}}, from {{inh|enm|gem-pro|*dauþuz}}. ===Nama=== {{enm-noun|pl=es|-}} ===Tudru=== * {{desctree|en|death|qq=obsolete {{en|deth}}|noalts=1}} * {{desc|gmw-msc|dethe|deith|deth}} ** {{desc|sco|daeth|daith|death|deeth|deith}}'
55084
wikitext
text/x-wiki
=={{sprǣc|enm}}==
===Rihtstefn===
* IPA: /dɛ̝ːθ/
===Wordstǣr===
{{inh+|enm|ang|dēaþ}}, from {{inh|enm|gmw-pro|*dauþu}}, from {{inh|enm|gem-pro|*dauþuz}}.
===Nama===
{{enm-noun|pl=es|-}}
===Tudru===
* {{desctree|en|death|qq=obsolete {{en|deth}}|noalts=1}}
* {{desc|gmw-msc|dethe|deith|deth}}
** {{desc|sco|daeth|daith|death|deeth|deith}}
lbae2fr3q8dvqyaj0tyly957gh8ui66
Bysen:ang-decl-noun-o-f
10
8175
55087
2026-09-30T23:30:55Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:checkparams|error}}<!-- Validate template parameters -->{{ang-decl-noun<!-- -->|type=strang ''ō''-stefn<!-- -->|1={{{nomsg|{{{1}}}{{#if:{{{short|}}}|u}}}}}<!-- -->|3={{{1}}}e<!-- -->|5={{{1}}}e<!-- -->|7={{{1}}}e<!-- -->|2={{{1}}}a,{{{1}}}e<!-- -->|4={{{1}}}a,{{{1}}}e<!-- -->|6={{{1}}}a<!-- -->|8={{{1}}}um<!-- -->|num={{{num|}}}<!-- -->|title={{{title|}}}<!-- -->}}<!-- -->{{cln|ang|ō-stem nouns}}<!-- --><noinclude>{{documentation}}</noinclude>'
55087
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{ang-decl-noun<!--
-->|type=strang ''ō''-stefn<!--
-->|1={{{nomsg|{{{1}}}{{#if:{{{short|}}}|u}}}}}<!--
-->|3={{{1}}}e<!--
-->|5={{{1}}}e<!--
-->|7={{{1}}}e<!--
-->|2={{{1}}}a,{{{1}}}e<!--
-->|4={{{1}}}a,{{{1}}}e<!--
-->|6={{{1}}}a<!--
-->|8={{{1}}}um<!--
-->|num={{{num|}}}<!--
-->|title={{{title|}}}<!--
-->}}<!--
-->{{cln|ang|ō-stem nouns}}<!--
--><noinclude>{{documentation}}</noinclude>
fec77elb369wfipkafvdzcdhkfu40yc
worulddǣl
0
8176
55092
2026-10-01T00:02:57Z
Deadend0914
7211
Gesceop tramet þe hafaþ '=={{sprǣc|ang}}== ===Wordstǣr=== * Of ''[[woruld]]'' and ''[[dǣl]]'' ===Werlic Nama=== # (gelīcaþ nīweword) þā micle [[dǣl|dǣlas]] of landum, wiþ þā [[sǣ]] ===Wendunga=== * Nīwenglisc: [[continent]]'
55092
wikitext
text/x-wiki
=={{sprǣc|ang}}==
===Wordstǣr===
* Of ''[[woruld]]'' and ''[[dǣl]]''
===Werlic Nama===
# (gelīcaþ nīweword) þā micle [[dǣl|dǣlas]] of landum, wiþ þā [[sǣ]]
===Wendunga===
* Nīwenglisc: [[continent]]
sy8bhmw9i3l23rnap2z0uqx64vtiwel
55093
55092
2026-10-01T00:03:08Z
Deadend0914
7211
/* Werlic Nama */
55093
wikitext
text/x-wiki
=={{sprǣc|ang}}==
===Wordstǣr===
* Of ''[[woruld]]'' and ''[[dǣl]]''
===Werlic Nama===
# ''(gelīcaþ nīweword)'' þā micle [[dǣl|dǣlas]] of landum, wiþ þā [[sǣ]]
===Wendunga===
* Nīwenglisc: [[continent]]
drd6wzz95p0u7tbeifvvo80k1xcim8z
55094
55093
2026-10-01T00:04:05Z
Deadend0914
7211
/* {{sprǣc|ang}} */
55094
wikitext
text/x-wiki
=={{sprǣc|ang}}==
===Wordstǣr===
* Of ''[[woruld]]'' and ''[[dǣl]]''
===Werlic Nama===
{{ang-noun|m|head=worulddǣl}}
# ''(gelīcaþ nīweword)'' þā micle [[dǣl|dǣlas]] of landum, wiþ þā [[sǣ]]
====Declīnung====
{{ang-decl-noun-i-m|dǣl}}
===Wendunga===
* Nīwenglisc: [[continent]]
3yl22rjgkgor200p90j4fo16px24yh7
55097
55094
2026-10-01T00:05:41Z
Deadend0914
7211
/* Werlic Nama */
55097
wikitext
text/x-wiki
=={{sprǣc|ang}}==
===Wordstǣr===
* Of ''[[woruld]]'' and ''[[dǣl]]''
===Werlic Nama===
{{ang-noun|m|head=worulddǣl}}
# ''(gelīcaþ nīweword)'' þā micle [[dǣl|dǣlas]] of landum, wiþ þā [[sǣ]]
====Declīnung====
{{ang-decl-noun-i-m|worulddǣl}}
===Wendunga===
* Nīwenglisc: [[continent]]
e2nsj5ur61n9sina9hnjaq3omxh5331
Bysen:ang-decl-noun-i-m
10
8177
55095
2026-10-01T00:04:27Z
Deadend0914
7211
Gesceop tramet þe hafaþ '{{#invoke:checkparams|error}}<!-- Validate template parameters -->{{ang-decl-noun<!-- -->|type=strong ''i''-stem<!-- -->|1={{{1}}}{{#if:{{{short|}}}|e}}<!-- -->|3={{{1}}}{{#if:{{{short|}}}|e}}<!-- -->|5={{{1}}}es<!-- -->|7={{{1}}}e<!-- -->|2={{{1}}}as<!-- -->|4={{{1}}}as<!-- -->|6={{{1}}}a<!-- -->|8={{{1}}}um<!-- -->|num={{{num|}}}<!-- -->|title={{{title|}}}<!-- -->}}<!-- -->{{cln|ang|i-stem nouns}}<!-- --><noinclude>{{documentation}}</noinclude>'
55095
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{ang-decl-noun<!--
-->|type=strong ''i''-stem<!--
-->|1={{{1}}}{{#if:{{{short|}}}|e}}<!--
-->|3={{{1}}}{{#if:{{{short|}}}|e}}<!--
-->|5={{{1}}}es<!--
-->|7={{{1}}}e<!--
-->|2={{{1}}}as<!--
-->|4={{{1}}}as<!--
-->|6={{{1}}}a<!--
-->|8={{{1}}}um<!--
-->|num={{{num|}}}<!--
-->|title={{{title|}}}<!--
-->}}<!--
-->{{cln|ang|i-stem nouns}}<!--
--><noinclude>{{documentation}}</noinclude>
1w7259893fsfot07msbc56mj8r7eqw3
55096
55095
2026-10-01T00:04:47Z
Deadend0914
7211
55096
wikitext
text/x-wiki
{{#invoke:checkparams|error}}<!-- Validate template parameters
-->{{ang-decl-noun<!--
-->|type=strang ''i''-stefn<!--
-->|1={{{1}}}{{#if:{{{short|}}}|e}}<!--
-->|3={{{1}}}{{#if:{{{short|}}}|e}}<!--
-->|5={{{1}}}es<!--
-->|7={{{1}}}e<!--
-->|2={{{1}}}as<!--
-->|4={{{1}}}as<!--
-->|6={{{1}}}a<!--
-->|8={{{1}}}um<!--
-->|num={{{num|}}}<!--
-->|title={{{title|}}}<!--
-->}}<!--
-->{{cln|ang|i-stem nouns}}<!--
--><noinclude>{{documentation}}</noinclude>
my5e8azk9b5uel99yll103rnclajmx5
worulddæl
0
8178
55098
2026-10-01T00:06:59Z
Deadend0914
7211
Redirected page to [[worulddǣl]]
55098
wikitext
text/x-wiki
#redirect [[worulddǣl]]
q3uasdo60uvfzmb6yx7y9vjkddwxhs5
Bysen:dercat
10
8179
55099
2026-10-01T00:09:23Z
Deadend0914
7211
Gesceop tramet þe hafaþ '<includeonly>{{#invoke:dercat|dercat}}</includeonly><noinclude>{{documentation}}</noinclude>'
55099
wikitext
text/x-wiki
<includeonly>{{#invoke:dercat|dercat}}</includeonly><noinclude>{{documentation}}</noinclude>
5cd5b1zpnezetgb5ibnpc1g49queo4o
Module:dercat
828
8180
55100
2026-10-01T00:09:56Z
Deadend0914
7211
Gesceop tramet þe hafaþ 'local export = {} local etymology_module = "Module:etymology" local parameters_module = "Module:parameters" local utilities_module = "Module:utilities" local insert = table.insert local require = require --[==[ Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after...'
55100
Scribunto
text/plain
local export = {}
local etymology_module = "Module:etymology"
local parameters_module = "Module:parameters"
local utilities_module = "Module:utilities"
local insert = table.insert
local require = require
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]==]
local function check_ancestor(...)
check_ancestor = require(etymology_module).check_ancestor
return check_ancestor(...)
end
local function format_categories(...)
format_categories = require(utilities_module).format_categories
return format_categories(...)
end
local function process_params(...)
process_params = require(parameters_module).process
return process_params(...)
end
local function language_or_string(val)
return val == "<" and "string" or "language"
end
function export.dercat(frame)
local args = process_params(frame:getParent().args, {
[1] = {required = true, type = "full language"},
[2] = {list = true, required = true, type = language_or_string, family = true},
["inh"] = {type = "number"},
})
local target_lang, langs, categories, cutoff = args[1], args[2], {}
local target_lang_code, target_lang_name = target_lang:getCode(), target_lang:getCanonicalName()
for i = 1, #langs do
local source_lang = langs[i]
if source_lang == "<" then
-- Disallow "inh" and "<" together.
if args.inh then
error(("Cannot specify parameter \"inh\" if \"<\" has been set (see parameter %d): use one or the other."):format(i))
-- Disallow multiple "<"s.
elseif cutoff then
-- Collate keys for all "<"s in the input; all keys need to be offset by 1, since args[1] isn't in the table.
local lt = {cutoff + 1, i + 1}
while langs[i] do
i = i + 1
if langs[i] == "<" then
insert(lt, i + 1)
end
end
error(("Cannot specify \"<\" more than once, but currently used in parameters %s."):format(mw.text.listToText(lt)))
end
cutoff = i
-- Add inheritance categories for all languages encountered so far.
for j = 1, i - 1 do
local inh_source_lang = langs[j]
check_ancestor(target_lang, inh_source_lang)
insert(categories, j * 2, target_lang_name .. " terms inherited from " .. inh_source_lang:getDisplayForm())
end
else
-- Generate the "derived from" category, accounting for the special case when a language derives from itself.
-- Note: "borrowed back into" uses :getCanonicalName(), while the other categories use :getDisplayForm().
insert(categories, target_lang_name .. " terms " .. (target_lang_code == source_lang:getCode() and
"borrowed back into " .. source_lang:getCanonicalName() or
"derived from " .. source_lang:getDisplayForm()
))
if args.inh and i <= args.inh then
check_ancestor(target_lang, source_lang)
insert(categories, target_lang_name .. " terms inherited from " .. source_lang:getDisplayForm())
end
end
end
return format_categories(categories, target_lang)
end
return export
q3dqmzjxe1e513c038q440p97jfu4t3